Organização além do Taskwarrior

Traduções: en
20/01/2021

Parte da série "Como eu me organizo":

  1. Gerenciando minhas tarefas usando o VIT
  2. Organização além do Taskwarrior

No artigo anterior dessa série, eu falei sobre todas as minhas customizações no Taskwarrior e no VIT, e meu fluxo de trabalho com eles, que me permite organizar e concluir minhas tarefas. No entanto, apenas tarefas não são o suficiente para se organizar.

Outro componente crucial na organização é ter um calendário. Ele te permite estar ciente de tarefas e eventos que são sensíveis ao tempo (por exemplo, com data de entrega), e também tomar decisões bem informadas no momento de marcar novos eventos. Claro que nada disso é novo, e é até parte do GTD.

Mas algo que não é parte do GTD, e que eu senti falta, é algo para estabelecer uma rotina. O GTD é um ótimo sistema para acompanhar as diferentes tarefas de cada projeto na sua vida, e para concluí-los, mas ele não tem a menor preocupação em reservar diferentes porções do seu dia ou semana para fazer os básicos (como comer), fazer algo regularmente (como praticar piano), ou avançar nas tarefas gerais. Ele apenas organiza o que fazer e em quais contextos, mas não exatamente quando, o que pode deixar a desejar, quando se está tentando aproveitar bem o tempo.

Portanto, além de ter o VIT como meu organizador central de tarefas, meu sistema também precisa

  • de um calendário decente, de alguma forma integrado ao VIT para também mostrar os prazos de tarefas, além dos compromissos normais
  • um jeito de configurar uma rotina, e de ser constantemente lembrado dela

Vamos ver sobre cada um deles.

Calendário usando calcurse

Antes de mais nada, o próprio Taskwarrior na verdade tem um calendário, que pode ser visto com task calendar, mas sinceramente ele é inútil. Só é possível ver quais dias tem tarefas, mas não quais são essas tarefas.

Eu queria um calendário que me desse uma boa visão geral dos compromissos mensais e semanais, que fosse leve (de preferência de terminal), e customizável o suficiente para ser integrado ao VIT. Eu acabei usando o calcurse.

Agora, a grande questão era: Como eu posso ter minhas tarefas aparecendo no calcurse? Bom, já que o calcurse usa um arquivo de texto com uma sintaxe simples para armazenar todos os compromissos, eu só precisava de um script que lesse todas as tarefas do Taskwarrior e escrevesse na saída os compromissos usando a sintaxe do calcurse. E esse foi o script que eu escrevi:

#!/bin/python

from tasklib import TaskWarrior
import sys

tw = TaskWarrior('/home/nfraprado/.task/data')

# Parse appointments
apts = tw.tasks.filter('(status:pending or status:waiting or status:completed)', type='cal')
for apt in apts:
    start = apt['scheduled']
    if start is None:
        sys.stderr.write(f"Apt '{apt}' has no sched date!\n")
        continue

    summary = str(apt)

    if start.hour == 0 and start.minute == 0:
        start_fmt = start.strftime("%m/%d/%Y")
        print(f"{start_fmt} [1] {summary}")
    else:
        start_fmt = start.strftime("%m/%d/%Y @ %H:%M")

        if apt['due']:
            end_fmt = apt['due'].strftime("%m/%d/%Y @ %H:%M")
        else:
            end_fmt = start_fmt

        print(f"{start_fmt} -> {end_fmt}|{summary}")

# Parse due dates for next actions and projects
tasks = tw.tasks.filter('(status:pending or status:waiting) and (type:next or '
                       'type:objective or type:standby)')
for task in tasks:
    for date_type, label in [('due', "Prazo final: "),
                             ('scheduled', "Prazo inicial: ")]:
        if not task[date_type]: # Skip tasks with no date
            continue
        start = task[date_type]

        proj = "Projeto: " if task['type'] == "objective" else ""

        summary = label + proj + str(task)

        if start.hour == 0 and start.minute == 0:
            start_fmt = start.strftime("%m/%d/%Y")
            print(f"{start_fmt} [1] {summary}")
        else:
            start_fmt = start.strftime("%m/%d/%Y @ %H:%M")
            end_fmt = start_fmt
            print(f"{start_fmt} -> {end_fmt}|{summary}")

Esse script basicamente define quais as tarefas que vão ser mostradas no calendário e em qual formato. Primeiro, tem as tarefas cal, que, se você lembrar do artigo passado, são meus compromissos, e são a principal coisa a ser mostrada no calendário. Cada uma delas é convertida em uma entrada no calcurse, com a data scheduled usada como a hora de início do compromisso, e a data due como a data de término. O texto do compromisso é simplesmente o texto de descrição da tarefa.

O outro caso são as tarefas normais, que aparecem não como um evento contínuo no calendário, mas sim uma entrada para a data de início, para me mostrar quando eu posso começar a fazer a tarefa, e outra para a data de término, para me mostrar até quando eu preciso concluí-la. Além disso, essas sim possuem identificadores a mais no texto de descrição do compromisso, para diferenciá-los dos compromissos normais. Para isso, o script adiciona "Prazo inicial: " à descrição da data de início e "Prazo final: " à descrição da data de término. Por fim, se a tarefa é do tipo objective, ela além disso tem "Projeto: " adicionado à sua descrição no calendário, significando que essa data é relevante para o projeto como um todo e não a uma única próxima ação.

Inicialmente, era isso. Eu mapeei uma tecla no VIT para rodar esse programa e então recarregava os compromissos do calcurse com R. Dessa forma, eu precisava pressionar duas teclas em duas janelas diferentes para ver o calendário atualizado.

Depois de um tempo, eu descobri que o calcurse também suporta hooks (assim como o Taskwarrior, como mostrado no artigo anterior) e adicionei um hook pre-load com o seguinte:

taskwarrior-task2cal > /home/nfraprado/.calcurse/apts

O que significa que quando eu pressiono R no calcurse, ele automaticamente roda meu script para exportar as tarefas para o arquivo do calcurse, e portanto eu agora preciso apertar uma única tecla no calcurse para ver meu calendário atualizado! 🙂

O gif a seguir mostra tarefas next e cal sendo adicionadas no Taskwarrior e automaticamente aparecendo no calcurse:

Tarefas sendo mostradas no Taskwarrior e aparecendo automaticamente no calcurse

Em asciinema.org

Outra pequena coisa que eu tenho é a configuração notification.command do calcurse com o seguinte:

calcurse --next | sed -n -e '2s/.*\] \(.*\)/\1/p' | xargs -I '{}' notify-send '  Upcomming appointment' '{}'

Isso faz com que ele mostre uma notificação no meu sistema algum tempo (configurável, eu uso 10 minutos) antes de cada compromisso, com sua descrição.

Rotina usando python

O primeiro passo para manter uma rotina é, claro, criá-la.

Eu queria um jeito simples e fácil de definir e depois editar minha rotina, então eu a implementei usando dicionários em python, onde a rotina de cada dia da semana é dada por um dicionário separado.

A ideia é que a chave define a hora de início de uma ação, e o valor correspondente define a ação em si. A ação é considerada a atual a partir dessa hora até a hora da próxima ação. Por exemplo, eu tenho o seguinte dicionário base para as rotinas:

base = {
    '08': "Banho+café",
    '12': "Almoçar",
    '15': "Piano",
    '19': "Jantar",
    '24': "Dormir",
}

Se ele fosse usado como uma rotina, significaria que a rotina começa com "Banho+café" das 8 da manhã até meio-dia, quando mudaria para "Almoçar", e assim por diante.

Como todo dicionário em python, eu posso estendê-lo para implementar a rotina de um dia:

segunda = dict(base)
segunda.update({
    '09': "Tarefas",
})

Agora, considerando segunda como a rotina, "Banho+café" só vai até às 9, quando muda para "Tarefas", que por sua vez vai até meio-dia, quando "Almoçar" começa, igual antes, etc.

Um valor também pode ser deletado, como sempre, usando del segunda['09'], por exemplo.

Para definir a minha rotina semanal nesse sistema, eu preciso apenas criar um dicionário para cada dia da semana usando nomes específicos para as variáveis (segunda, terça, quarta, quinta, sexta, sábado e domingo).

Eu gosto desse sistema porque eu posso adicionar ações simplesmente adicionando seu nome e hora de início, e também porque eu posso adicionar ações comuns em um dicionário base que é estendido pelo dicionário de cada dia, reduzindo redundância.

Em seguida, eu tenho um módulo python que sabe como ler cada um dos dicionários para retornar as informações de interesse:

import datetime

import cur_sched


scheds = [cur_sched.segunda, cur_sched.terça, cur_sched.quarta,
          cur_sched.quinta, cur_sched.sexta, cur_sched.sábado,
          cur_sched.domingo]

gran = 30
max_minute = 60 - gran


def get_cur_wday_time():
    weekday = datetime.datetime.today().weekday()
    hour = datetime.datetime.today().hour
    minute = (datetime.datetime.today().minute // gran) * gran
    if hour == 0:
        hour = 24

    return weekday, hour, minute


def format_time(hour, minute):
    if minute > 0:
        time = f"{hour:02}h{minute:02}"
    else:
        time = f"{hour:02}"

    return time


def get_current():
    weekday, hour, minute = get_cur_wday_time()
    return get_sched(weekday, hour, minute)


def get_sched(weekday, hour, minute):
    for m in range(minute, -1, -gran):
        try:
            return scheds[weekday][format_time(hour, m)]
        except:
            pass
    for h in range(hour - 1, 1, -1):
        for m in range(max_minute, -1, -gran):
            try:
                return scheds[weekday][format_time(h, m)]
            except:
                pass
    return ''


def get_new():
    weekday, hour, minute = get_cur_wday_time()
    try:
        return scheds[weekday][format_time(hour, minute)]
    except:
        return ''

get_current() retorna a ação atual da rotina com base na hora e dia atuais. get_new() faz o mesmo, mas apenas se a ação acabou de começar. Por exemplo, se "Piano" vai das 3 até às 4 da tarde, e considerando uma granularidade de 30 minutos (que eu estou usando atualmente), ela vai retornar "Piano" apenas entre 3 e 3:30 da tarde.

Para que eu sempre possa facilmente ver qual é a ação atual com base na minha rotina, eu tenho um bloco do i3blocks específico para isso na barra do meu sistema:

Barra do sistema mostrando a ação atual da rotina: "Piano"

Ele simplesmente chama o get_current() do módulo python anterior.

Mas apenas saber a ação atual não é o suficiente, eu preciso ser notificado quando a ação atual da rotina mudar. É por isso que eu também tenho um job do cron que roda a cada 30 minutos e executa a get_new() para checar se a ação atual da rotina mudou e se sim, me mostra uma notificação:

Notificação mostrando a ação atual com base na rotina: "Schedule change: Piano"

Por fim, também é útil ver a rotina completa da semana de vez em quando, então eu também tenho um script que mostra ela no terminal, usando uma cor diferente para cada ação na rotina, e com as cores definidas aleatoriamente (então elas mudam a cada execução do programa):

#!/bin/python

import schedule
import colored

weekday_name = ["2a", "3a", "4a", "5a", "6a", "Sáb", "Dom"]
color = True

print("      ", end='')
for weekday in range(0, 7):
    print(f"{weekday_name[weekday]:15}", end='')
print()


def get_color(text):
    hex_num = hex(hash(text) % (16 ** 6))
    hex_num6 = hex_num[:2] + hex_num[2:].rjust(6, '0')
    return hex_num6.replace('0x', '#')


for hour in range(8, 25):
    for minute in range(0, schedule.max_minute + 1, schedule.gran):
        if hour == 24 and minute != 0:
            break
        print(f"{schedule.format_time(hour, minute):5}" + " ", end='')
        for weekday in range(0, 7):
            text = schedule.get_sched(weekday, hour, minute)
            if color:
                print(colored.stylize(f"{text:15}", colored.fg(get_color(text))),
                      end='')
            else:
                print(f"{text:15}", end='')
        print()

Extra: tarefas na barra do sistema

Como eu já uso i3blocks como a barra do meu sistema, eu adicionei também um bloco com um resumo da situação das minhas de tarefas para me ajudar a ficar atento a elas e revisá-las regularmente (e não apenas durante a revisão semanal):

Barra do sistema mostrando um resumo das tarefas

O texto no começo mostra o contexto atual, nesse caso, sp. Os três números seguintes são o número de tarefas in pendentes (em amarelo), o número de projetos "empacados" (em magenta) e o número de tarefas com prazo final para essa semana (em vermelho).

Melhorias futuras

E isso é tudo sobre meu sistema de organização. É basicamente o VIT sobre o Taskwarrior para organizar minhas tarefas, o calcurse para mostrar meu calendário, e blocos na barra do sistema e notificações para me ajudar a acompanhar e para avisar sobre as tarefas, rotina e compromissos.

Esse sistema funciona bem, mas ainda há melhorias possíveis. Principalmente integração com o meu celular. Como eu mencionei anteriormente, isso não é um problema no momento já que estou sempre em casa, mas assim que a pandemia acabar, eu preciso de um bom jeito de ter minhas tarefas no celular, e sincronizadas com o meu computador. Eu preciso no mínimo poder facilmente adicionar tarefas in, e ver meus relatórios, opcionalmente com algum filtro. Também vou precisar de um calendário com a mesma integração com o Taskwarrior que eu tenho no computador. Quem sabe com todo o movimento de "Convergência" acontecendo na Purism, eu acabe comprando um Librem 5 e tendo um sistema bem parecido nos dois dispositivos 😃.