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 #!/usr/bin/python
 2 
 3 import argparse
 4 from datetime import datetime
 5 import re
 6 import subprocess
 7 
 8 WHEN = "/usr/local/bin/when"
 9 
10 def When(start, days, filename):
11     command = [
12             WHEN,
13             "--future={}".format(days),
14             "--past=0",
15             "--calendar={}".format(filename),
16             "--wrap=0",
17             "--noheader",
18             "--now={:%Y %m %d}".format(start),
19             ]
20     return subprocess.check_output(command)
21 
22 
23 def Translate(line):
24     m = re.match(r"^\S*\s*(\d{4} \w{3} +\d+) (.*)$", line)
25     try:
26         d = datetime.strptime(m.group(1), "%Y %b %d")
27     except AttributeError, ValueError:
28         return line
29     return "    - {} @start({:%Y-%m-%d})".format(m.group(2), d)
30 
31 
32 def NextMonth(date):
33     if date.month < 12:
34         return date.replace(month=(date.month + 1))
35     else:
36         return date.replace(year=(date.year + 1), month=1)
37 
38 
39 def StartDateAndDays(next_month=False):
40     date = datetime.today().replace(day=1)
41     if next_month:
42         date = NextMonth(date)
43     days = (NextMonth(date) - date).days - 1
44     return date, days
45 
46 
47 if __name__ == "__main__":
48     parser = argparse.ArgumentParser(
49             description="Print calendar items in taskpaper format")
50     parser.add_argument("filename", help="Name of calendar file")
51     parser.add_argument("-n", "--next", action="store_true",
52             help="Use next month instead of this month")
53     args = parser.parse_args()
54 
55     date, days = StartDateAndDays(args.next)
56     out =  When(date, days, args.filename)
57     for line in out.split('\n'):
58         if line:
59             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)