thartm 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +51 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/rrtm +40 -0
- data/lib/thartm.rb +200 -0
- data/lib/thartm_lib.rb +735 -0
- data/test/helper.rb +10 -0
- data/test/test_thartm.rb +7 -0
- metadata +82 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 tha
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= thartm
|
2
|
+
|
3
|
+
Remember the milk command line interface
|
4
|
+
|
5
|
+
using rtmapi library
|
6
|
+
patched to work with the new version of ruby-libxml
|
7
|
+
|
8
|
+
To make the cli work you have to obtain an api key
|
9
|
+
and an api secret for remember the milk.
|
10
|
+
|
11
|
+
Ask them at:
|
12
|
+
http://www.rememberthemilk.com/services/api/keys.rtm
|
13
|
+
|
14
|
+
puts those keys in a .rtm file in your $HOME
|
15
|
+
the file is supposed to be in YAML format
|
16
|
+
|
17
|
+
example:
|
18
|
+
key: yourkey
|
19
|
+
secret: yoursecret
|
20
|
+
tz: your timezone (UTC, GMT etc..)
|
21
|
+
|
22
|
+
Than you have to authorize the app and obtain the authorization token
|
23
|
+
start thartm command line interface
|
24
|
+
and you'll be prompted for an url
|
25
|
+
|
26
|
+
The auth method could be better. I now.. give me some time :)
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
== Note on Patches/Pull Requests
|
31
|
+
|
32
|
+
* Fork the project.
|
33
|
+
* Make your feature addition or bug fix.
|
34
|
+
* Add tests for it. This is important so I don't break it in a
|
35
|
+
future version unintentionally.
|
36
|
+
* Commit, do not mess with rakefile, version, or history.
|
37
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
38
|
+
* Send me a pull request. Bonus points for topic branches.
|
39
|
+
|
40
|
+
== Copyright
|
41
|
+
|
42
|
+
Copyright (c) 2010 thamayor. See LICENSE for details.
|
43
|
+
|
44
|
+
Feel free to send me suggestions :)
|
45
|
+
|
46
|
+
Thanks again to the rtmapi guys, and sorry for my bad fixies :P
|
47
|
+
|
48
|
+
Mail me at: thamayor [at] gmail [dot] com
|
49
|
+
|
50
|
+
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "thartm"
|
8
|
+
gem.summary = %Q{rtmapi based remember the milk cli.}
|
9
|
+
gem.description = %Q{rtmapi fixed version with a simple cli added}
|
10
|
+
gem.email = "thamayor@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/ghedamat/thartm"
|
12
|
+
gem.authors = ["tha"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", ]
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
25
|
+
test.libs << 'lib' << 'test'
|
26
|
+
test.pattern = 'test/**/test_*.rb'
|
27
|
+
test.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |test|
|
33
|
+
test.libs << 'test'
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
task :rcov do
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :test => :check_dependencies
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "thartm #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.15
|
data/bin/rrtm
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require File.dirname(__FILE__) + '/../lib/thartm.rb'
|
3
|
+
|
4
|
+
CONFIGFILE = File.join(File.expand_path(ENV['HOME']), '.rtm')
|
5
|
+
begin
|
6
|
+
@@config = YAML.load_file(CONFIGFILE)
|
7
|
+
rescue
|
8
|
+
raise "please create a .rtm file in your $HOME"
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
ENV['TZ'] = @@config['tz']
|
13
|
+
|
14
|
+
# validating token
|
15
|
+
unless @@config['token']
|
16
|
+
@rtm = ThaRememberTheMilk.new(@@config['key'],@@config['secret'])
|
17
|
+
puts "please authorize this program: open the following url and puts the frob value back here."
|
18
|
+
puts @rtm.auth_url
|
19
|
+
frob = gets
|
20
|
+
|
21
|
+
auth = @rtm.auth.getToken('frob' => frob.chomp)
|
22
|
+
token_file = File.open(CONFIGFILE,"+w")
|
23
|
+
token_file << "token: " + auth.token
|
24
|
+
|
25
|
+
puts "restart the program now"
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
cli = CommandLineInterface.new(@@config['key'],@@config['secret'],@@config['token'])
|
31
|
+
if ARGV[0]
|
32
|
+
begin
|
33
|
+
cli.send ARGV[0]
|
34
|
+
rescue
|
35
|
+
puts "command #{ARGV[0]} is not available"
|
36
|
+
cli.help
|
37
|
+
end
|
38
|
+
else
|
39
|
+
cli.tasks
|
40
|
+
end
|
data/lib/thartm.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'yaml'
|
5
|
+
require 'thartm_lib.rb'
|
6
|
+
|
7
|
+
class Rrtm
|
8
|
+
|
9
|
+
def initialize(key,secret,token)
|
10
|
+
#@rtm = ThaRememberTheMilk.new(@@config['key'],@@config['secret'],@@config['token'])
|
11
|
+
@rtm = ThaRememberTheMilk.new(key,secret,token)
|
12
|
+
@rtm.use_user_tz = true
|
13
|
+
|
14
|
+
# id of the all tasks list
|
15
|
+
@allTaskList = String.new
|
16
|
+
|
17
|
+
@lists = lists
|
18
|
+
@timeline = @rtm.timelines.create
|
19
|
+
end
|
20
|
+
|
21
|
+
def allTaskList
|
22
|
+
allTaskList = ''
|
23
|
+
@lists.each do |k,v|
|
24
|
+
if v[:name] == "All Tasks"
|
25
|
+
allTaskList = v[:id]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
return allTaskList
|
29
|
+
end
|
30
|
+
|
31
|
+
def lists
|
32
|
+
lists = @rtm.lists.getList
|
33
|
+
end
|
34
|
+
|
35
|
+
def tasks(args = {})
|
36
|
+
tasks = @rtm.tasks.getList args
|
37
|
+
end
|
38
|
+
|
39
|
+
def tasksAllTaskList
|
40
|
+
t = tasks :list_id => allTaskList
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def addTask(text, * args )
|
45
|
+
if args.length == 1
|
46
|
+
listname = args[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
listid = 0
|
50
|
+
if listname
|
51
|
+
@lists.each do |k,v|
|
52
|
+
if v[:name].match(listname)
|
53
|
+
listid = v[:id]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if listid != 0
|
59
|
+
@rtm.tasks.add :timeline => @timeline, :name => text, :parse => '1', :list_id => listid
|
60
|
+
else
|
61
|
+
@rtm.tasks.add :timeline => @timeline, :name => text, :parse => '1'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def findTask(id)
|
66
|
+
tt = tasks
|
67
|
+
tt.each do |key,val|
|
68
|
+
val.each do |k,v|
|
69
|
+
return v if v[:id] == id
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def completeTask(id)
|
76
|
+
v = findTask(id)
|
77
|
+
@rtm.tasks.complete :timeline => @timeline,:list_id =>v.list_id , :taskseries_id => v.taskseries_id, :task_id => v.task_id
|
78
|
+
end
|
79
|
+
|
80
|
+
def postponeTask(id)
|
81
|
+
v = findTask(id)
|
82
|
+
@rtm.tasks.postpone :timeline => @timeline,:list_id =>v.list_id , :taskseries_id => v.taskseries_id, :task_id => v.task_id
|
83
|
+
end
|
84
|
+
|
85
|
+
def renameTask(id,newname)
|
86
|
+
v = findTask(id)
|
87
|
+
@rtm.tasks.setName :timeline => @timeline,:list_id =>v.list_id , :taskseries_id => v.taskseries_id, :task_id => v.task_id, :name => newname
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
class CommandLineInterface
|
93
|
+
|
94
|
+
def initialize(key,secret,token)
|
95
|
+
@rtm = Rrtm.new(key,secret,token)
|
96
|
+
end
|
97
|
+
|
98
|
+
def tasks
|
99
|
+
t = Array.new
|
100
|
+
tasks = @rtm.tasksAllTaskList
|
101
|
+
|
102
|
+
tasks.each do |key,val|
|
103
|
+
val.each do |k,v|
|
104
|
+
t.push(v) unless v.complete? # do not add c ompleted tasks
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# sorting by date (inverse order) and than by task name
|
109
|
+
t.sort! do |a,b|
|
110
|
+
if (a.has_due? and b.has_due?)
|
111
|
+
a.due <=> b.due
|
112
|
+
elsif a.has_due?
|
113
|
+
-1
|
114
|
+
elsif b.has_due?
|
115
|
+
1
|
116
|
+
else
|
117
|
+
a[:name] <=> b[:name]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# compose string result
|
122
|
+
s = ''
|
123
|
+
t.each do |tt|
|
124
|
+
s += tt[:id] + ": " + tt[:name].to_s + " -- " + tt.due.to_s + "\n"
|
125
|
+
end
|
126
|
+
puts s
|
127
|
+
end
|
128
|
+
|
129
|
+
def add
|
130
|
+
@rtm.addTask(ARGV[1], ARGV[2] )
|
131
|
+
end
|
132
|
+
|
133
|
+
def lists
|
134
|
+
l = @rtm.lists
|
135
|
+
|
136
|
+
l.each do |k,v|
|
137
|
+
puts v[:name]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def complete
|
142
|
+
@rtm.completeTask(ARGV[1])
|
143
|
+
end
|
144
|
+
|
145
|
+
def postpone
|
146
|
+
@rtm.postpone(ARGV[1])
|
147
|
+
end
|
148
|
+
|
149
|
+
def first
|
150
|
+
t = Array.new
|
151
|
+
tasks = @rtm.tasksAllTaskList
|
152
|
+
|
153
|
+
tasks.each do |key,val|
|
154
|
+
val.each do |k,v|
|
155
|
+
t.push(v) unless v.complete? # do not add c ompleted tasks
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# sorting by date (inverse order) and than by task name
|
160
|
+
t.sort! do |a,b|
|
161
|
+
if (a.has_due? and b.has_due?)
|
162
|
+
a.due <=> b.due
|
163
|
+
elsif a.has_due?
|
164
|
+
-1
|
165
|
+
elsif b.has_due?
|
166
|
+
1
|
167
|
+
else
|
168
|
+
a[:name] <=> b[:name]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# compose string result
|
173
|
+
s = ''
|
174
|
+
tt = t[0]
|
175
|
+
s += tt[:name].to_s + " -- " + tt.due.to_s + "\n"
|
176
|
+
puts s
|
177
|
+
end
|
178
|
+
|
179
|
+
def help
|
180
|
+
s = ''
|
181
|
+
s += 'Rrtm: Tha remember the milk Command Line Usage
|
182
|
+
usage rrtm <command> <params>
|
183
|
+
|
184
|
+
help: print this help and exits
|
185
|
+
lists: show available tasks lists
|
186
|
+
tasks: show not completed tasks
|
187
|
+
add name [lists name]: adds a task to the lists
|
188
|
+
complete id: mark task with id "id" as completed
|
189
|
+
postpone id: postpone task by one day
|
190
|
+
first: show first uncompleted task
|
191
|
+
'
|
192
|
+
puts s
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
#TODO gestione priorita' tasks
|
199
|
+
#TODO sort by priority?
|
200
|
+
|
data/lib/thartm_lib.rb
ADDED
@@ -0,0 +1,735 @@
|
|
1
|
+
# This file is part of the RTM Ruby API Wrapper.
|
2
|
+
#
|
3
|
+
# The RTM Ruby API Wrapper is free software; you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as
|
5
|
+
# published by the Free Software Foundation; either version 2 of the
|
6
|
+
# License, or (at your option) any later version.
|
7
|
+
#
|
8
|
+
# The RTM Ruby API Wrapper is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with the RTM Ruby API Wrapper; if not, write to the Free Software
|
15
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
16
|
+
#
|
17
|
+
# (c) 2006, QuantumFoam.org, Inc.
|
18
|
+
|
19
|
+
|
20
|
+
#Modified by thamayor, mail: thamayor at gmail dot com
|
21
|
+
#my private rtm key is inside this file..
|
22
|
+
|
23
|
+
#this file is intended to be used with my rtm command line interface
|
24
|
+
|
25
|
+
#TODO add yaml api check?
|
26
|
+
|
27
|
+
require 'uri'
|
28
|
+
require 'md5'
|
29
|
+
require 'cgi'
|
30
|
+
require 'net/http'
|
31
|
+
require 'date'
|
32
|
+
require 'time'
|
33
|
+
require 'parsedate'
|
34
|
+
require 'rubygems'
|
35
|
+
require 'xml/libxml'
|
36
|
+
require 'tzinfo'
|
37
|
+
|
38
|
+
|
39
|
+
#TODO: allow specifying whether retval should be indexed by rtm_id or list name for lists
|
40
|
+
|
41
|
+
class ThaRememberTheMilk
|
42
|
+
RUBY_API_VERSION = '0.6'
|
43
|
+
# you can just put set these here so you don't have to pass them in with
|
44
|
+
# every constructor call
|
45
|
+
API_KEY = ''
|
46
|
+
API_SHARED_SECRET = ''
|
47
|
+
AUTH_TOKEN= ''
|
48
|
+
|
49
|
+
|
50
|
+
Element = 0
|
51
|
+
CloseTag = 1
|
52
|
+
Tag = 2
|
53
|
+
Attributes = 3
|
54
|
+
#SelfContainedElement = 4
|
55
|
+
TextNode = 4
|
56
|
+
|
57
|
+
TagName = 0
|
58
|
+
TagHash = 1
|
59
|
+
|
60
|
+
|
61
|
+
attr_accessor :debug, :auth_token, :return_raw_response, :api_key, :shared_secret, :max_connection_attempts, :use_user_tz
|
62
|
+
|
63
|
+
def user
|
64
|
+
@user_info_cache[auth_token] ||= auth.checkToken.user
|
65
|
+
end
|
66
|
+
|
67
|
+
def user_settings
|
68
|
+
@user_settings_cache[auth_token]
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_timeline
|
72
|
+
user[:timeline] ||= timelines.create
|
73
|
+
end
|
74
|
+
|
75
|
+
def time_to_user_tz( time )
|
76
|
+
return time unless(@use_user_tz && @auth_token && defined?(TZInfo::Timezone))
|
77
|
+
begin
|
78
|
+
unless defined?(@user_settings_cache[auth_token]) && defined?(@user_settings_cache[auth_token][:tz])
|
79
|
+
@user_settings_cache[auth_token] = settings.getList
|
80
|
+
@user_settings_cache[auth_token][:tz] = TZInfo::Timezone.get(@user_settings_cache[auth_token].timezone)
|
81
|
+
end
|
82
|
+
debug "returning time in local zone(%s/%s)", @user_settings_cache[auth_token].timezone, @user_settings_cache[auth_token][:tz]
|
83
|
+
@user_settings_cache[auth_token][:tz].utc_to_local(time)
|
84
|
+
rescue Exception => err
|
85
|
+
debug "unable to read local timezone for auth_token<%s>, ignoring timezone. err<%s>", auth_token, err
|
86
|
+
time
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def logout_user(auth_token)
|
91
|
+
@auth_token = nil if @auth_token == auth_token
|
92
|
+
@user_settings_cache.delete(auth_token)
|
93
|
+
@user_info_cache.delete(auth_token)
|
94
|
+
end
|
95
|
+
|
96
|
+
# TODO: test efficacy of using https://www.rememberthemilk.com/services/rest/
|
97
|
+
def initialize( api_key = API_KEY, shared_secret = API_SHARED_SECRET, auth_token = AUTH_TOKEN, endpoint = 'http://www.rememberthemilk.com/services/rest/')
|
98
|
+
@max_connection_attempts = 3
|
99
|
+
@debug = false
|
100
|
+
@api_key = api_key
|
101
|
+
@shared_secret = shared_secret
|
102
|
+
@uri = URI.parse(endpoint)
|
103
|
+
#@auth_token = nil
|
104
|
+
@auth_token = auth_token
|
105
|
+
@return_raw_response = false
|
106
|
+
@use_user_tz = true
|
107
|
+
@user_settings_cache = {}
|
108
|
+
@user_info_cache = {}
|
109
|
+
#@xml_parser = XML::Parser.new
|
110
|
+
@xml_parser = XML::Parser.new(XML::Parser::Context.new)
|
111
|
+
end
|
112
|
+
|
113
|
+
def version() RUBY_API_VERSION end
|
114
|
+
|
115
|
+
def debug(*args)
|
116
|
+
return unless @debug
|
117
|
+
if defined?(RAILS_DEFAULT_LOGGER)
|
118
|
+
RAILS_DEFAULT_LOGGER.warn( sprintf(*args) )
|
119
|
+
else
|
120
|
+
$stderr.puts(sprintf(*args))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def auth_url( perms = 'delete' )
|
125
|
+
auth_url = 'http://www.rememberthemilk.com/services/auth/'
|
126
|
+
args = { 'api_key' => @api_key, 'perms' => perms }
|
127
|
+
args['api_sig'] = sign_request(args)
|
128
|
+
return auth_url + '?' + args.keys.collect {|k| "#{k}=#{args[k]}"}.join('&')
|
129
|
+
end
|
130
|
+
|
131
|
+
# this is a little fragile. it assumes we are being invoked with RTM api calls
|
132
|
+
# (which are two levels deep)
|
133
|
+
# e.g.,
|
134
|
+
# rtm = RememberTheMilk.new
|
135
|
+
# data = rtm.reflection.getMethodInfo('method_name' => 'rtm.test.login')
|
136
|
+
# the above line gets turned into two calls, the first to this, which returns
|
137
|
+
# an RememberTheMilkAPINamespace object, which then gets *its* method_missing
|
138
|
+
# invoked with 'getMethodInfo' and the above args
|
139
|
+
# i.e.,
|
140
|
+
# rtm.foo.bar
|
141
|
+
# rtm.foo() => a
|
142
|
+
# a.bar
|
143
|
+
|
144
|
+
def method_missing( symbol, *args )
|
145
|
+
rtm_namespace = symbol.id2name
|
146
|
+
debug("method_missing called with namespace <%s>", rtm_namespace)
|
147
|
+
RememberTheMilkAPINamespace.new( rtm_namespace, self )
|
148
|
+
end
|
149
|
+
|
150
|
+
def xml_node_to_hash( node, recursion_level = 0 )
|
151
|
+
result = xml_attributes_to_hash( node.attributes )
|
152
|
+
if node.element? == false
|
153
|
+
result[node.name.to_sym] = node.content
|
154
|
+
else
|
155
|
+
node.each do |child|
|
156
|
+
name = child.name.to_sym
|
157
|
+
value = xml_node_to_hash( child, recursion_level+1 )
|
158
|
+
|
159
|
+
# if we have the same node name appear multiple times, we need to build up an array
|
160
|
+
# of the converted nodes
|
161
|
+
if !result.has_key?(name)
|
162
|
+
result[name] = value
|
163
|
+
elsif result[name].class != Array
|
164
|
+
result[name] = [result[name], value]
|
165
|
+
else
|
166
|
+
result[name] << value
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# top level nodes should be a hash no matter what
|
172
|
+
(recursion_level == 0 || result.values.size > 1) ? result : result.values[0]
|
173
|
+
end
|
174
|
+
|
175
|
+
def xml_attributes_to_hash( attributes, class_name = RememberTheMilkHash )
|
176
|
+
hash = class_name.send(:new)
|
177
|
+
attributes.each {|a| hash[a.name.to_sym] = a.value} if attributes.respond_to?(:each)
|
178
|
+
return hash
|
179
|
+
end
|
180
|
+
|
181
|
+
def index_data_into_hash( data, key )
|
182
|
+
new_hash = RememberTheMilkHash.new
|
183
|
+
|
184
|
+
if data.class == Array
|
185
|
+
data.each {|datum| new_hash[datum[key]] = datum }
|
186
|
+
else
|
187
|
+
new_hash[data[key]] = data
|
188
|
+
end
|
189
|
+
|
190
|
+
new_hash
|
191
|
+
end
|
192
|
+
|
193
|
+
def parse_response(response,method,args)
|
194
|
+
# groups -- an array of group obj
|
195
|
+
# group -- some attributes and a possible contacts array
|
196
|
+
# contacts -- an array of contact obj
|
197
|
+
# contact -- just attributes
|
198
|
+
# lists -- array of list obj
|
199
|
+
# list -- attributes and possible filter obj, and a set of taskseries objs?
|
200
|
+
# task sereies obj are always wrapped in a list. why?
|
201
|
+
# taskseries -- set of attributes, array of tags, an rrule, participants array of contacts, notes,
|
202
|
+
# and task. created and modified are time obj,
|
203
|
+
# task -- attributes, due/added are time obj
|
204
|
+
# note -- attributes and a body of text, with created and modified time obj
|
205
|
+
# time -- convert to a time obj
|
206
|
+
# timeline -- just has a body of text
|
207
|
+
return true unless response.keys.size > 1 # empty response (stat only)
|
208
|
+
|
209
|
+
rtm_transaction = nil
|
210
|
+
if response.has_key?(:transaction)
|
211
|
+
# debug("got back <%s> elements in my transaction", response[:transaction].keys.size)
|
212
|
+
# we just did a write operation, got back a transaction AND some data.
|
213
|
+
# Now, we will do some fanciness.
|
214
|
+
rtm_transaction = response[:transaction]
|
215
|
+
end
|
216
|
+
|
217
|
+
response_types = response.keys - [:stat, :transaction]
|
218
|
+
|
219
|
+
if response.has_key?(:api_key) # echo call, we assume
|
220
|
+
response_type = :echo
|
221
|
+
data = response
|
222
|
+
elsif response_types.size > 1
|
223
|
+
error = RememberTheMilkAPIError.new({:code => "666", :msg=>"found more than one response type[#{response_types.join(',')}]"},method,args)
|
224
|
+
debug( "%s", error )
|
225
|
+
raise error
|
226
|
+
else
|
227
|
+
response_type = response_types[0] || :transaction
|
228
|
+
|
229
|
+
data = response[response_type]
|
230
|
+
end
|
231
|
+
|
232
|
+
case response_type
|
233
|
+
when :auth
|
234
|
+
when :frob
|
235
|
+
when :echo
|
236
|
+
when :transaction
|
237
|
+
when :timeline
|
238
|
+
when :methods
|
239
|
+
when :settings
|
240
|
+
when :contact
|
241
|
+
when :group
|
242
|
+
# no op
|
243
|
+
|
244
|
+
when :tasks
|
245
|
+
data = data[:list]
|
246
|
+
new_hash = RememberTheMilkHash.new
|
247
|
+
if data.class == Array # a bunch of lists
|
248
|
+
data.each do |list|
|
249
|
+
if list.class == String # empty list, just an id, so we create a stub
|
250
|
+
new_list = RememberTheMilkHash.new
|
251
|
+
new_list[:id] = list
|
252
|
+
list = new_list
|
253
|
+
end
|
254
|
+
new_hash[list[:id]] = process_task_list( list[:id], list.arrayify_value(:taskseries) )
|
255
|
+
end
|
256
|
+
data = new_hash
|
257
|
+
elsif data.class == RememberTheMilkHash # only one list
|
258
|
+
#puts data.inspect
|
259
|
+
#puts data[:list][3][:taskseries].inspect
|
260
|
+
data = process_task_list( data[:id], data.arrayify_value(:taskseries) )
|
261
|
+
elsif data.class == NilClass || (data.class == String && data == args['list_id']) # empty list
|
262
|
+
data = new_hash
|
263
|
+
else # who knows...
|
264
|
+
debug( "got a class of (%s [%s]) when processing tasks. passing it on through", data.class, data )
|
265
|
+
end
|
266
|
+
when :groups
|
267
|
+
# contacts expected to be array, so look at each group and fix it's contact
|
268
|
+
data = [data] unless data.class == Array # won't be array if there's only one group. normalize here
|
269
|
+
data.each do |datum|
|
270
|
+
datum.arrayify_value( :contacts )
|
271
|
+
end
|
272
|
+
data = index_data_into_hash( data, :id )
|
273
|
+
when :time
|
274
|
+
data = time_to_user_tz( Time.parse(data[:text]) )
|
275
|
+
when :timezones
|
276
|
+
data = index_data_into_hash( data, :name )
|
277
|
+
when :lists
|
278
|
+
data = index_data_into_hash( data, :id )
|
279
|
+
when :contacts
|
280
|
+
data = [data].compact unless data.class == Array
|
281
|
+
when :list
|
282
|
+
# rtm.tasks.add returns one of these, which looks like this:
|
283
|
+
# <rsp stat='ok'><transaction id='978920558' undoable='0'/><list id='761280'><taskseries name='Try out Remember The Milk' modified='2006-12-19T22:07:50Z' url='' id='1939553' created='2006-12-19T22:07:50Z' source='api'><tags/><participants/><notes/><task added='2006-12-19T22:07:50Z' completed='' postponed='0' priority='N' id='2688677' has_due_time='0' deleted='' estimate='' due=''/></taskseries></list></rsp>
|
284
|
+
# rtm.lists.add also returns this, but it looks like this:
|
285
|
+
# <rsp stat='ok'><transaction id='978727001' undoable='0'/><list name='PersonalClone2' smart='0' id='761266' archived='0' deleted='0' position='0' locked='0'/></rsp>
|
286
|
+
# so we can look for a name attribute
|
287
|
+
if !data.has_key?(:name)
|
288
|
+
data = process_task_list( data[:id], data.arrayify_value(:taskseries) )
|
289
|
+
data = data.values[0] if data.values.size == 1
|
290
|
+
end
|
291
|
+
else
|
292
|
+
throw "Unsupported reply type<#{response_type}>#{response.inspect}"
|
293
|
+
end
|
294
|
+
|
295
|
+
if rtm_transaction
|
296
|
+
if !data.respond_to?(:keys)
|
297
|
+
new_hash = RememberTheMilkHash.new
|
298
|
+
new_hash[response_type] = data
|
299
|
+
data = new_hash
|
300
|
+
end
|
301
|
+
|
302
|
+
if data.keys.size == 0
|
303
|
+
data = rtm_transaction
|
304
|
+
else
|
305
|
+
data[:rtm_transaction] = rtm_transaction if rtm_transaction
|
306
|
+
end
|
307
|
+
end
|
308
|
+
return data
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
def process_task_list( list_id, list )
|
313
|
+
return {} unless list
|
314
|
+
tasks = RememberTheMilkHash.new
|
315
|
+
list.each do |taskseries_as_hash|
|
316
|
+
taskseries = RememberTheMilkTask.new(self).merge(taskseries_as_hash)
|
317
|
+
|
318
|
+
taskseries[:parent_list] = list_id # parent pointers are nice
|
319
|
+
taskseries[:tasks] = taskseries.arrayify_value(:task)
|
320
|
+
taskseries.arrayify_value(:tags)
|
321
|
+
taskseries.arrayify_value(:participants)
|
322
|
+
|
323
|
+
# TODO is there a ruby lib that speaks rrule?
|
324
|
+
taskseries[:recurrence] = nil
|
325
|
+
if taskseries[:rrule]
|
326
|
+
taskseries[:recurrence] = taskseries[:rrule]
|
327
|
+
taskseries[:recurrence][:rule] = taskseries[:rrule][:text]
|
328
|
+
end
|
329
|
+
|
330
|
+
taskseries[:completed] = nil
|
331
|
+
taskseries.tasks.each do |item|
|
332
|
+
if item.has_key?(:due) && item.due != ''
|
333
|
+
item.due = time_to_user_tz( Time.parse(item.due) )
|
334
|
+
end
|
335
|
+
|
336
|
+
if item.has_key?(:completed) && item.completed != '' && taskseries[:completed] == nil
|
337
|
+
taskseries[:completed] = true
|
338
|
+
else # once we set it to false, it can't get set to true
|
339
|
+
taskseries[:completed] = false
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# TODO: support past tasks?
|
344
|
+
tasks[taskseries[:id]] = taskseries
|
345
|
+
end
|
346
|
+
|
347
|
+
return tasks
|
348
|
+
end
|
349
|
+
|
350
|
+
def call_api_method( method, args={} )
|
351
|
+
|
352
|
+
args['method'] = "rtm.#{method}"
|
353
|
+
args['api_key'] = @api_key
|
354
|
+
args['auth_token'] ||= @auth_token if @auth_token
|
355
|
+
|
356
|
+
# make sure everything in our arguments is a string
|
357
|
+
args.each do |key,value|
|
358
|
+
key_s = key.to_s
|
359
|
+
args.delete(key) if key.class != String
|
360
|
+
args[key_s] = value.to_s
|
361
|
+
end
|
362
|
+
|
363
|
+
args['api_sig'] = sign_request(args)
|
364
|
+
|
365
|
+
debug( 'rtm.%s(%s)', method, args.inspect )
|
366
|
+
|
367
|
+
attempts_left = @max_connection_attempts
|
368
|
+
|
369
|
+
begin
|
370
|
+
if args.has_key?('test_data')
|
371
|
+
@xml_parser.string = args['test_data']
|
372
|
+
else
|
373
|
+
attempts_left -= 1
|
374
|
+
response = Net::HTTP.get_response(@uri.host, "#{@uri.path}?#{args.keys.collect {|k| "#{CGI::escape(k).gsub(/ /,'+')}=#{CGI::escape(args[k]).gsub(/ /,'+')}"}.join('&')}")
|
375
|
+
debug('RESPONSE code: %s\n%sEND RESPONSE\n', response.code, response.body)
|
376
|
+
#puts response.body
|
377
|
+
#@xml_parser.string = response.body
|
378
|
+
@xml_parser= XML::Parser.string(response.body)
|
379
|
+
end
|
380
|
+
|
381
|
+
raw_data = @xml_parser.parse
|
382
|
+
data = xml_node_to_hash( raw_data.root )
|
383
|
+
#puts data.inspect
|
384
|
+
debug( "processed into data<#{data.inspect}>")
|
385
|
+
|
386
|
+
if data[:stat] != 'ok'
|
387
|
+
error = RememberTheMilkAPIError.new(data[:err],method,args)
|
388
|
+
debug( "%s", error )
|
389
|
+
raise error
|
390
|
+
end
|
391
|
+
#return return_raw_response ? @xml_parser.string : parse_response(data,method,args)
|
392
|
+
return parse_response(data,method,args)
|
393
|
+
#rescue XML::Parser::ParseError => err
|
394
|
+
# debug("Unable to parse document.\nGot response:%s\nGot Error:\n", response.body, err.to_s)
|
395
|
+
# raise err
|
396
|
+
rescue Timeout::Error => timeout
|
397
|
+
$stderr.puts "Timed out to<#{endpoint}>, trying #{attempts_left} more times"
|
398
|
+
if attempts_left > 0
|
399
|
+
retry
|
400
|
+
else
|
401
|
+
raise timeout
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
def sign_request( args )
|
407
|
+
return MD5.md5(@shared_secret + args.sort.flatten.join).to_s
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
## a pretty crappy exception class, but it should be sufficient for bubbling
|
413
|
+
## up errors returned by the RTM API (website)
|
414
|
+
class RememberTheMilkAPIError < RuntimeError
|
415
|
+
attr_reader :response, :error_code, :error_message
|
416
|
+
|
417
|
+
def initialize(error, method, args_to_method)
|
418
|
+
@method_name = method
|
419
|
+
@args_to_method = args_to_method
|
420
|
+
@error_code = error[:code].to_i
|
421
|
+
@error_message = error[:msg]
|
422
|
+
end
|
423
|
+
|
424
|
+
def to_s
|
425
|
+
"Calling rtm.#{@method_name}(#{@args_to_method.inspect}) produced => <#{@error_code}>: #{@error_message}"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
|
430
|
+
## this is just a helper class so that you can do things like
|
431
|
+
## rtm.test.echo. the method_missing in RememberTheMilkAPI returns one of
|
432
|
+
## these.
|
433
|
+
## this class is the "test" portion of the programming. its method_missing then
|
434
|
+
## get invoked with "echo" as the symbol. it has stored a reference to the original
|
435
|
+
## rtm object, so it can then invoke call_api_method
|
436
|
+
class RememberTheMilkAPINamespace
|
437
|
+
def initialize(namespace, rtm)
|
438
|
+
@namespace = namespace
|
439
|
+
@rtm = rtm
|
440
|
+
end
|
441
|
+
|
442
|
+
def method_missing( symbol, *args )
|
443
|
+
method_name = symbol.id2name
|
444
|
+
@rtm.call_api_method( "#{@namespace}.#{method_name}", *args)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
## a standard hash with some helper methods
|
449
|
+
class RememberTheMilkHash < Hash
|
450
|
+
attr_accessor :rtm
|
451
|
+
|
452
|
+
@@strict_keys = true
|
453
|
+
def self.strict_keys=( value )
|
454
|
+
@@strict_keys = value
|
455
|
+
end
|
456
|
+
|
457
|
+
def initialize(rtm_object = nil)
|
458
|
+
super
|
459
|
+
@rtm = rtm_object
|
460
|
+
end
|
461
|
+
|
462
|
+
def id
|
463
|
+
rtm_id || object_id
|
464
|
+
end
|
465
|
+
|
466
|
+
def rtm_id
|
467
|
+
self[:id]
|
468
|
+
end
|
469
|
+
|
470
|
+
# guarantees that a given key corresponds to an array, even if it's an empty array
|
471
|
+
def arrayify_value( key )
|
472
|
+
if !self.has_key?(key)
|
473
|
+
self[key] = []
|
474
|
+
elsif self[key].class != Array
|
475
|
+
self[key] = [ self[key] ].compact
|
476
|
+
else
|
477
|
+
self[key]
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
def method_missing( key, *args )
|
483
|
+
name = key.to_s
|
484
|
+
|
485
|
+
setter = false
|
486
|
+
if name[-1,1] == '='
|
487
|
+
name = name.chop
|
488
|
+
setter = true
|
489
|
+
end
|
490
|
+
|
491
|
+
if name == ""
|
492
|
+
name = "rtm_nil".to_sym
|
493
|
+
else
|
494
|
+
name = name.to_sym
|
495
|
+
end
|
496
|
+
|
497
|
+
|
498
|
+
# TODO: should we allow the blind setting of values? (i.e., only do this test
|
499
|
+
# if setter==false )
|
500
|
+
raise "unknown hash key<#{name}> requested for #{self.inspect}" if @@strict_keys && !self.has_key?(name)
|
501
|
+
|
502
|
+
if setter
|
503
|
+
self[name] = *args
|
504
|
+
else
|
505
|
+
self[name]
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
|
511
|
+
## TODO -- better rrule support. start here with this code, commented out for now
|
512
|
+
## DateSet is to manage rrules
|
513
|
+
## this comes from the iCal ruby module as mentioned here:
|
514
|
+
## http://www.macdevcenter.com/pub/a/mac/2003/09/03/rubycocoa.html
|
515
|
+
|
516
|
+
# The API is aware it's creating tasks. You may want to add semantics to a "task"
|
517
|
+
# elsewhere in your program. This gives you that flexibility
|
518
|
+
# plus, we've added some helper methods
|
519
|
+
|
520
|
+
class RememberTheMilkTask < RememberTheMilkHash
|
521
|
+
attr_accessor :rtm
|
522
|
+
|
523
|
+
def timeline
|
524
|
+
@timeline ||= rtm.get_timeline # this caches timelines per user
|
525
|
+
end
|
526
|
+
|
527
|
+
def initialize( rtm_api_handle=nil )
|
528
|
+
super
|
529
|
+
@rtm = rtm_api_handle # keep track of this so we can do setters (see factory below)
|
530
|
+
end
|
531
|
+
|
532
|
+
def task() tasks[-1] end
|
533
|
+
def taskseries_id() self.has_key?(:taskseries_id) ? self[:taskseries_id] : rtm_id end
|
534
|
+
def task_id() self.has_key?(:task_id) ? self[:task_id] : task.rtm_id end
|
535
|
+
def list_id() parent_list end
|
536
|
+
def due() task.due end
|
537
|
+
|
538
|
+
def has_due?() due.class == Time end
|
539
|
+
def has_due_time?() task.has_due_time == '1' end
|
540
|
+
def complete?() task[:completed] != '' end
|
541
|
+
def to_s
|
542
|
+
a_parent_list = self[:parent_list] || '<Parent Not Set>'
|
543
|
+
a_taskseries_id = self[:taskseries_id] || self[:id] || '<No Taskseries Id>'
|
544
|
+
a_task_id = self[:task_id] || (self[:task] && self[:task].rtm_td) || '<No Task Id>'
|
545
|
+
a_name = self[:name] || '<Name Not Set>'
|
546
|
+
"#{a_parent_list}/#{a_taskseries_id}/#{a_task_id}: #{a_name}"
|
547
|
+
end
|
548
|
+
|
549
|
+
def due_display
|
550
|
+
if has_due?
|
551
|
+
if has_due_time?
|
552
|
+
due.strftime("%a %d %b %y at %I:%M%p")
|
553
|
+
else
|
554
|
+
due.strftime("%a %d %b %y")
|
555
|
+
end
|
556
|
+
else
|
557
|
+
'[no due date]'
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
@@BeginningOfEpoch = Time.parse("Jan 1 1904") # kludgey.. sure. life's a kludge. deal with it.
|
562
|
+
include Comparable
|
563
|
+
def <=>(other)
|
564
|
+
due = (has_key?(:tasks) && tasks.class == Array) ? task[:due] : nil
|
565
|
+
due = @@BeginningOfEpoch unless due.class == Time
|
566
|
+
other_due = (other.has_key?(:tasks) && other.tasks.class == Array) ? other.task[:due] : nil
|
567
|
+
other_due = @@BeginningOfEpoch unless other_due.class == Time
|
568
|
+
|
569
|
+
# sort based on priority, then due date, then name
|
570
|
+
# which is the rememberthemilk default
|
571
|
+
# if 0 was false in ruby, we could have done
|
572
|
+
# prio <=> other_due || due <=> other_due || self['name'].to_s <=> other['name'].to_s
|
573
|
+
# but it's not, so oh well....
|
574
|
+
prio = priority.to_i
|
575
|
+
prio += 666 if prio == 0 # prio of 0 is no priority which means it should show up below 1-3
|
576
|
+
other_prio = other.priority.to_i
|
577
|
+
other_prio += 666 if other_prio == 0
|
578
|
+
|
579
|
+
if prio != other_prio
|
580
|
+
return prio <=> other_prio
|
581
|
+
elsif due != other_due
|
582
|
+
return due <=> other_due
|
583
|
+
else
|
584
|
+
# TODO: should this be case insensitive?
|
585
|
+
return self[:name].to_s <=> other[:name].to_s
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
# Factory Methods...
|
590
|
+
# these are for methods that take arguments and apply to the taskseries
|
591
|
+
# if you have RememberTheMilkTask called task, you might do:
|
592
|
+
# task.addTags( 'tag1, tag2, tag3' )
|
593
|
+
# task.setRecurrence # turns off all rrules
|
594
|
+
# task.complete # marks last task as complete
|
595
|
+
# task.setDueDate # unsets due date for last task
|
596
|
+
# task.setDueDate( nil, :task_id => task.tasks[0].id ) # unsets due date for first task in task array
|
597
|
+
# task.setDueDate( "tomorrow at 1pm", :parse => 1 ) # sets due date for last task to tomorrow at 1pm
|
598
|
+
[['addTags','tags'], ['setTags', 'tags'], ['removeTags', 'tags'], ['setName', 'name'],
|
599
|
+
['setRecurrence', 'repeat'], ['complete', ''], ['uncomplete', ''], ['setDueDate', 'due'],
|
600
|
+
['setPriority', 'priority'], ['movePriority', 'direction'], ['setEstimate', 'estimate'],
|
601
|
+
['setURL', 'url'], ['postpone', ''], ['delete', ''] ].each do |method_name, arg|
|
602
|
+
class_eval <<-RTM_METHOD
|
603
|
+
def #{method_name} ( value=nil, args={} )
|
604
|
+
if @rtm == nil
|
605
|
+
raise RememberTheMilkAPIError.new( :code => '667', :msg => "#{method_name} called without a handle to an rtm object [#{self.to_s}]" )
|
606
|
+
end
|
607
|
+
method_args = {}
|
608
|
+
method_args["#{arg}"] = value if "#{arg}" != '' && value
|
609
|
+
method_args[:timeline] = timeline
|
610
|
+
method_args[:list_id] = list_id
|
611
|
+
method_args[:taskseries_id] = taskseries_id
|
612
|
+
method_args[:task_id] = task_id
|
613
|
+
method_args.merge!( args )
|
614
|
+
@rtm.call_api_method( "tasks.#{method_name}", method_args ) # returns the modified task
|
615
|
+
end
|
616
|
+
RTM_METHOD
|
617
|
+
end
|
618
|
+
|
619
|
+
# We have to do this because moveTo takes a "from_list_id", not "list_id", so the above factory
|
620
|
+
# wouldn't work. sigh.
|
621
|
+
def moveTo( to_list_id, args = {} )
|
622
|
+
if @rtm == nil
|
623
|
+
raise RememberTheMilkAPIError.new( :code => '667', :msg => "moveTO called without a handle to an rtm object [#{self.to_s}]" )
|
624
|
+
end
|
625
|
+
method_args = {}
|
626
|
+
method_args[:timeline] = timeline
|
627
|
+
method_args[:from_list_id] = list_id
|
628
|
+
method_args[:to_list_id] = to_list_id
|
629
|
+
method_args[:taskseries_id] = taskseries_id
|
630
|
+
method_args[:task_id] = task_id
|
631
|
+
method_args.merge( args )
|
632
|
+
@rtm.call_api_method( :moveTo, method_args )
|
633
|
+
end
|
634
|
+
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
#
|
639
|
+
# class DateSet
|
640
|
+
#
|
641
|
+
# def initialize(startDate, rule)
|
642
|
+
# @startDate = startDate
|
643
|
+
# @frequency = nil
|
644
|
+
# @count = nil
|
645
|
+
# @untilDate = nil
|
646
|
+
# @byMonth = nil
|
647
|
+
# @byDay = nil
|
648
|
+
# @starts = nil
|
649
|
+
# if not rule.nil? then
|
650
|
+
# @starts = rule.every == 1 ? 'every' : 'after'
|
651
|
+
# parseRecurrenceRule(rule.rule)
|
652
|
+
# end
|
653
|
+
# end
|
654
|
+
#
|
655
|
+
# def parseRecurrenceRule(rule)
|
656
|
+
#
|
657
|
+
# if rule =~ /FREQ=(.*?);/ then
|
658
|
+
# @frequency = $1
|
659
|
+
# end
|
660
|
+
#
|
661
|
+
# if rule =~ /COUNT=(\d*)/ then
|
662
|
+
# @count = $1.to_i
|
663
|
+
# end
|
664
|
+
#
|
665
|
+
# if rule =~ /UNTIL=(.*?)[;\r]/ then
|
666
|
+
# @untilDate = DateParser.parse($1)
|
667
|
+
# end
|
668
|
+
#
|
669
|
+
# if rule =~ /INTERVAL=(\d*)/ then
|
670
|
+
# @interval = $1.to_i
|
671
|
+
# end
|
672
|
+
#
|
673
|
+
# if rule =~ /BYMONTH=(.*?);/ then
|
674
|
+
# @byMonth = $1
|
675
|
+
# end
|
676
|
+
#
|
677
|
+
# if rule =~ /BYDAY=(.*?);/ then
|
678
|
+
# @byDay = $1
|
679
|
+
# #puts "byDay = #{@byDay}"
|
680
|
+
# end
|
681
|
+
# end
|
682
|
+
#
|
683
|
+
# def to_s
|
684
|
+
# # after/every FREQ
|
685
|
+
# puts "UNIMPLETEMENT"
|
686
|
+
# # puts "#<DateSet: starts: #{@startDate.strftime("%m/%d/%Y")}, occurs: #{@frequency}, count: #{@count}, until: #{@untilDate}, byMonth: #{@byMonth}, byDay: #{@byDay}>"
|
687
|
+
# end
|
688
|
+
#
|
689
|
+
# def includes?(date)
|
690
|
+
# return true if date == @startDate
|
691
|
+
# return false if @untilDate and date > @untilDate
|
692
|
+
#
|
693
|
+
# case @frequency
|
694
|
+
# when 'DAILY'
|
695
|
+
# #if @untilDate then
|
696
|
+
# # return (@startDate..@untilDate).include?(date)
|
697
|
+
# #end
|
698
|
+
# increment = @interval ? @interval : 1
|
699
|
+
# d = @startDate
|
700
|
+
# counter = 0
|
701
|
+
# until d > date
|
702
|
+
#
|
703
|
+
# if @count then
|
704
|
+
# counter += 1
|
705
|
+
# if counter >= @count
|
706
|
+
# return false
|
707
|
+
# end
|
708
|
+
# end
|
709
|
+
#
|
710
|
+
# d += (increment * SECONDS_PER_DAY)
|
711
|
+
# if d.day == date.day and
|
712
|
+
# d.year == date.year and
|
713
|
+
# d.month == date.month then
|
714
|
+
# puts "true for start: #{@startDate}, until: #{@untilDate}"
|
715
|
+
# return true
|
716
|
+
# end
|
717
|
+
#
|
718
|
+
# end
|
719
|
+
#
|
720
|
+
# when 'WEEKLY'
|
721
|
+
# return true if @startDate.wday == date.wday
|
722
|
+
#
|
723
|
+
# when 'MONTHLY'
|
724
|
+
#
|
725
|
+
# when 'YEARLY'
|
726
|
+
#
|
727
|
+
# end
|
728
|
+
#
|
729
|
+
# false
|
730
|
+
# end
|
731
|
+
#
|
732
|
+
# attr_reader :frequency
|
733
|
+
# attr_accessor :startDate
|
734
|
+
# end
|
735
|
+
#
|
data/test/helper.rb
ADDED
data/test/test_thartm.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thartm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.15
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- tha
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-06-06 00:00:00 +02:00
|
13
|
+
default_executable: rrtm
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thoughtbot-shoulda
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: rtmapi fixed version with a simple cli added
|
36
|
+
email: thamayor@gmail.com
|
37
|
+
executables:
|
38
|
+
- rrtm
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- LICENSE
|
46
|
+
- README.rdoc
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- bin/rrtm
|
50
|
+
- lib/thartm.rb
|
51
|
+
- lib/thartm_lib.rb
|
52
|
+
- test/helper.rb
|
53
|
+
- test/test_thartm.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/ghedamat/thartm
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.1
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: rtmapi based remember the milk cli.
|
80
|
+
test_files:
|
81
|
+
- test/helper.rb
|
82
|
+
- test/test_thartm.rb
|