Friday, September 14, 2012

Displaying Remember The Milk (RTM) Tasks on Your Mac Desktop

So if you're like me, you're day-to-day work revolves around a GTD/TODO/Task manager of some sort. If you haven't tried or heard of Remember The Milk, its pretty much free to use. If I remember correctly, you only need to pay the yearly subscription (which really, is at a bargain price if you use it like I do) if you want to use the mobile apps.

I really love RTM because of its deep integration into existing services. You can create tasks by tweeting, sending emails, sending IMs, on your Google Calendar, on your Gmail, etc. Now, to the heart of the post: RTM gives you an atom feed of your tasks via some generated URL. So we can use this and massage the data into whatever we want :D

Geektool, on the other hand, is a dandy Mac tool that lets you, among other things, run scripts and display the results on your desktop. So basically, our workflow to display RTM tasks on a Mac desktop is simple:

  1. Install Geektool.
  2. Grab your RTM tasks atom feed url. There's one for each list, I'd get one for "All Tasks".  
  3. And setup Geektool to run the following ruby script. Done!
The only configuration you need to do for the script is change the URL and TEMP_FILE to point to your atom feed url and to a temporary file which the script will create to cache the last-fetched atom feed.

#!/usr/bin/env ruby

require 'rexml/document'
require 'open-uri'
require 'date'

URL ="YOUR_ATOM_FEED_URL_GOES_HERE"
TEMP_FILE = "ABSOLUTE_PATH_FOR_THE_CACHED_ATOM_FEED_GOES_HERE"

doc = nil
begin
doc = REXML::Document.new(open(URL).read)
rescue SocketError
  File.new(TEMP_FILE).each_line { |line|
    puts line
  }
  exit
end

tasks = {}
dates = []
REXML::XPath.each(doc, "//entry") { |node|
  title = REXML::XPath.first(node, "title/text()").to_s
  title = title[0..91] + "..." if title.size > 95
  date = REXML::XPath.first(node, "content/div/div/span[@class='rtm_due_value']/text()").to_s
  list = REXML::XPath.first(node, "content/div/div/span[@class='rtm_list_value']/text()").to_s
  short_date = date
  unless short_date.match(/\sat\s/).nil?
    short_date = date.scan(/^(.*)\sat/).first.first
  end

  break if (Date.strptime(short_date, "%a %d %b %y") - Date.today).to_i > 7
  break if date == "never"

  if tasks[short_date].nil?
    dates << short_date
    tasks[short_date] = [] if tasks[short_date].nil?
  end
  tasks[short_date] << {
    :title => title,
    :list => list,
    :date => date
  }
}
File.open(TEMP_FILE, 'w') { |f|
  dates.each { |d|
    f.puts d
    puts d
    tasks[d].each { |val|
      time = "        - "
      unless val[:date].match(/\sat\s/).nil?
        time = val[:date].scan(/\sat\s(.+)$/).first.first.gsub(/^(\d):/,'0\1:') + " - "
      end
      f.puts "#{time}#{val[:title]} (#{val[:list]})"
      puts "#{time}#{val[:title]} (#{val[:list]})"
    }
    f.puts ""
    puts ""
  }
  f.puts "[Updated: #{Time.now.to_s}]"
  puts "[Updated: #{Time.now.to_s}]"
}