remember-the-ruby 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1 @@
1
+ This will be a command line interface to Remember the Milk
data/bin/rtr ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'remember-the-ruby'))
4
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rtr'))
5
+
6
+ require 'pp'
7
+
8
+ $storage = RememberTheRuby::Storage.new
9
+ $storage[:cache_timeout] ||= 15.minutes
10
+
11
+ usage_and_exit if ARGV.length < 1
12
+
13
+ command = ARGV.shift
14
+ options = ARGV.dup
15
+
16
+ proc = $methods[command.to_sym]
17
+
18
+ usage_and_exit unless proc
19
+
20
+ proc.call(options)
@@ -0,0 +1,9 @@
1
+ require 'date'
2
+
3
+ class DateTime
4
+
5
+ def blank?
6
+ false
7
+ end
8
+
9
+ end
@@ -0,0 +1,7 @@
1
+ module Enumerable
2
+
3
+ def sorted_by(attribute)
4
+ self.sort_by { |e| e[attribute] }
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class Fixnum
2
+
3
+ def minutes
4
+ self * 60
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class NilClass
2
+
3
+ def blank?
4
+ true
5
+ end
6
+
7
+ end
@@ -0,0 +1,22 @@
1
+ require 'date'
2
+ require 'rubygems'
3
+ require 'term/ansicolor'
4
+
5
+ class String
6
+
7
+ include Term::ANSIColor
8
+
9
+ def to_date
10
+ begin
11
+ date = DateTime.parse(self)
12
+ rescue
13
+ date = nil
14
+ end
15
+ date
16
+ end
17
+
18
+ def blank?
19
+ self == ""
20
+ end
21
+
22
+ end
@@ -0,0 +1,74 @@
1
+ module RememberTheRuby
2
+ class API
3
+
4
+ def initialize(options={})
5
+ @transport = RPC::Transport.new(
6
+ :key => 'cb877b5c450d216ce9e563394dff6c07',
7
+ :secret => '97cbbc48a9b1b54c',
8
+ :frob => options[:frob],
9
+ :token => options[:token]
10
+ )
11
+ end
12
+
13
+ def authorization_url
14
+ self.frob = @transport.auth.get_frob
15
+ @transport.auth.get_authorization_url
16
+ end
17
+
18
+ def authenticate
19
+ self.token = @transport.auth.get_token
20
+ end
21
+
22
+ # first-order objects #####################################################
23
+
24
+ def lists
25
+ @transport.lists.get_list
26
+ end
27
+
28
+ def settings
29
+ @transport.settings.get_list
30
+ end
31
+
32
+ def tasks
33
+ @transport.tasks.get_list
34
+ end
35
+
36
+ # derivative objects ######################################################
37
+
38
+ def default_list
39
+ lists.find(settings["defaultlist"])
40
+ end
41
+
42
+ def tags
43
+ found_tags = EntityList.new(@transport, Tag)
44
+ tasks.each do |task|
45
+ task.tags.each do |tag|
46
+ found = false
47
+ found_tags.each do |found_tag|
48
+ if tag.to_s == found_tag.to_s
49
+ found = true
50
+ break
51
+ end
52
+ end
53
+ found_tags << tag unless found
54
+ end
55
+ end
56
+ found_tags
57
+ end
58
+
59
+ ## accessors ##############################################################
60
+
61
+ def frob
62
+ @transport.frob
63
+ end
64
+
65
+ def frob=(frob)
66
+ @transport.frob = frob
67
+ end
68
+
69
+ def token=(token)
70
+ @transport.token = token
71
+ end
72
+
73
+ end
74
+ end
File without changes
@@ -0,0 +1,57 @@
1
+ module RememberTheRuby
2
+ class Entity < Hash
3
+
4
+ def initialize(transport, data={})
5
+ @transport = transport
6
+ self.merge!(data)
7
+ end
8
+
9
+ def self.from_element(transport, element)
10
+ data = element.attributes.keys.inject({}) do |memo, key|
11
+ memo[key] = element.attributes[key]
12
+ memo
13
+ end
14
+ self.new(transport, data)
15
+ end
16
+
17
+ def self.list_from_elements(transport, rsp, element)
18
+ EntityList.from_element(transport, self, rsp, element)
19
+ end
20
+
21
+ def method_missing(method_name)
22
+ self[method_name]
23
+ end
24
+
25
+ def hydrate_from(&blk)
26
+ entity = yield blk
27
+ entity.each do |key, value|
28
+ self[key] = value
29
+ end
30
+ end
31
+
32
+ alias_method :regular_reader, :[]
33
+ alias_method :regular_writer, :[]=
34
+
35
+ undef id
36
+
37
+ def [](key)
38
+ regular_reader(key.to_s)
39
+ end
40
+
41
+ def []=(key, value)
42
+ regular_writer(key.to_s, value)
43
+ end
44
+
45
+ alias_method :original_lookup, :[]
46
+
47
+ def [](key)
48
+ value = self
49
+ parts = key.to_s.split('/')
50
+ parts.each do |part|
51
+ value = value.original_lookup(part)
52
+ end
53
+ value
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ module RememberTheRuby
2
+ class EntityList < Array
3
+
4
+ attr_accessor :defaults
5
+
6
+ def initialize(transport, type)
7
+ @transport = transport
8
+ @type = type
9
+ @defaults = {}
10
+ end
11
+
12
+ def new
13
+ entity = @type.new(@transport)
14
+ @defaults.each do |key, value|
15
+ entity[key] = value
16
+ end
17
+ entity
18
+ end
19
+
20
+ def find(options)
21
+
22
+ case options
23
+ when String, Integer then
24
+ params = { 'id' => options.to_s }
25
+ amount = :one
26
+ when Hash then
27
+ params = options
28
+ amount = :many
29
+ else
30
+ params = {}
31
+ amount = :many
32
+ end
33
+
34
+ matches = EntityList.new(@transport, @type)
35
+ self.each do |entity|
36
+ matched = true
37
+ params.each do |key, value|
38
+ unless entity[key] == value
39
+ matched = false
40
+ break
41
+ end
42
+ end
43
+ matches << entity if matched
44
+ end
45
+
46
+ amount == :many ? matches : matches.first
47
+ end
48
+
49
+ def self.from_element(transport, type, rsp, xpath)
50
+ entity_list = EntityList.new(transport, type)
51
+ rsp.get_elements(xpath).map do |element|
52
+ entity_list << type.from_element(transport, element)
53
+ end
54
+ entity_list
55
+ end
56
+
57
+ end
58
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ module RememberTheRuby
2
+ class List < Entity
3
+
4
+ def tasks
5
+ tasks = @transport.tasks.get_list(:list_id => self['id'])
6
+ tasks.defaults['list_id'] = self['id']
7
+ tasks
8
+ end
9
+
10
+ end
11
+ end
File without changes
File without changes
@@ -0,0 +1,187 @@
1
+ module RememberTheRuby
2
+ module RPC
3
+
4
+ require 'digest/md5'
5
+ require 'net/http'
6
+ require 'rexml/document'
7
+
8
+ class Transport
9
+
10
+ TRANSPORT_URI = 'http://www.rememberthemilk.com'
11
+
12
+ attr_accessor :key, :secret, :frob, :token
13
+
14
+ def initialize(params)
15
+ @key = params[:key]
16
+ @secret = params[:secret]
17
+ @frob = params[:frob]
18
+ @token = params[:token]
19
+
20
+ @transport_http = build_transport_http
21
+ end
22
+
23
+ public ####################################################################
24
+
25
+ def request(method, params={})
26
+ params[:method] = method
27
+
28
+ uri = URI.encode(rest_path(params))
29
+ data = @transport_http.get(uri)
30
+ doc = REXML::Document.new(data.body)
31
+
32
+ doc.elements['rsp']
33
+ end
34
+
35
+ # rpc types ###############################################################
36
+
37
+ def auth
38
+ @auth ||= Auth.new(self)
39
+ end
40
+
41
+ def lists
42
+ @lists ||= Lists.new(self)
43
+ end
44
+
45
+ def settings
46
+ @settings ||= Settings.new(self)
47
+ end
48
+
49
+ def tasks
50
+ @tasks ||= Tasks.new(self)
51
+ end
52
+
53
+ def timeline
54
+ Timelines.new(self).create
55
+ end
56
+
57
+ # private #################################################################
58
+
59
+ def build_transport_http
60
+ uri = URI::parse(TRANSPORT_URI)
61
+ @transport_http = Net::HTTP.start(uri.host, uri.port)
62
+ end
63
+
64
+ def clear_empty_parameters(params)
65
+ params.dup.each do |k, v|
66
+ params.delete(k) if v.nil? || v == ''
67
+ end
68
+ end
69
+
70
+ def auth_path(params)
71
+ self.service_path('auth', params)
72
+ end
73
+
74
+ def rest_path(params)
75
+ self.service_path('rest', params)
76
+ end
77
+
78
+ def service_path(service, params)
79
+ params[:frob] ||= @frob
80
+ params[:api_key] ||= @key
81
+ params[:auth_token] ||= @token
82
+
83
+ clear_empty_parameters(params)
84
+
85
+ params[:api_sig] = sign_parameters(params)
86
+ "/services/#{service}/?#{flatten_parameters(params)}"
87
+ end
88
+
89
+ def sign_parameters(params)
90
+ signature = @secret
91
+ signature += params.map { |k, v| [k, v].join('') }.sort.join('')
92
+ Digest::MD5.hexdigest(signature)
93
+ end
94
+
95
+ def flatten_parameters(params)
96
+ params.map { |k, v| [k, v].join('=') }.join('&')
97
+ end
98
+
99
+ def hash_from_element(element)
100
+ element.attributes
101
+ end
102
+
103
+ end
104
+
105
+ class Transported
106
+
107
+ def initialize(transport)
108
+ @transport = transport
109
+ end
110
+
111
+ end
112
+
113
+ class Auth < Transported
114
+
115
+ def get_authorization_url(params={})
116
+ params[:perms] ||= 'delete'
117
+ data = Transport::TRANSPORT_URI + @transport.auth_path(params)
118
+ end
119
+
120
+ def get_frob
121
+ rsp = @transport.request('rtm.auth.getFrob')
122
+ rsp.elements['frob'].text
123
+ end
124
+
125
+ def get_token
126
+ rsp = @transport.request('rtm.auth.getToken')
127
+ rsp.elements['auth/token'].text
128
+ end
129
+
130
+ end
131
+
132
+ class Lists < Transported
133
+
134
+ def get_list
135
+ rsp = @transport.request('rtm.lists.getList')
136
+ List.list_from_elements(@transport, rsp, 'lists/list')
137
+ end
138
+
139
+ end
140
+
141
+ class Settings < Transported
142
+
143
+ def get_list
144
+ rsp = @transport.request('rtm.settings.getList')
145
+ rsp.get_elements('settings').first.inject({}) do |memo, element|
146
+ memo[element.name] = element.text
147
+ memo
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+ class Tasks < Transported
154
+
155
+ def add(params={})
156
+ params[:list_id] ||= nil
157
+ params[:name] ||= nil
158
+ params[:parse] ||= nil
159
+
160
+ params[:timeline] = @transport.timeline
161
+ params[:parse] = params[:parse] ? 1 : 0
162
+
163
+ rsp = @transport.request('rtm.tasks.add', params)
164
+ Task.from_element(@transport, rsp.get_elements('list/taskseries').first)
165
+ end
166
+
167
+ def get_list(params={})
168
+ params[:list_id] ||= nil
169
+ params[:filter] ||= nil
170
+ params[:last_sync] ||= nil
171
+ rsp = @transport.request('rtm.tasks.getList', params)
172
+ Task.list_from_elements(@transport, rsp, 'tasks/list/taskseries')
173
+ end
174
+
175
+ end
176
+
177
+ class Timelines < Transported
178
+
179
+ def create(params={})
180
+ rsp = @transport.request('rtm.timelines.create', params)
181
+ rsp.get_elements('timeline').first.text
182
+ end
183
+
184
+ end
185
+
186
+ end
187
+ end
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'preferences'
3
+
4
+ module RememberTheRuby
5
+ class Storage < Preferences::Manager
6
+
7
+ def initialize
8
+ super(:remember_the_ruby, :autosave => true)
9
+ end
10
+
11
+ def set_cache_timeout(key, timeout)
12
+ self[:cache] ||= {}
13
+ self[:cache][key] ||= Time.now + timeout
14
+ self.save
15
+ end
16
+
17
+ def cache_timeout_for(key)
18
+ original_reader(:cache) ? original_reader(:cache)[key] : nil
19
+ end
20
+
21
+ def purge_cache!
22
+ return unless self[:cache].is_a?(Enumerable)
23
+ self[:cache].each do |key, timeout|
24
+ self.delete(key)
25
+ end
26
+ self.delete(:cache)
27
+ self.save
28
+ end
29
+
30
+ alias_method :original_reader, :[]
31
+
32
+ def [](key)
33
+ now = Time.now
34
+ timeout = cache_timeout_for(key) || now
35
+ if timeout < now
36
+ self.delete(key)
37
+ self[:cache].delete(key)
38
+ end
39
+ super
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ module RememberTheRuby
2
+ class Tag < Entity
3
+
4
+ def self.from_element(transport, element)
5
+ tag = super
6
+ tag['name'] = element.text
7
+ tag
8
+ end
9
+
10
+ def to_s
11
+ self['name']
12
+ end
13
+
14
+ def tasks
15
+ tasks = @transport.tasks.get_list(:filter => "tag:#{self["name"]}")
16
+ # TODO: add default tag to the tasks list
17
+ tasks
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module RememberTheRuby
2
+ class Task < Entity
3
+
4
+ def self.from_element(transport, element)
5
+ task = super
6
+ task["occurrences"] = TaskOccurrence.list_from_elements(transport, element, 'task')
7
+ task["next"] = task["occurrences"].sorted_by(:due).first
8
+ task["tags"] = Tag.list_from_elements(transport, element, 'tags/tag')
9
+ task
10
+ end
11
+
12
+ def save!(params={})
13
+ hydrate_from do
14
+ @transport.tasks.add(:name => self[:name], :parse => params[:parse])
15
+ end
16
+ # TODO: apply tags
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module RememberTheRuby
2
+ class TaskOccurrence < Entity
3
+
4
+ end
5
+ end
@@ -0,0 +1,49 @@
1
+ # Equivalent to a header guard in C/C++
2
+ # Used to prevent the class/module from being loaded more than once
3
+ unless defined? RememberTheRuby
4
+
5
+ module RememberTheRuby
6
+
7
+ # :stopdoc:
8
+ VERSION = '0.4.1'
9
+ LIBPATH = File.expand_path(File.dirname(__FILE__)) + File::SEPARATOR
10
+ PATH = File.dirname(LIBPATH) + File::SEPARATOR
11
+ # :startdoc:
12
+
13
+ # Returns the version string for the library.
14
+ #
15
+ def self.version
16
+ VERSION
17
+ end
18
+
19
+ # Returns the library path for the module. If any arguments are given,
20
+ # they will be joined to the end of the libray path using
21
+ # <tt>File.join</tt>.
22
+ #
23
+ def self.libpath( *args )
24
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
25
+ end
26
+
27
+ # Returns the lpath for the module. If any arguments are given,
28
+ # they will be joined to the end of the path using
29
+ # <tt>File.join</tt>.
30
+ #
31
+ def self.path( *args )
32
+ args.empty? ? PATH : ::File.join(PATH, *args)
33
+ end
34
+
35
+ # Utility method used to rquire all files ending in .rb that lie in the
36
+ # directory below this file that has the same name as the filename passed
37
+ # in. Optionally, a specific _directory_ name can be passed in such that
38
+ # the _filename_ does not have to be equivalent to the directory.
39
+ #
40
+ def self.require_all_libs_relative_to(fname)
41
+ search_me = File.expand_path(File.join(File.dirname(fname), '**', '*.rb'))
42
+ Dir.glob(search_me).sort.each { |rb| require rb }
43
+ end
44
+
45
+ end
46
+
47
+ RememberTheRuby.require_all_libs_relative_to __FILE__
48
+
49
+ end # unless defined?
data/lib/rtr/api.rb ADDED
@@ -0,0 +1,22 @@
1
+ def api
2
+
3
+ @api ||= begin
4
+
5
+ api = RememberTheRuby::API.new(:frob => $storage[:frob],
6
+ :token => $storage[:token])
7
+
8
+ unless $storage[:frob]
9
+ system %{open "#{api.authorization_url}"}
10
+ $storage[:frob] = api.frob
11
+ exit
12
+ end
13
+
14
+ unless $storage[:token]
15
+ $storage[:token] = api.authenticate
16
+ end
17
+
18
+ api
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,11 @@
1
+ $methods = {}
2
+
3
+ def register_method(method, &blk)
4
+ $methods[method] = blk
5
+ end
6
+
7
+ def usage_and_exit
8
+ puts "usage: rtr <command> [options]"
9
+ exit 1
10
+ end
11
+
@@ -0,0 +1,67 @@
1
+ require 'term/ansicolor'
2
+ require 'pp'
3
+
4
+ module RTR
5
+ class Commands
6
+
7
+ register_method :add do |options|
8
+ tags, task_name = options.partition { |o| o[0..0] == '@' }
9
+ task_name = task_name.join(' ')
10
+
11
+ task = api.tasks.new
12
+
13
+ task["name"] = task_name
14
+ task["tags"] = tags.map { |tag| tag[1..-1] }
15
+
16
+ task.save!(:parse => true)
17
+
18
+ $storage.purge_cache!
19
+ end
20
+
21
+ register_method :tasks do |options|
22
+
23
+ puts "Oustanding Tasks".white.bold
24
+ puts
25
+
26
+ tasks = $storage[:tasks] || api.default_list.tasks
27
+
28
+ tasks.sorted_by('next/due').reverse.each do |task|
29
+
30
+ now = DateTime.now
31
+
32
+ due = task['next/due']
33
+ due = due.to_date unless due.blank?
34
+
35
+ diff = due.blank? ? nil : due - now
36
+
37
+ color = case
38
+ when diff.nil? then :white
39
+ when diff.abs < 1 && due.day == now.day then :yellow
40
+ when diff < 0 then :red
41
+ else :white
42
+ end
43
+
44
+ title = task.name
45
+ title = title.send(color).bold if color
46
+
47
+ prefix = " *"
48
+ prefix = prefix.send(color).bold if color
49
+
50
+ suffix = task.tags.map { |t| "@#{t.name}" }.join(' ').cyan
51
+
52
+ puts "#{prefix} #{title} #{suffix}"
53
+
54
+ end
55
+
56
+ $storage[:tasks] = tasks
57
+ $storage.set_cache_timeout(:tasks, 30.minutes)
58
+
59
+ end
60
+
61
+ register_method :purge do |options|
62
+ $storage.purge_cache!
63
+ end
64
+
65
+
66
+ end
67
+ end
data/lib/rtr.rb ADDED
@@ -0,0 +1,21 @@
1
+ # Equivalent to a header guard in C/C++
2
+ # Used to prevent the class/module from being loaded more than once
3
+ unless defined? RTR
4
+
5
+ module RTR
6
+
7
+ # Utility method used to rquire all files ending in .rb that lie in the
8
+ # directory below this file that has the same name as the filename passed
9
+ # in. Optionally, a specific _directory_ name can be passed in such that
10
+ # the _filename_ does not have to be equivalent to the directory.
11
+ #
12
+ def self.require_all_libs_relative_to(fname)
13
+ search_me = File.expand_path(File.join(File.dirname(fname), '**', '*.rb'))
14
+ Dir.glob(search_me).sort.each { |rb| require rb }
15
+ end
16
+
17
+ end
18
+
19
+ RTR.require_all_libs_relative_to __FILE__
20
+
21
+ end # unless defined?
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remember-the-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ platform: ruby
6
+ authors:
7
+ - David Dollar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-30 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ddollar-preferences
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: term-ansicolor
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.3
34
+ version:
35
+ description:
36
+ email: ddollar@gmail.com
37
+ executables:
38
+ - rtr
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ files:
44
+ - bin/rtr
45
+ - lib/core_ext/datetime.rb
46
+ - lib/core_ext/enumerable.rb
47
+ - lib/core_ext/fixnum.rb
48
+ - lib/core_ext/nil.rb
49
+ - lib/core_ext/string.rb
50
+ - lib/remember-the-ruby/api.rb
51
+ - lib/remember-the-ruby/contact.rb
52
+ - lib/remember-the-ruby/entity.rb
53
+ - lib/remember-the-ruby/entity_list.rb
54
+ - lib/remember-the-ruby/group.rb
55
+ - lib/remember-the-ruby/list.rb
56
+ - lib/remember-the-ruby/location.rb
57
+ - lib/remember-the-ruby/note.rb
58
+ - lib/remember-the-ruby/rpc.rb
59
+ - lib/remember-the-ruby/storage.rb
60
+ - lib/remember-the-ruby/tag.rb
61
+ - lib/remember-the-ruby/task.rb
62
+ - lib/remember-the-ruby/task_occurrence.rb
63
+ - lib/remember-the-ruby.rb
64
+ - lib/rtr/api.rb
65
+ - lib/rtr/command_helper.rb
66
+ - lib/rtr/commands.rb
67
+ - lib/rtr.rb
68
+ - README
69
+ has_rdoc: true
70
+ homepage: http://peervoice.com/software/remember-the-ruby
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options: []
75
+
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project: remember-the-ruby
93
+ rubygems_version: 1.3.5
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: A Ruby interface to Remember the Milk
97
+ test_files: []
98
+