Nathan Grigg

Repeating tasks for TaskPaper

I’ve been kicking the tires of TaskPaper lately. I’m intrigued by its minimalist, flexible, plain-text approach to managing a to-do list.

I have a lot of repeating tasks, some with strange intervals. For example, once per year, I download a free copy of my credit report. But I can’t just do it every year on January 1, because if I’m busy one year and don’t do it until the 4th, I have to wait until at least the 4th the following year. You see the problem. The solution is to give myself a buffer, and plan on downloading my credit report every 55 weeks.

Taskpaper has no built-in support for repeating tasks, but its plain-text format makes it easy to manipulate using external scripts. So, for example, I can keep my repeating tasks in an external file, and then once a month have them inserted into my to-do list.

The plain-text calendar tool when, which I also use to remember birthdays, seems like the perfect tool for the job. You store your calendar entries in a text file using a cron-like syntax. You can also do more complicated patterns. For example, I put this line in my file:

!(j%385-116), Transunion credit report

The expression !(j%385-116) is true whenever the modified Julian day is equal to 116 modulo 385. This happens every 385 days, starting today.

When I run when with my new calendar file, I get this output:

today      2014 Feb 22 Transunion credit report

I wrote a quick Python script to translate this into TaskPaper syntax.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/python

import argparse
from datetime import datetime
import re
import subprocess

WHEN = "/usr/local/bin/when"

def When(start, days, filename):
    command = [
            WHEN,
            "--future={}".format(days),
            "--past=0",
            "--calendar={}".format(filename),
            "--wrap=0",
            "--noheader",
            "--now={:%Y %m %d}".format(start),
            ]
    return subprocess.check_output(command)


def Translate(line):
    m = re.match(r"^\S*\s*(\d{4} \w{3} +\d+) (.*)$", line)
    try:
        d = datetime.strptime(m.group(1), "%Y %b %d")
    except AttributeError, ValueError:
        return line
    return "    - {} @start({:%Y-%m-%d})".format(m.group(2), d)


def NextMonth(date):
    if date.month < 12:
        return date.replace(month=(date.month + 1))
    else:
        return date.replace(year=(date.year + 1), month=1)


def StartDateAndDays(next_month=False):
    date = datetime.today().replace(day=1)
    if next_month:
        date = NextMonth(date)
    days = (NextMonth(date) - date).days - 1
    return date, days


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
            description="Print calendar items in taskpaper format")
    parser.add_argument("filename", help="Name of calendar file")
    parser.add_argument("-n", "--next", action="store_true",
            help="Use next month instead of this month")
    args = parser.parse_args()

    date, days = StartDateAndDays(args.next)
    out =  When(date, days, args.filename)
    for line in out.split('\n'):
        if line:
            print Translate(line)

This takes the when output, and translates it into something I can dump into my TaskPaper file:

- Transunion credit report @start(2014-02-22)