Deprecated: Return type of I::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 62

Deprecated: Return type of I::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 91

Deprecated: Return type of I::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 71

Deprecated: Return type of I::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 101

Deprecated: Return type of I::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 53

Deprecated: Return type of Collection::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/collection.php on line 80

Deprecated: parse_str(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/url.php on line 135
One Tap Less | Listacular & Pythonista

Listacular & Pythonista

Hello! I wrote an improved version of this workflow. You can find it here.

Plain text apps on your phone are great, specially when they expand its possibilities. TaskPaper is a great task management system and Listacular is a great and gorgeous way to keep lists, however, both cases come with a syntax that keeps making things hard. Every task must be preceded by a hyphen and a space, meaning that do it is not a task, but - do it is. I hate these extra keyboard taps, so let's fix this thing altogether.

The Drafts-only way

I want a super simple list, one item per line. I can achieve that with Drafts only by using a quirky Dropbox rule and 2 other rules, one to recreate the document as we can't chain from a Dropbox action, unfortunately. You can download the Dropbox action here, I'll get in detail on the two other rules next.

The Dropbox action you just installed prepends the [[title]] of the actual draft preceded by the "- " to convert it into a task. The file is predefined as Groceries.txt and the path is my Listacular folder within Apps. We're going to call this rule again numerous times.

Groceries to Listacular

drafts://x-callback-url/create?text=[[draft]]&action=Add%20Groceries%20to%20Listacular&x-success={{drafts://x-callback-url/create?text=[[draft]]&action=Listacular%20Helper}}

This is the action you call to start the chain. It recreates your draft and plays our Dropbox action (called Add Groceries to Listacular), after succeeding it creates another full draft and calls the action Listacular Helper.

Listacular Helper

drafts://x-callback-url/create?text=[[body]]&action=Add%20Groceries%20to%20Listacular&x-success={{drafts://x-callback-url/create?text=[[body]]&action=Listacular%20Helper}}

This little action here will create a new draft composed with the [[body]] tag, the [[title]] was already added in the previous action, then it will play the Dropbox action again, appending the first line of the new draft to our plain text document, this line is actually the second line from our whole document. After succeeding, the rule creates a new document with the [[body]] and calls itself. It is a bit ugly to watch it doing the stuff, but it works, when you open Listacular or Taskpaper, you'll have an appropriate list of tasks right there for you.

The Pythonista way

Ok, I couldn't wait to finish the first part of this article to start discussing Python. If you're a smart guy, you bought Pythonista for $0.99 one month ago, otherwise you gotta pay $6.99, but it is totally worth as it drastically expands our ways to deal with automatization.

First, we create a regular Drafts action I like to call Convert to Listacular:

pythonista://Convert2Listacular?action=run&args=[[draft]]

This action calls Pythonista and runs a workflow called Convert2Listacular, but before exploring my poorly written Python code, let's add one more action in Drafts, this time a Dropbox action called Pytho-Groceries. This rule is almost the same as the previous Dropbox action I listed, the only difference is that instead of - [[title]] you have a basic [[draft]]. Ok, let's move to Python and have some fun.

import urllib
import webbrowser

list = sys.argv[-1].splitlines()

tasks = []
lineb = []

for t in list:
    f = '- ' + t
    tasks.append(f)

for l in tasks[:-1]:
    s = l + '\n'
    lineb.append(s)

lineb.append(tasks[-1])

tasks = ''.join(lineb)
encoded = urllib.quote(tasks)

drafts = 'drafts://x-callback-url/create?text=' + encoded + '&action=Pytho-Groceries'

webbrowser.open(drafts)

Ok, let's go in parts Jack. I import two modules in the beginning, gonna use them by the end of the workflow1. Then I define the variable list as sys.argv[1], which picks the argument we brought from Drafts. The list comes like this: bananas\milk\beer, then I apply the splitlines() function to convert it to a Python list: ['bananas','milk','beer']. That's the value of our list variable.

Afterwards, I create 2 empty lists called tasks and lineb, I could create the second later, but screw it. Then comes the first loop:

for t in list:
    f = '- ' + t
    tasks.append(f)

This loop picks every element within the list, they're called t here, then it appends a - and calls this variable as f, afterwards, it appends f to our empty list tasks. Then it loops until there are no more items left. If you're lost, this is how tasks looks like in the end: ['- bananas','- milk','- beer']. Next, we need line breaks.

for l in tasks[:-1]:
    s = l + '\n'
    lineb.append(s)

Almost the same thing as the previous loop, with a single difference: I exclude the last item from the loop, so it doesn't get a line break which would later render into a blank line on our apps. The results are appended to our previously created lineb list, which right now looks like this: ['- bananas\n','- milk\n']. Oh, shit, where's our beer? We're going to add it in the next line using lineb.append(tasks[-1]). You didn't think I'd let you out of beer, did you? Now it looks perfectly fine, but the last item doesn't have a line break2.

We still must convert our list into a string before encoding it and delivering it back to Drafts. The tasks = ''.join(lineb) will pick lineb and glue it together into a new tasks variable3. Then encoded = urllib.quote(tasks) will use one of the modules we imported in the beginning of the code to grab the new tasks and percent-encode it so Drafts can read it fine.

drafts = 'drafts://x-callback-url/create?text=' + encoded + '&action=Pytho-Groceries'

This line creates a new variable called drafts and inserts the result from our encoding in the text field of our upcoming Drafts comeback. It also calls the action Pytho-Groceries we did before, remember? To call drafts we use the last line, which relies on the last module we imported, webbrowser.open(drafts). Now just watch Drafts do all the magic.

The Pythonista way is great because you aren't limited to this action. If you tweak it a little bit you can use the First Line statement as the title of your Dropbox-file and pick whatever file you want4.

I feel this will open a huge door when it comes to the future of this little blog. Now serve yourself.

UPDATE: Eric Pramono kicked my ass in lightning speed and cut my action down to almost a tweet:

from urllib import quote
from webbrowser import open

list = sys.argv[1].splitlines()

list = "\n".join(('- ' + x) for x in list)
encoded = quote(list)

drafts = 'drafts://x-callback-url/create?text=' + encoded + '&action=Pytho-Groceries'

open(drafts)

Show him some love at his site.


  1. As far as I understood, modules are packages of functions. 

  2. I'm 99% sure that I could achieve this same result using while and maybe if within a single loop, but this is my first code, so be kind. 

  3. Yes, I'm not creative with names. 

  4. Free hint, I believe you can achieve that by changing for t in list: to for t in list[1:]:, and then after the loop do a tasks.insert(0,list[0]) or something like this.