thartm 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|