The Joy of Over-Engineering

June 07, 2011 by Gabe | [mmd] |

I really loved the OmniFocus CLI posted over on I loved it so much that I decided to build my own, but instead of a shell script, I wanted to explore a solution in Python. I solve quite a bit of my own little problems using Python. I'm no expert, but I like this scripting language for small solutions. I also wanted an excuse to learn more about Python's Natural Language Processing (NLP).

That's where the fun is. Solve your own problems with unique solutions. You don't have to be a computer science geek. Start by copying other people's work. Customize it, change it, make it yours (but add the proper attribution in the code). I use whatever language suits the task or that I want to learn more about. I make stuff in Python, AppleScript, Objective C, Java, shell commands, and Perl. I still want to make something in Ruby but have not found the right project. Make no mistake, I'm a hack and an amateur. But we all have to start somewhere.


In this case, I did quite a bit of research into the Python NLP libraries. NLP is some impressive stuff. If you've never thought you had a need for it, just think about how apps like OmniFocus or Fantastical parse natural language dates like "next Tuesday at 10am" into a date-time like 06/08/2011 10:00:00. That's Natural Language Processing. I'm guessing that both example apps rely on Apple's NSDate in Cocoa since it directly supports plain language dates and times. However, I wanted to try to parse dates from anywhere, not just in a Cocoa application.

Inspired by the OmniFocus CLI from, my test case was to parse a task with a date into OmniFocus from Launchbar.

The parsedatetime Library

The library that seemed the easiest to use and also worked amazingly well was the parsedatetime library. After picking the library, the rest was relatively straight forward. Parsedatetime has some handy tricks. For example, "eod" for end of day. Strings like "next thursday at noon" are also easily converted to dates. There's not much to using the module.

Sidetrack: Installing new python library "eggs"

Python has a couple very simple ways to install new libraries. If you want the simplest way to install an up-to-date version of the library, just use the "Easy Install" route. But first you need to install the setuptools library.
  1. Download the version of setuptools for your Python install (my python is version 2.6).
  2. Change the extension of the download to "egg" from "sh"
  3. From the Terminal change directory to where the egg file is located
  4. Type the following command to install: sudo sh setuptools-0.6c11-py2.6.egg
Notice this egg needs to be installed with admin rights.

To install a new "egg" file like parsedatetime you use the easy_install command you just finished with. Type the following from the Terminal:

easy_install parsedatetime
That's it. You've installed two great tools for use with Python.

The Pay Off

So, after all of that, we have some pretty nice technology for parsing dates. I wrote a python script as an example. It accepts a command line argument that is in the OmniFocus task format. For example running this from the Terminal:
python "Lunch next monday with John @work >networking"
Prints this:
Start Date: 2011-06-06 09:00:00
Context: work
Project: networking
Lunch next monday with John
The script not only parses the date of the task but also gets the context and project. Finally, it removes the context and project from the task string. Pretty neat, and it's all done in Python. I learned a bunch, but in the end I'll choose practicality over cleverness.

An Easier Way

This is where we come full circle. Sometimes over-engineering a solution can be frustrating. But most of the time it is at least educational. After tinkering with this script for a couple of days, I decided that the best solution is to use the AppleScript hooks provided by the OmniGroup.

These 5 lines of AppleScript does the same thing that my entire Python script does.

tell application "OmniFocus"
        tell default document
            parse tasks with transport text theText as single task singleTask
        end tell
end tell
That's really all you need to create a task from a natural language date. The context and project tags will be obeyed as well. You can't get much more simple than that, and that's one example of why the OmniGroup is so awesome. Even their AppleScript support is something to marvel at.

Note, if you would like a list of all the references I used for this little project, visit my public Pinboard feed.