Created
September 30, 2008 22:08
-
-
Save ebot/13972 to your computer and use it in GitHub Desktop.
A script that generates a gtd worlist from your backpack pages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Creates a weekly gtd report from your backpack account | |
# Based on the sample wrapper script from | |
# David Heinemeier Hansson, 37signals and | |
# Gmail sample script from shunsuk | |
# Requires that XmlSimple, RedCloth, tlsmail, and time | |
# are installed. | |
# Assumptions: | |
# ** All personal/home pages start with a space ' ' | |
# ** All closed projects start with zzc | |
# ** All waiting projects start with zw | |
# Author: Ed Botzum | |
require 'net/https' | |
require 'rexml/document' | |
require 'yaml' | |
require 'rubygems' | |
require 'RedCloth' | |
require 'XmlSimple' | |
require 'tlsmail' | |
require 'time' | |
include REXML | |
class Gmail | |
attr_accessor :username, :password | |
def initialize(username, password) | |
@username, @password = username, password | |
end | |
def send_mail(recepient, subject, content) | |
msg = <<EOF | |
From: #{@username} | |
To: #{recepient} | |
MIME-Version: 1.0 | |
Content-type: text/html | |
Subject: #{subject} | |
Date: #{Time.now.rfc2822} | |
#{content} | |
EOF | |
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE) | |
Net::SMTP.start('smtp.gmail.com', 587, 'gmail.com', @username, @password, :login) do |smtp| | |
smtp.send_message(msg, @username, recepient) | |
end | |
end | |
end | |
class Backpack | |
attr_accessor :username, :token, :ignore_pages | |
def initialize(username, token) | |
@username, @token = username, token | |
connect | |
end | |
def generate_gtd_report | |
report = <<EOF | |
<style type='text/css' media='screen'> | |
li {font-size: .8em;} | |
h1 {border-bottom: solid thick black;padding-left: .3em;} | |
h2 {border-bottom: solid thin black} | |
h3 {border-bottom: solid thin black} | |
#active_projects h4 {border-bottom: solid thin black} | |
</style> | |
EOF | |
report << "h1. My Worklist\n" << | |
"\nThis is your weekly GTD wrklist report.\n" | |
# List all reminders for the upcoming week | |
report << "\nh2. Reminders\n\n" | |
list_reminders.each do |reminder| | |
report << "* #{reminder['remind_at']}: #{reminder['content']}\n" | |
end | |
# Get the list of pages | |
projects = { :home => [], :work => [], :waiting => [], :closed => [] } | |
list_pages.each do |page| | |
page_info = {:title => page['title'], :id => page['id']} | |
# Split into groups of home, work, closed, and waiting | |
if page['title'][0,1] == ' ' then | |
projects[:home] << page_info | |
elsif page['title'][0,3] == 'zzc' | |
projects[:closed] << page_info | |
elsif page['title'][0,2] == 'zw' | |
projects[:waiting] << page_info | |
else | |
projects[:work] << page_info | |
end | |
end | |
report << "\nh2. Projects\n" | |
# List all work projects | |
report << "\n<div id=\"active_projects\">\n" | |
report << list_projects('work', 'Work Projects', projects[:work]) | |
report << list_projects('home', 'Home Projects', projects[:home]) | |
report << "\n</div>\n" | |
report << list_projects('waiting', 'Projects on Hold', projects[:waiting]) | |
report << list_projects('closed', 'Completed Projects', projects[:closed]) | |
# Generate the report | |
return RedCloth.new(report).to_html | |
end | |
private | |
# Backpack API Methods | |
def connect(use_ssl = false) | |
@connection = Net::HTTP.new("#{@username}.backpackit.com", use_ssl ? 443 : 80) | |
@connection.use_ssl = use_ssl | |
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if use_ssl | |
end | |
def request(path, parameters = {}, second_try = false) | |
parameters = { "token" => @token }.merge(parameters) | |
response = @connection.post("/ws/#{path}", parameters.to_yaml, "X-POST_DATA_FORMAT" => "yaml") | |
if response.code == "200" | |
result = XmlSimple.xml_in(response.body) | |
result.delete "success" | |
result.empty? ? true : result | |
elsif response.code == "302" && !second_try | |
connect(true) | |
request(path, parameters, true) | |
else | |
raise "Error occured (#{response.code}): #{response.body}" | |
end | |
end | |
def list_page_lists(page_id) | |
request("page/#{page_id}/lists/list")['lists'][0]['list'] | |
end | |
def last_item_completed(page_id, list_id) | |
xs = request "page/#{page_id}/lists/#{list_id}/items/list" | |
items = Document.new XmlSimple.xml_out(xs) | |
completed_items = XPath.match( items, "//item[@completed='true']" ) | |
return completed_items[completed_items.size-1] | |
end | |
def list_open_items(page_id, list_id) | |
xs = request "page/#{page_id}/lists/#{list_id}/items/list" | |
items = Document.new XmlSimple.xml_out(xs) | |
completed_items = XPath.match( items, "//item[@completed='false']" ) | |
end | |
def list_pages | |
return request("pages/all")['pages'][0]['page'] | |
end | |
def list_reminders | |
return request("reminders")['reminder'] | |
end | |
# Reporting Methods | |
def list_items(page) | |
list = '' | |
# Get the lists on each page | |
begin | |
list_page_lists(page[:id]).each do |page_list| | |
list << "\nh5. #{page_list['name']}\n\n" | |
last_one = last_item_completed(page[:id], page_list['id']) | |
list <<"* [-#{last_one.text}-]\n" unless last_one == nil | |
count = 0 | |
list_open_items(page[:id], page_list['id']).each do |item| | |
if count == 0 then | |
list << "* *%{color:green}#{item.text}%*\n" | |
elsif count < 3 | |
list << "* #{item.text}\n" | |
end | |
count += 1 | |
end | |
end | |
rescue | |
# There are no valid | |
list = "\n* No valid to-do exist for this project.\n" | |
end | |
return list | |
end | |
def list_projects(category, title, pages) | |
list = "\nh3. #{title}\n" | |
pages.each do |page| | |
unless @ignore_pages.include?(page[:title]) | |
list << "\nh4. #{page[:title]}\n" | |
list << list_items(page) if category == 'work' or category == 'home' | |
end | |
end | |
return list | |
end | |
end | |
bp = Backpack.new 'user_name', 'token' | |
bp.ignore_pages = ['Ignore Works', ' Ignore Home', 'zw - Ignore Waiting'] | |
report = bp.generate_gtd_report | |
gmail = Gmail.new '[email protected]', 'your_password' | |
gmail.send_mail '[email protected]', 'Test Report', report |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" | |
"http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>Label</key> | |
<string>com.edbotz.gtd.plist</string> | |
<key>ProgramArguments</key> | |
<array> | |
<string>/Users/[your_user_name_here]/Library/Scripts/bp_worklist.rb</string> | |
<string>daily</string> | |
</array> | |
<key>LowPriorityIO</key> | |
<true/> | |
<key>Nice</key> | |
<integer>1</integer> | |
<key>StartCalendarInterval</key> | |
<dict> | |
<key>Hour</key> | |
<integer>2</integer> | |
<key>Minute</key> | |
<integer>15</integer> | |
</dict> | |
</dict> | |
</plist> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# bash commands | |
# Create a directory named Scripts in your ~/Library directory. | |
# Put the ruby files in the Scripts directory | |
# Create a directory named LaunchAgents in your ~/Library directory. | |
# Open a bash prompt and navigate to ~/ | |
# Load the service into launchd with the following command: | |
launchctl load Library/LaunchAgents/com.edbotz.gtd.plist | |
# Verify that it was loaded with this command: | |
launchctl list | grep gtd | |
# You can unload it at any time with: | |
launchctl unload Library/LaunchAgents/com.edbotz.gtd.plist |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment