toodledo 1.0.0
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/CHANGELOG +1 -0
- data/History.txt +4 -0
- data/Manifest.txt +30 -0
- data/README.txt +272 -0
- data/Rakefile +20 -0
- data/bin/toodledo +11 -0
- data/lib/toodledo.rb +64 -0
- data/lib/toodledo/command_line/add_command.rb +29 -0
- data/lib/toodledo/command_line/base_command.rb +25 -0
- data/lib/toodledo/command_line/client.rb +507 -0
- data/lib/toodledo/command_line/complete_command.rb +23 -0
- data/lib/toodledo/command_line/delete_command.rb +24 -0
- data/lib/toodledo/command_line/edit_command.rb +26 -0
- data/lib/toodledo/command_line/hotlist_command.rb +23 -0
- data/lib/toodledo/command_line/list_command.rb +24 -0
- data/lib/toodledo/command_line/main_command.rb +150 -0
- data/lib/toodledo/command_line/parser_helper.rb +57 -0
- data/lib/toodledo/command_line/setup_command.rb +15 -0
- data/lib/toodledo/context.rb +41 -0
- data/lib/toodledo/folder.rb +50 -0
- data/lib/toodledo/goal.rb +74 -0
- data/lib/toodledo/item_not_found_error.rb +8 -0
- data/lib/toodledo/priority.rb +34 -0
- data/lib/toodledo/repeat.rb +44 -0
- data/lib/toodledo/server_error.rb +10 -0
- data/lib/toodledo/session.rb +1029 -0
- data/lib/toodledo/task.rb +320 -0
- data/test/parser_helper_test.rb +63 -0
- data/test/session_test.rb +197 -0
- data/test/toodledo_functional_test.rb +166 -0
- metadata +111 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Toodledo
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# A read only representation of a Goal.
|
|
5
|
+
#
|
|
6
|
+
class Goal
|
|
7
|
+
|
|
8
|
+
LIFE_LEVEL = 0
|
|
9
|
+
|
|
10
|
+
MEDIUM_LEVEL = 1
|
|
11
|
+
|
|
12
|
+
SHORT_LEVEL = 2
|
|
13
|
+
|
|
14
|
+
LEVEL_ARRAY = [ LIFE_LEVEL, MEDIUM_LEVEL, SHORT_LEVEL ]
|
|
15
|
+
|
|
16
|
+
def self.valid?(input)
|
|
17
|
+
for level in LEVEL_ARRAY
|
|
18
|
+
if (level == input)
|
|
19
|
+
return true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(id, level, contributes_id, name)
|
|
26
|
+
@id = id
|
|
27
|
+
@level = level
|
|
28
|
+
@contributes_id = contributes_id
|
|
29
|
+
@name = name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
NO_GOAL = Goal.new(0, 0, 0, "No goal")
|
|
33
|
+
|
|
34
|
+
attr_reader :level, :contributes_id, :name
|
|
35
|
+
|
|
36
|
+
def contributes
|
|
37
|
+
if (@contributes == nil)
|
|
38
|
+
return NO_GOAL
|
|
39
|
+
end
|
|
40
|
+
return @contributes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def contributes=(parent_goal)
|
|
44
|
+
@contributes = parent_goal
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def server_id
|
|
48
|
+
return @id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Parses a goal from an XML element.
|
|
52
|
+
def self.parse(session, el)
|
|
53
|
+
id = el.attributes['id']
|
|
54
|
+
level = el.attributes['level'].to_i
|
|
55
|
+
contributes_id = el.attributes['contributes']
|
|
56
|
+
name = el.text
|
|
57
|
+
goal = Goal.new(id, level, contributes_id, name)
|
|
58
|
+
return goal
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_xml()
|
|
62
|
+
return "<goal id=\"#{@id}\" level=\"#{@level}\" contributes=\"#{@contributes.server_id}\" name=\"#{@name}\">"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_s()
|
|
66
|
+
msg = "$[#{name}]"
|
|
67
|
+
#if (contributes != NO_GOAL)
|
|
68
|
+
# msg += " (Contributes to: #{contributes.name})"
|
|
69
|
+
#end
|
|
70
|
+
return msg
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Toodledo
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# A priority enum.
|
|
5
|
+
#
|
|
6
|
+
class Priority
|
|
7
|
+
|
|
8
|
+
NEGATIVE = -1
|
|
9
|
+
|
|
10
|
+
LOW = 0
|
|
11
|
+
|
|
12
|
+
MEDIUM = 1
|
|
13
|
+
|
|
14
|
+
HIGH = 2
|
|
15
|
+
|
|
16
|
+
TOP = 3
|
|
17
|
+
|
|
18
|
+
PRIORITY_ARRAY = [ TOP, HIGH, MEDIUM, LOW, NEGATIVE ]
|
|
19
|
+
|
|
20
|
+
def self.each
|
|
21
|
+
PRIORITY_ARRAY.each{|value| yield(value)}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.valid?(input)
|
|
25
|
+
for priority in PRIORITY_ARRAY
|
|
26
|
+
if input == priority
|
|
27
|
+
return true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#
|
|
2
|
+
# To change this template, choose Tools | Templates
|
|
3
|
+
# and open the template in the editor.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module Toodledo
|
|
7
|
+
class Repeat
|
|
8
|
+
|
|
9
|
+
NONE = 0
|
|
10
|
+
WEEKLY = 1
|
|
11
|
+
MONTHLY = 2
|
|
12
|
+
YEARLY = 3
|
|
13
|
+
DAILY = 4
|
|
14
|
+
BIWEEKLY = 5
|
|
15
|
+
BIMONTHLY = 6
|
|
16
|
+
SEMIANNUALLY = 7
|
|
17
|
+
QUARTERLY = 8
|
|
18
|
+
|
|
19
|
+
REPEAT_ARRAY = [
|
|
20
|
+
NONE,
|
|
21
|
+
WEEKLY,
|
|
22
|
+
MONTHLY,
|
|
23
|
+
YEARLY,
|
|
24
|
+
DAILY,
|
|
25
|
+
BIWEEKLY,
|
|
26
|
+
BIMONTHLY,
|
|
27
|
+
SEMIANNUALLY,
|
|
28
|
+
QUARTERLY
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
def self.each
|
|
32
|
+
REPEAT_ARRAY.each{|value| yield(value)}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.valid?(input)
|
|
36
|
+
for repeat in REPEAT_ARRAY
|
|
37
|
+
if (repeat == input)
|
|
38
|
+
return true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
return false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
require 'toodledo'
|
|
2
|
+
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'net/https'
|
|
7
|
+
require 'openssl/ssl'
|
|
8
|
+
require 'rexml/document'
|
|
9
|
+
require 'logger'
|
|
10
|
+
|
|
11
|
+
module Toodledo
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# The Session. This is responsible for calling to the server
|
|
15
|
+
# and handling most functionality.
|
|
16
|
+
#
|
|
17
|
+
class Session
|
|
18
|
+
|
|
19
|
+
DEFAULT_API_URL = 'http://www.toodledo.com/api.php'
|
|
20
|
+
|
|
21
|
+
USER_AGENT = "Ruby/#{Toodledo::VERSION} (#{RUBY_PLATFORM})"
|
|
22
|
+
|
|
23
|
+
HEADERS = {
|
|
24
|
+
'User-Agent' => USER_AGENT
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
EXPIRATION_TIME_IN_SECS = 60 * 60
|
|
28
|
+
|
|
29
|
+
DATE_FORMAT = '%Y-%m-%d'
|
|
30
|
+
|
|
31
|
+
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
32
|
+
|
|
33
|
+
TIME_FORMAT = '%I:%M %p'
|
|
34
|
+
|
|
35
|
+
attr_accessor :logger
|
|
36
|
+
|
|
37
|
+
attr_reader :base_url, :user_id, :proxy
|
|
38
|
+
|
|
39
|
+
# Creates a new session, using the given user name and password.
|
|
40
|
+
# throws exception if user_id or password are nil.
|
|
41
|
+
def initialize(user_id, password, logger = nil)
|
|
42
|
+
raise "Nil user_id" if (user_id == nil)
|
|
43
|
+
raise "Nil password" if (password == nil)
|
|
44
|
+
|
|
45
|
+
@user_id = user_id
|
|
46
|
+
@password = password
|
|
47
|
+
|
|
48
|
+
@folders = nil
|
|
49
|
+
@contexts = nil
|
|
50
|
+
@goals = nil
|
|
51
|
+
@key = nil
|
|
52
|
+
@folders_by_id = nil
|
|
53
|
+
@goals_by_id = nil
|
|
54
|
+
@contexts_by_id = nil
|
|
55
|
+
|
|
56
|
+
@logger = logger
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Hashes the input string and returns a string hex digest.
|
|
60
|
+
def md5(input_string)
|
|
61
|
+
return Digest::MD5.hexdigest(input_string)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Connects to the server, asking for a new key that's good for an hour.
|
|
65
|
+
# Optionally takes a base URL as a parameter. Defaults to DEFAULT_API_URL.
|
|
66
|
+
def connect(base_url = DEFAULT_API_URL, proxy = nil)
|
|
67
|
+
logger.debug("connect(#{base_url}, #{proxy.inspect})") if logger
|
|
68
|
+
|
|
69
|
+
# XXX It looks like get_user_id doesn't work reliably. It always
|
|
70
|
+
# returns 1 even when we pass in a valid email and password.
|
|
71
|
+
# @user_id = get_user_id(@email, @password)
|
|
72
|
+
# logger.debug("user_id = #{@user_id}, #{@email} #{@password}")
|
|
73
|
+
|
|
74
|
+
if (@user_id == '1')
|
|
75
|
+
raise "No matching user_id found"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if (@user_id == '0')
|
|
79
|
+
raise "Server says we have a blank email or password"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Set the base URL.
|
|
83
|
+
@base_url = base_url
|
|
84
|
+
|
|
85
|
+
# Get the proxy information if it exists.
|
|
86
|
+
@proxy = proxy
|
|
87
|
+
|
|
88
|
+
session_token = get_token(@user_id)
|
|
89
|
+
key = md5(md5(@password).to_s + session_token + @user_id);
|
|
90
|
+
|
|
91
|
+
@key = key
|
|
92
|
+
@start_time = Time.now
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Disconnects from the server.
|
|
96
|
+
def disconnect()
|
|
97
|
+
logger.debug("disconnect()") if logger
|
|
98
|
+
@key = nil
|
|
99
|
+
@start_time = nil
|
|
100
|
+
@base_url = nil
|
|
101
|
+
@proxy = nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns true if the session has expired.
|
|
105
|
+
def expired?
|
|
106
|
+
#logger.debug("expired?") too annoying
|
|
107
|
+
|
|
108
|
+
# The key is only good for an hour. If it's been over an hour,
|
|
109
|
+
# then we count it as expired.
|
|
110
|
+
return true if (@start_time == nil)
|
|
111
|
+
return (Time.now - @start_time > EXPIRATION_TIME_IN_SECS)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns a parsable URI object from the base API URL and the parameters.
|
|
115
|
+
def make_uri(method, params)
|
|
116
|
+
url_string = URI.escape(@base_url + '?method=' + method + params)
|
|
117
|
+
return URI.parse(url_string)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# escape the & character as %26 and the ; character as %3B.
|
|
121
|
+
# throws an exception if input is nil.
|
|
122
|
+
def escape_text(input)
|
|
123
|
+
raise "Nil input" if (input == nil)
|
|
124
|
+
return input.to_s if (! input.kind_of? String)
|
|
125
|
+
|
|
126
|
+
output_string = input.gsub('&', '%26')
|
|
127
|
+
output_string = output_string.gsub(';', '%3B')
|
|
128
|
+
return output_string
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Calls Toodledo with the method name, the parameters and the session key.
|
|
132
|
+
# Returns the text inside the document root, if any.
|
|
133
|
+
def call(method, params, key = nil)
|
|
134
|
+
raise 'Nil method' if (method == nil)
|
|
135
|
+
raise 'Nil params' if (params == nil)
|
|
136
|
+
raise 'Wrong type of params' if (! params.kind_of? Hash)
|
|
137
|
+
raise 'Wrong method type' if (! method.kind_of? String)
|
|
138
|
+
|
|
139
|
+
if (@base_url == nil)
|
|
140
|
+
raise 'Must call connect() before this method'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Break all the parameters down into key=value seperated by semi colons
|
|
144
|
+
stringified_params = (key != nil) ? ';key=' + key : ''
|
|
145
|
+
|
|
146
|
+
params.each { |k, v|
|
|
147
|
+
stringified_params += ';' + k.to_s + '=' + escape_text(v)
|
|
148
|
+
}
|
|
149
|
+
url = make_uri(method, stringified_params)
|
|
150
|
+
|
|
151
|
+
# If it's been more than an hour, then ask for a new key.
|
|
152
|
+
if (@key != nil && expired?)
|
|
153
|
+
logger.debug("call(#{method}) connection expired, reconnecting...") if logger
|
|
154
|
+
|
|
155
|
+
# Save the connection information (we'll need it)
|
|
156
|
+
base_url = @base_url
|
|
157
|
+
proxy = @proxy
|
|
158
|
+
disconnect() # ensures that key == nil, which is crucial to avoid an endless loop...
|
|
159
|
+
connect(base_url, proxy)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Establish the proxy
|
|
163
|
+
if (@proxy != nil)
|
|
164
|
+
logger.debug("call(#{method}) establishing proxy...") if logger
|
|
165
|
+
|
|
166
|
+
proxy_host = @proxy['host']
|
|
167
|
+
proxy_port = @proxy['port']
|
|
168
|
+
proxy_user = @proxy['user']
|
|
169
|
+
proxy_password = @proxy['password']
|
|
170
|
+
|
|
171
|
+
if (proxy_user == nil || proxy_password == nil)
|
|
172
|
+
http = Net::HTTP::Proxy(proxy_host, proxy_port).new(url.host, url.port)
|
|
173
|
+
else
|
|
174
|
+
http = Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_password).new(url.host, url.port)
|
|
175
|
+
end
|
|
176
|
+
else
|
|
177
|
+
http = Net::HTTP.new(url.host, url.port)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
if (url.scheme == 'https')
|
|
181
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
182
|
+
http.use_ssl = true
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
if logger
|
|
186
|
+
logger.debug("call(#{method}) request: #{url.path}?#{url.query}#{url.fragment}")
|
|
187
|
+
end
|
|
188
|
+
start_time = Time.now
|
|
189
|
+
|
|
190
|
+
# make the call
|
|
191
|
+
response = http.request_get(url.request_uri, HEADERS)
|
|
192
|
+
body = response.body
|
|
193
|
+
|
|
194
|
+
# body = url.read
|
|
195
|
+
end_time = Time.now
|
|
196
|
+
doc = REXML::Document.new body
|
|
197
|
+
|
|
198
|
+
if logger
|
|
199
|
+
logger.debug("call(#{method}) response: " + doc.to_s)
|
|
200
|
+
logger.debug("call(#{method}) time: " + (end_time - start_time).to_s + ' seconds')
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
root_node = doc.root
|
|
204
|
+
if (root_node.name == 'error')
|
|
205
|
+
error_text = root_node.text
|
|
206
|
+
if (error_text == 'Invalid ID number')
|
|
207
|
+
raise Toodledo::ItemNotFoundError.new(error_text)
|
|
208
|
+
else
|
|
209
|
+
raise Toodledo::ServerError.new(error_text)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
return root_node
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Gets the token method, given the id.
|
|
217
|
+
def get_token(user_id)
|
|
218
|
+
raise "Nil user_id" if (user_id == nil)
|
|
219
|
+
|
|
220
|
+
params = { :userid => user_id }
|
|
221
|
+
result = call('getToken', params)
|
|
222
|
+
return result.text
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Returns the user id. As far as I can tell, this method is broken
|
|
226
|
+
# and always returns false.
|
|
227
|
+
#
|
|
228
|
+
# If the userid comes back as 0, it means that either the email
|
|
229
|
+
# or password that you sent was blank. If the userid comes back as 1,
|
|
230
|
+
# it means that the lookup failed. A valid userid will always be a
|
|
231
|
+
# 15 or 16 character hexadecimal string.
|
|
232
|
+
def get_user_id(email, password)
|
|
233
|
+
raise "Nil email" if (email == nil)
|
|
234
|
+
raise "Nil password" if (password == nil)
|
|
235
|
+
|
|
236
|
+
params = { :email => email, :pass => password }
|
|
237
|
+
result = call('getUserid', params)
|
|
238
|
+
return result.text
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
############################################################################
|
|
242
|
+
# Tasks
|
|
243
|
+
############################################################################
|
|
244
|
+
|
|
245
|
+
#
|
|
246
|
+
# Gets tasks that meet the criteria given in params. Available criteria is
|
|
247
|
+
# as follows:
|
|
248
|
+
|
|
249
|
+
# * title:
|
|
250
|
+
# * folder:
|
|
251
|
+
# * context:
|
|
252
|
+
# * goal:
|
|
253
|
+
# * duedate:
|
|
254
|
+
# * duetime
|
|
255
|
+
# * repeat:
|
|
256
|
+
# * priority:
|
|
257
|
+
# * parent:
|
|
258
|
+
# * shorter:
|
|
259
|
+
# * longer:
|
|
260
|
+
# * before
|
|
261
|
+
# * after
|
|
262
|
+
# * modbefore
|
|
263
|
+
# * modafter
|
|
264
|
+
# * compbefore
|
|
265
|
+
# * compafter
|
|
266
|
+
# * notcomp
|
|
267
|
+
#
|
|
268
|
+
# Returns an array of tasks. This information is never cached.
|
|
269
|
+
def get_tasks(params={})
|
|
270
|
+
logger.debug("get_tasks(#{params.inspect})") if logger
|
|
271
|
+
myhash = {}
|
|
272
|
+
|
|
273
|
+
# * title : A text string up to 255 characters.
|
|
274
|
+
handle_string(myhash, params, :title)
|
|
275
|
+
|
|
276
|
+
# If the folder is a string, then we assume we're supposed to find out what
|
|
277
|
+
# the id is.
|
|
278
|
+
handle_folder(myhash, params)
|
|
279
|
+
|
|
280
|
+
# Context handling
|
|
281
|
+
handle_context(myhash, params)
|
|
282
|
+
|
|
283
|
+
# Goal handling
|
|
284
|
+
handle_goal(myhash, params)
|
|
285
|
+
|
|
286
|
+
# duedate handling. Take either a string or a Time object.'YYYY-MM-DD'
|
|
287
|
+
# This does not need special handling, because if it's not a time object
|
|
288
|
+
# then we don't pass through anything at all.
|
|
289
|
+
handle_date(myhash, params, :duedate)
|
|
290
|
+
|
|
291
|
+
# duetime handling. Take either a string or a Time object.
|
|
292
|
+
handle_time(myhash, params, :duetime)
|
|
293
|
+
|
|
294
|
+
# repeat: takes in an integer in the proper range..
|
|
295
|
+
handle_repeat(myhash, params)
|
|
296
|
+
|
|
297
|
+
# priority: takes in an integer in the proper range.
|
|
298
|
+
handle_priority(myhash, params)
|
|
299
|
+
|
|
300
|
+
# * parent : This is used to Pro accounts that have access to subtasks.
|
|
301
|
+
# Set this to the id number of the parent task and you will get its
|
|
302
|
+
# subtasks. The default is 0, which is a special number that returns
|
|
303
|
+
# tasks that do not have a parent.
|
|
304
|
+
handle_parent(myhash, params)
|
|
305
|
+
|
|
306
|
+
# * shorter : An integer representing minutes. This is used for finding
|
|
307
|
+
# tasks with a duration that is shorter than the specified number of minutes.
|
|
308
|
+
handle_number(myhash, params, :shorter)
|
|
309
|
+
|
|
310
|
+
# * longer : An integer representing minutes. This is used for finding
|
|
311
|
+
# tasks with a duration that is longer than the specified number of minutes.
|
|
312
|
+
handle_number(myhash, params, :longer)
|
|
313
|
+
|
|
314
|
+
# * before : A date formated as YYYY-MM-DD. Used to find tasks with
|
|
315
|
+
# due-dates before this date.
|
|
316
|
+
handle_date(myhash, params, :before)
|
|
317
|
+
|
|
318
|
+
# * after : A date formated as YYYY-MM-DD. Used to find tasks with
|
|
319
|
+
# due-dates after this date.
|
|
320
|
+
handle_date(myhash, params, :after)
|
|
321
|
+
|
|
322
|
+
# * modbefore : A date-time formated as YYYY-MM-DD HH:MM:SS. Used to find
|
|
323
|
+
# tasks with a modified date and time before this dateand time.
|
|
324
|
+
handle_datetime(myhash, params, :modbefore)
|
|
325
|
+
|
|
326
|
+
# * modafter : A date-time formated as YYYY-MM-DD HH:MM:SS. Used to find
|
|
327
|
+
# tasks with a modified date and time after this dateand time.
|
|
328
|
+
handle_datetime(myhash, params, :modafter)
|
|
329
|
+
|
|
330
|
+
# * compbefore : A date formated as YYYY-MM-DD. Used to find tasks with a
|
|
331
|
+
# completed date before this date.
|
|
332
|
+
handle_date(myhash, params, :compbefore)
|
|
333
|
+
|
|
334
|
+
# * compafter : A date formated as YYYY-MM-DD. Used to find tasks with a
|
|
335
|
+
# completed date after this date.
|
|
336
|
+
handle_date(myhash, params, :compafter)
|
|
337
|
+
|
|
338
|
+
# * notcomp : Set to 1 to omit completed tasks. Omit variable, or set to 0
|
|
339
|
+
# to retrieve both completed and uncompleted tasks.
|
|
340
|
+
handle_boolean(myhash, params, :notcomp)
|
|
341
|
+
|
|
342
|
+
result = call('getTasks', myhash, @key)
|
|
343
|
+
tasks = []
|
|
344
|
+
result.elements.each do |el|
|
|
345
|
+
task = Task.parse(self, el)
|
|
346
|
+
tasks << task
|
|
347
|
+
end
|
|
348
|
+
return tasks
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Adds a task to Toodledo.
|
|
352
|
+
#
|
|
353
|
+
# Required Parameters:
|
|
354
|
+
# title: a String. This is the only required property.
|
|
355
|
+
#
|
|
356
|
+
# Optional Parameters:
|
|
357
|
+
# tag: a String
|
|
358
|
+
# folder: folder id or String matching the folder name
|
|
359
|
+
# context: context id or String matching the context name
|
|
360
|
+
# goal: goal id or String matching the Goal Name
|
|
361
|
+
# duedate: Time or String object "YYYY-MM-DD". If this is a string, it
|
|
362
|
+
# may take an optional modifier.
|
|
363
|
+
# duetime: Time or String object "MM:SS p"}
|
|
364
|
+
# parent: parent id }
|
|
365
|
+
# repeat: one of { :none, :weekly, :monthly :yearly :daily :biweekly,
|
|
366
|
+
# :bimonthly, :semiannually, :quarterly }
|
|
367
|
+
# length: a Number, number of minutes
|
|
368
|
+
# priority: one of { :negative, :low, :medium, :high, :top }
|
|
369
|
+
#
|
|
370
|
+
# Returns: the id of the added task as a String.
|
|
371
|
+
def add_task(title, params={})
|
|
372
|
+
logger.debug("add_task(#{title}, #{params.inspect})") if logger
|
|
373
|
+
raise "Nil id" if (title == nil)
|
|
374
|
+
|
|
375
|
+
myhash = {:title => title}
|
|
376
|
+
|
|
377
|
+
handle_string(myhash, params, :tag)
|
|
378
|
+
|
|
379
|
+
# If the folder is a string, then we assume we're supposed to find out what
|
|
380
|
+
# the id is.
|
|
381
|
+
handle_folder(myhash, params)
|
|
382
|
+
|
|
383
|
+
# Context handling
|
|
384
|
+
handle_context(myhash, params)
|
|
385
|
+
|
|
386
|
+
# Goal handling
|
|
387
|
+
handle_goal(myhash, params)
|
|
388
|
+
|
|
389
|
+
# duedate handling. Take either a string or a Time object.'YYYY-MM-DD'
|
|
390
|
+
handle_date(myhash, params, :duedate)
|
|
391
|
+
|
|
392
|
+
# duetime handling. Take either a string or a Time object.
|
|
393
|
+
handle_time(myhash, params, :duetime)
|
|
394
|
+
|
|
395
|
+
# parent handling.
|
|
396
|
+
handle_parent(myhash, params)
|
|
397
|
+
|
|
398
|
+
# repeat: use the map to change from the symbol to the raw numeric value.
|
|
399
|
+
handle_repeat(myhash, params)
|
|
400
|
+
|
|
401
|
+
# priority use the map to change from the symbol to the raw numeric value.
|
|
402
|
+
handle_priority(myhash, params)
|
|
403
|
+
|
|
404
|
+
handle_string(myhash, params, :note)
|
|
405
|
+
|
|
406
|
+
result = call('addTask', myhash, @key)
|
|
407
|
+
|
|
408
|
+
return result.text
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# * id : The id number of the task to edit.
|
|
412
|
+
# * title : A text string up to 255 characters representing the name of the task.
|
|
413
|
+
# * folder : The id number of the folder.
|
|
414
|
+
# * context : The id number of the context.
|
|
415
|
+
# * goal : The id number of the goal.
|
|
416
|
+
# * completed : true or false.
|
|
417
|
+
# * duedate : A date formatted as YYYY-MM-DD with an optional modifier
|
|
418
|
+
# attached to the front. Examples: "2007-01-23" , "=2007-01-23" ,
|
|
419
|
+
# ">2007-01-23". To unset the date, set it to '0000-00-00'.
|
|
420
|
+
# * duetime : A date formated as HH:MM MM.
|
|
421
|
+
# * repeat : Use the REPEAT_MAP with the relevant symbol here.
|
|
422
|
+
# * length : An integer representing the number of minutes that the task will take to complete.
|
|
423
|
+
# * priority : Use the PRIORITY_MAP with the relevant symbol here.
|
|
424
|
+
# * note : A text string.
|
|
425
|
+
def edit_task(id, params = {})
|
|
426
|
+
logger.debug("edit_task(#{id}, #{params.inspect})") if logger
|
|
427
|
+
raise "Nil id" if (id == nil)
|
|
428
|
+
|
|
429
|
+
myhash = { :id => id }
|
|
430
|
+
|
|
431
|
+
handle_string(myhash, params, :tag)
|
|
432
|
+
|
|
433
|
+
# If the folder is a string, then we assume we're supposed to find out what
|
|
434
|
+
# the id is.
|
|
435
|
+
handle_folder(myhash, params)
|
|
436
|
+
|
|
437
|
+
# Context handling
|
|
438
|
+
handle_context(myhash, params)
|
|
439
|
+
|
|
440
|
+
# Goal handling
|
|
441
|
+
handle_goal(myhash, params)
|
|
442
|
+
|
|
443
|
+
# duedate handling. Take either a string or a Time object.'YYYY-MM-DD'
|
|
444
|
+
handle_date(myhash, params, :duedate)
|
|
445
|
+
|
|
446
|
+
# duetime handling. Take either a string or a Time object.
|
|
447
|
+
handle_time(myhash, params, :duetime)
|
|
448
|
+
|
|
449
|
+
# parent handling.
|
|
450
|
+
handle_parent(myhash, params)
|
|
451
|
+
|
|
452
|
+
# Handle completion.
|
|
453
|
+
handle_boolean(myhash, params, :completed)
|
|
454
|
+
|
|
455
|
+
# repeat: use the map to change from the symbol to the raw numeric value.
|
|
456
|
+
handle_repeat(myhash, params)
|
|
457
|
+
|
|
458
|
+
# priority use the map to change from the symbol to the raw numeric value.
|
|
459
|
+
handle_priority(myhash, params)
|
|
460
|
+
|
|
461
|
+
handle_string(myhash, params, :note)
|
|
462
|
+
|
|
463
|
+
result = call('editTask', myhash, @key)
|
|
464
|
+
|
|
465
|
+
return (result.text == '1')
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
#
|
|
469
|
+
# Deletes the task, using the task id.
|
|
470
|
+
#
|
|
471
|
+
def delete_task(id)
|
|
472
|
+
logger.debug("delete_task(#{id})") if logger
|
|
473
|
+
raise "Nil id" if (id == nil)
|
|
474
|
+
|
|
475
|
+
result = call('deleteTask', { :id => id }, @key)
|
|
476
|
+
|
|
477
|
+
return (result.text == '1')
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
############################################################################
|
|
481
|
+
# Contexts
|
|
482
|
+
############################################################################
|
|
483
|
+
|
|
484
|
+
#
|
|
485
|
+
# Returns the context with the given name.
|
|
486
|
+
#
|
|
487
|
+
def get_context_by_name(context_name)
|
|
488
|
+
logger.debug("get_context_by_name(#{context_name})") if logger
|
|
489
|
+
|
|
490
|
+
if (@contexts_by_name == nil)
|
|
491
|
+
get_contexts(true)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
context = @contexts_by_name[context_name.downcase]
|
|
495
|
+
return context
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
#
|
|
499
|
+
# Returns the context with the given id.
|
|
500
|
+
#
|
|
501
|
+
def get_context_by_id(context_id)
|
|
502
|
+
logger.debug("get_context_by_id(#{context_id})") if logger
|
|
503
|
+
|
|
504
|
+
if (@contexts_by_id == nil)
|
|
505
|
+
get_contexts(true)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
context = @contexts_by_id[context_id]
|
|
509
|
+
return context
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
#
|
|
513
|
+
# Gets the array of contexts.
|
|
514
|
+
#
|
|
515
|
+
def get_contexts(flush = false)
|
|
516
|
+
logger.debug("get_contexts(#{flush})") if logger
|
|
517
|
+
return @contexts unless (flush || @contexts == nil)
|
|
518
|
+
|
|
519
|
+
result = call('getContexts', {}, @key)
|
|
520
|
+
contexts_by_name = {}
|
|
521
|
+
contexts_by_id = {}
|
|
522
|
+
contexts = []
|
|
523
|
+
|
|
524
|
+
result.elements.each { |el|
|
|
525
|
+
context = Context.parse(self, el)
|
|
526
|
+
contexts << context
|
|
527
|
+
contexts_by_id[context.server_id] = context
|
|
528
|
+
contexts_by_name[context.name.downcase] = context
|
|
529
|
+
}
|
|
530
|
+
@contexts_by_id = contexts_by_id
|
|
531
|
+
@contexts_by_name = contexts_by_name
|
|
532
|
+
@contexts = contexts
|
|
533
|
+
return contexts
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
#
|
|
537
|
+
# Adds the context to Toodledo, with the title.
|
|
538
|
+
#
|
|
539
|
+
def add_context(title)
|
|
540
|
+
logger.debug("add_context(#{title})") if logger
|
|
541
|
+
raise "Nil title" if (title == nil)
|
|
542
|
+
|
|
543
|
+
result = call('addContext', { :title => title }, @key)
|
|
544
|
+
|
|
545
|
+
flush_contexts()
|
|
546
|
+
|
|
547
|
+
return result.text
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
#
|
|
551
|
+
# Deletes the context from Toodledo, using the id.
|
|
552
|
+
#
|
|
553
|
+
def delete_context(id)
|
|
554
|
+
logger.debug("delete_context(#{id})") if logger
|
|
555
|
+
raise "Nil id" if (id == nil)
|
|
556
|
+
|
|
557
|
+
result = call('deleteContext', { :id => id }, @key)
|
|
558
|
+
|
|
559
|
+
flush_contexts();
|
|
560
|
+
|
|
561
|
+
return (result.text == '1')
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
#
|
|
565
|
+
# Deletes the cached contexts.
|
|
566
|
+
#
|
|
567
|
+
def flush_contexts()
|
|
568
|
+
logger.debug('flush_contexts()') if logger
|
|
569
|
+
|
|
570
|
+
@contexts_by_id = nil
|
|
571
|
+
@contexts_by_name = nil
|
|
572
|
+
@contexts = nil
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
############################################################################
|
|
576
|
+
# Goals
|
|
577
|
+
############################################################################
|
|
578
|
+
|
|
579
|
+
#
|
|
580
|
+
# Returns the goal with the given name. Case insensitive.
|
|
581
|
+
#
|
|
582
|
+
def get_goal_by_name(goal_name)
|
|
583
|
+
logger.debug("get_goal_by_name(#{goal_name})") if logger
|
|
584
|
+
if (@goals_by_name == nil)
|
|
585
|
+
get_goals(true)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
goal = @goals_by_name[goal_name.downcase]
|
|
589
|
+
return goal
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
#
|
|
593
|
+
# Returns the goal with the given id.
|
|
594
|
+
#
|
|
595
|
+
def get_goal_by_id(goal_id)
|
|
596
|
+
logger.debug("get_goal_by_id(#{goal_id})") if logger
|
|
597
|
+
if (@goals_by_id == nil)
|
|
598
|
+
get_goals(true)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
goal = @goals_by_id[goal_id]
|
|
602
|
+
return goal
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
#
|
|
606
|
+
# Returns the array of goals.
|
|
607
|
+
#
|
|
608
|
+
def get_goals(flush = false)
|
|
609
|
+
logger.debug("get_goals(#{flush})") if logger
|
|
610
|
+
return @goals unless (flush || @goals == nil)
|
|
611
|
+
|
|
612
|
+
result = call('getGoals', {}, @key)
|
|
613
|
+
|
|
614
|
+
goals_by_name = {}
|
|
615
|
+
goals_by_id = {}
|
|
616
|
+
goals = []
|
|
617
|
+
result.elements.each do |el|
|
|
618
|
+
goal = Goal.parse(self, el)
|
|
619
|
+
goals << goal
|
|
620
|
+
goals_by_id[goal.server_id] = goal
|
|
621
|
+
goals_by_name[goal.name.downcase] = goal
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# Loop through and make sure we've got a reference for every contributing
|
|
625
|
+
# goal.
|
|
626
|
+
for goal in goals
|
|
627
|
+
next if (goal.contributes_id == Goal::NO_GOAL.server_id)
|
|
628
|
+
parent_goal = goals_by_id[goal.contributes_id]
|
|
629
|
+
goal.contributes = parent_goal
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
@goals = goals
|
|
633
|
+
@goals_by_name = goals_by_name
|
|
634
|
+
@goals_by_id = goals_by_id
|
|
635
|
+
return goals
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
#
|
|
639
|
+
# Adds a new goal with the given title, the level (short to long term) and
|
|
640
|
+
# the contributing goal id.
|
|
641
|
+
#
|
|
642
|
+
def add_goal(title, level = 0, contributes = 0)
|
|
643
|
+
logger.debug("add_goal(#{title}, #{level}, #{contributes})") if logger
|
|
644
|
+
raise "Nil title" if (title == nil)
|
|
645
|
+
|
|
646
|
+
params = { :title => title, :level => level, :contributes => contributes }
|
|
647
|
+
result = call('addGoal', params, @key)
|
|
648
|
+
|
|
649
|
+
flush_goals()
|
|
650
|
+
|
|
651
|
+
return result.text
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
#
|
|
655
|
+
# Delete the goal with the given id.
|
|
656
|
+
#
|
|
657
|
+
def delete_goal(id)
|
|
658
|
+
logger.debug("delete_goal(#{id})") if logger
|
|
659
|
+
raise "Nil id" if (id == nil)
|
|
660
|
+
|
|
661
|
+
result = call('deleteGoal', { :id => id }, @key)
|
|
662
|
+
|
|
663
|
+
flush_goals()
|
|
664
|
+
|
|
665
|
+
return (result.text == '1')
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
#
|
|
669
|
+
# Nils the cached goals.
|
|
670
|
+
#
|
|
671
|
+
def flush_goals()
|
|
672
|
+
logger.debug('flush_goals()') if logger
|
|
673
|
+
|
|
674
|
+
@goals = nil
|
|
675
|
+
@goals_by_name = nil
|
|
676
|
+
@goals_by_id = nil
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
############################################################################
|
|
680
|
+
# Folders
|
|
681
|
+
############################################################################
|
|
682
|
+
|
|
683
|
+
#
|
|
684
|
+
# Gets the folder by the name. Case insensitive.
|
|
685
|
+
def get_folder_by_name(folder_name)
|
|
686
|
+
logger.debug("get_folders_by_name(#{folder_name})") if logger
|
|
687
|
+
raise "Nil folder name" if (folder_name == nil)
|
|
688
|
+
|
|
689
|
+
if (@folders_by_name == nil)
|
|
690
|
+
get_folders(true)
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
return @folders_by_name[folder_name.downcase]
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
#
|
|
697
|
+
# Gets the folder with the given id.
|
|
698
|
+
#
|
|
699
|
+
def get_folder_by_id(folder_id)
|
|
700
|
+
logger.debug("get_folder_by_id(#{folder_id})") if logger
|
|
701
|
+
raise "Nil folder_id" if (folder_id == nil)
|
|
702
|
+
|
|
703
|
+
if (@folders_by_id == nil)
|
|
704
|
+
get_folders(true)
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
return @folders_by_id[folder_id]
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Gets all the folders.
|
|
711
|
+
def get_folders(flush = false)
|
|
712
|
+
logger.debug("get_folders(#{flush})") if logger
|
|
713
|
+
return @folders unless (flush || @folders == nil)
|
|
714
|
+
|
|
715
|
+
result = call('getFolders', {}, @key)
|
|
716
|
+
# <folders>
|
|
717
|
+
# <folder id="123" private="0" archived="0">Shopping</folder>
|
|
718
|
+
# <folder id="456" private="0" archived="0">Home Repairs</folder>
|
|
719
|
+
# <folder id="789" private="0" archived="0">Vacation Planning</folder>
|
|
720
|
+
# <folder id="234" private="0" archived="0">Chores</folder>
|
|
721
|
+
# <folder id="567" private="1" archived="0">Work</folder>
|
|
722
|
+
# </folders>
|
|
723
|
+
folders = []
|
|
724
|
+
folders_by_name = {}
|
|
725
|
+
folders_by_id = {}
|
|
726
|
+
result.elements.each { |el|
|
|
727
|
+
folder = Folder.parse(self, el)
|
|
728
|
+
folders.push(folder)
|
|
729
|
+
folders_by_name[folder.name.downcase] = folder # lowercase the key search
|
|
730
|
+
folders_by_id[folder.server_id] = folder
|
|
731
|
+
}
|
|
732
|
+
@folders = folders
|
|
733
|
+
@folders_by_name = folders_by_name
|
|
734
|
+
@folders_by_id = folders_by_id
|
|
735
|
+
return @folders
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
# Adds a folder.
|
|
739
|
+
# * title : A text string up to 32 characters.
|
|
740
|
+
# * private : A boolean value that describes if this folder can be shared.
|
|
741
|
+
#
|
|
742
|
+
# Returns the id of the newly added folder.
|
|
743
|
+
def add_folder(title, is_private = 1)
|
|
744
|
+
logger.debug("add_folder(#{title}, #{is_private})") if logger
|
|
745
|
+
raise "Nil title" if (title == nil)
|
|
746
|
+
|
|
747
|
+
if (is_private.kind_of? TrueClass)
|
|
748
|
+
is_private = 1
|
|
749
|
+
elsif (is_private.kind_of? FalseClass)
|
|
750
|
+
is_private = 0
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
myhash = { :title => title, :private => is_private}
|
|
754
|
+
|
|
755
|
+
result = call('addFolder', myhash, @key)
|
|
756
|
+
|
|
757
|
+
flush_folders()
|
|
758
|
+
|
|
759
|
+
return result.text
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
#
|
|
763
|
+
# Nils out the cached folders.
|
|
764
|
+
#
|
|
765
|
+
def flush_folders()
|
|
766
|
+
logger.debug("flush_folders()") if logger
|
|
767
|
+
|
|
768
|
+
@folders = nil
|
|
769
|
+
@folders_by_name = nil
|
|
770
|
+
@folders_by_id = nil
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Edits a folder.
|
|
774
|
+
# * id : The id number of the folder to edit.
|
|
775
|
+
# * title : A text string up to 32 characters.
|
|
776
|
+
# * private : A boolean value (0 or 1) that describes if this folder can be
|
|
777
|
+
# shared. A value of 1 means that this folder is private.
|
|
778
|
+
# * archived : A boolean value (0 or 1) that describes if this folder is archived.
|
|
779
|
+
#
|
|
780
|
+
# Returns true if the edit was successful.
|
|
781
|
+
def edit_folder(id, params = {})
|
|
782
|
+
logger.debug("edit_folder(#{id}, #{params.inspect})") if logger
|
|
783
|
+
raise "Nil id" if (id == nil)
|
|
784
|
+
|
|
785
|
+
myhash = { :id => id }
|
|
786
|
+
|
|
787
|
+
handle_string(myhash, params, :title)
|
|
788
|
+
|
|
789
|
+
handle_boolean(myhash, params, :private)
|
|
790
|
+
|
|
791
|
+
handle_boolean(myhash, params, :archived)
|
|
792
|
+
|
|
793
|
+
result = call('editFolder', myhash, @key)
|
|
794
|
+
|
|
795
|
+
flush_folders()
|
|
796
|
+
|
|
797
|
+
return (result.text == '1')
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
# Deletes the folder with the id.
|
|
801
|
+
# id : The folder id.
|
|
802
|
+
#
|
|
803
|
+
# Returns true if the delete was successful.
|
|
804
|
+
def delete_folder(id)
|
|
805
|
+
logger.debug("delete_folder(#{id})") if logger
|
|
806
|
+
raise "Nil id" if (id == nil)
|
|
807
|
+
|
|
808
|
+
result = call('deleteFolder', { :id => id }, @key)
|
|
809
|
+
|
|
810
|
+
flush_folders()
|
|
811
|
+
|
|
812
|
+
return (result.text == '1')
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
############################################################################
|
|
816
|
+
# Helper methods follow.
|
|
817
|
+
#
|
|
818
|
+
# These methods will convert the appropriate format for talking to the
|
|
819
|
+
# Toodledo server. They do not parse the XML that comes back from the
|
|
820
|
+
# server.
|
|
821
|
+
############################################################################
|
|
822
|
+
|
|
823
|
+
def handle_number(myhash, params, symbol)
|
|
824
|
+
value = params[symbol]
|
|
825
|
+
if (value != nil)
|
|
826
|
+
if (value.kind_of? FixNum)
|
|
827
|
+
myhash.merge!({ symbol => value.to_s})
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
def handle_boolean(myhash, params, symbol)
|
|
833
|
+
value = params[symbol]
|
|
834
|
+
if (value == nil)
|
|
835
|
+
return
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
case value
|
|
839
|
+
when TrueClass, FalseClass
|
|
840
|
+
bool = (value == true) ? '1' : '0'
|
|
841
|
+
when String
|
|
842
|
+
bool = ('true' == value.downcase) ? '1' : '0'
|
|
843
|
+
when FixNum
|
|
844
|
+
bool = (value == 1) ? '1' : '0'
|
|
845
|
+
else
|
|
846
|
+
bool = value
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
myhash.merge!({ symbol => bool })
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def handle_string(myhash, params, symbol)
|
|
853
|
+
value = params[symbol]
|
|
854
|
+
if (value != nil)
|
|
855
|
+
myhash.merge!({ symbol => value })
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
def handle_date(myhash, params, symbol)
|
|
860
|
+
value = params[symbol]
|
|
861
|
+
if (value == nil)
|
|
862
|
+
return
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
case value
|
|
866
|
+
when Time
|
|
867
|
+
value = value.strftime('%Y-%m-%d')
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
myhash.merge!({ symbol => value })
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
def handle_time(myhash, params, symbol)
|
|
874
|
+
value = params[symbol]
|
|
875
|
+
if (value == nil)
|
|
876
|
+
return
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
case value
|
|
880
|
+
when Time
|
|
881
|
+
value = value.strftime(TIME_FORMAT)
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
myhash.merge!({ symbol => value })
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
# Handles a generic date time value.
|
|
888
|
+
def handle_datetime(myhash, params, symbol)
|
|
889
|
+
# YYYY-MM-DD HH:MM:SS
|
|
890
|
+
value = params[symbol]
|
|
891
|
+
if (value == nil)
|
|
892
|
+
return
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
case value
|
|
896
|
+
when Time
|
|
897
|
+
value = value.strftime(DATETIME_FORMAT)
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
myhash.merge!({ symbol => value })
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
# Handles the parent task object. Only takes a task object or id.
|
|
904
|
+
def handle_parent(myhash, params)
|
|
905
|
+
parent = params[:parent]
|
|
906
|
+
if (parent == nil)
|
|
907
|
+
return
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
parent_id = nil
|
|
911
|
+
case parent
|
|
912
|
+
when Task
|
|
913
|
+
parent_id = parent.server_id
|
|
914
|
+
else
|
|
915
|
+
parent_id = parent
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
myhash.merge!({ :parent => parent_id })
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# Handles a folder (in the form of a folder name, object or id) and puts
|
|
922
|
+
# the folder_id in myhash.
|
|
923
|
+
def handle_folder(myhash, params)
|
|
924
|
+
folder = params[:folder]
|
|
925
|
+
if (folder == nil)
|
|
926
|
+
return
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
folder_id = nil
|
|
930
|
+
case folder
|
|
931
|
+
when String
|
|
932
|
+
folder_obj = get_folder_by_name(folder)
|
|
933
|
+
if (folder_obj == nil)
|
|
934
|
+
raise Toodledo::ItemNotFoundError.new("No folder found with name #{folder}")
|
|
935
|
+
end
|
|
936
|
+
folder_id = folder_obj.server_id
|
|
937
|
+
when Folder
|
|
938
|
+
folder_id = folder.server_id
|
|
939
|
+
else
|
|
940
|
+
folder_id = folder
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
myhash.merge!({ :folder => folder_id })
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
# Takes in a context (in the form of a string, a Context object or
|
|
947
|
+
# a context id) and adds it to myhash as a context_id.
|
|
948
|
+
def handle_context(myhash, params)
|
|
949
|
+
context = params[:context]
|
|
950
|
+
if (context == nil)
|
|
951
|
+
return
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
case context
|
|
955
|
+
when String
|
|
956
|
+
context_obj = get_context_by_name(context)
|
|
957
|
+
if (context_obj == nil)
|
|
958
|
+
raise Toodledo::ItemNotFoundError.new("No context found with name '#{context}'")
|
|
959
|
+
end
|
|
960
|
+
context_id = context_obj.server_id
|
|
961
|
+
when Context
|
|
962
|
+
context_id = context.server_id
|
|
963
|
+
else
|
|
964
|
+
context_id = context
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
myhash.merge!({ :context => context_id })
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
# Takes a goal (as a goal title, a goal object or a goal id) and sets it
|
|
971
|
+
# in myhash.
|
|
972
|
+
def handle_goal(myhash, params)
|
|
973
|
+
goal = params[:goal]
|
|
974
|
+
if (goal == nil)
|
|
975
|
+
return
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
case goal
|
|
979
|
+
when String
|
|
980
|
+
goal_obj = get_goal_by_name(goal)
|
|
981
|
+
if (goal_obj == nil)
|
|
982
|
+
raise Toodledo::ItemNotFoundError.new("No goal found with name '#{goal}'")
|
|
983
|
+
end
|
|
984
|
+
goal_id = goal_obj.server_id
|
|
985
|
+
when Goal
|
|
986
|
+
goal_id = goal.server_id
|
|
987
|
+
else
|
|
988
|
+
goal_id = goal
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
# Otherwise, assume it's a number.
|
|
992
|
+
myhash.merge!({ :goal => goal_id })
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
# Handles the repeat parameter.
|
|
996
|
+
def handle_repeat(myhash, params)
|
|
997
|
+
repeat = params[:repeat]
|
|
998
|
+
|
|
999
|
+
if (repeat == nil)
|
|
1000
|
+
return
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
if (! Repeat.valid?(repeat))
|
|
1004
|
+
raise "Invalid repeat value: #{repeat}"
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
myhash.merge!({ :repeat => repeat })
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
# Handles the priority. This must be one of several values.
|
|
1011
|
+
def handle_priority(myhash, params)
|
|
1012
|
+
priority = params[:priority]
|
|
1013
|
+
|
|
1014
|
+
if (priority == nil)
|
|
1015
|
+
return nil
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
if (! Priority.valid?(priority))
|
|
1019
|
+
raise "Invalid priority value: #{priority}"
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
myhash.merge!({ :priority => priority })
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
|