toodledo 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,19 @@
1
+ == 1.1.0 / 2008-02-24
2
+
3
+ * Add functionality to add, delete goals, contexts, and folders from client.
4
+ * Add functionality to archive folders.
5
+ * Add stdin command to read from pipe or redirected files
6
+ * Add formatters for the client so it's easier to tweak line output.
7
+ * Add proper priority support to the client (in the format !top, !high, etc.)
8
+ * Make filter able to take symbols.
9
+ * Change symbol for goals from $ to ^ (was confusing when entering 'I owe $5')
10
+ * Add unit tests for client.
11
+ * Better error messages and explanation of toodledo setup.
12
+ * Fix the debug command so it doesn't crash the interactive client.
13
+ * Fixed some bugs in date processing.
14
+ * Fixed bugs in priority handling.
15
+ * Fixed session invalid bug on reconnect.
16
+
1
17
  == 1.0.2 / 2008-02-14
2
18
 
3
19
  * Fix a silly bug where whitespace wasn't stripped from edit, complete and delete.
data/Manifest.txt CHANGED
@@ -1,4 +1,3 @@
1
- CHANGELOG
2
1
  History.txt
3
2
  Manifest.txt
4
3
  README.txt
@@ -9,22 +8,32 @@ lib/toodledo/command_line/add_command.rb
9
8
  lib/toodledo/command_line/base_command.rb
10
9
  lib/toodledo/command_line/client.rb
11
10
  lib/toodledo/command_line/complete_command.rb
11
+ lib/toodledo/command_line/context_formatter.rb
12
12
  lib/toodledo/command_line/delete_command.rb
13
13
  lib/toodledo/command_line/edit_command.rb
14
+ lib/toodledo/command_line/folder_formatter.rb
15
+ lib/toodledo/command_line/goal_formatter.rb
14
16
  lib/toodledo/command_line/hotlist_command.rb
15
- lib/toodledo/command_line/list_command.rb
16
- lib/toodledo/command_line/main_command.rb
17
+ lib/toodledo/command_line/interactive_command.rb
18
+ lib/toodledo/command_line/list_tasks_command.rb
19
+ lib/toodledo/command_line/list_folders_command.rb
20
+ lib/toodledo/command_line/list_goals_command.rb
21
+ lib/toodledo/command_line/list_tasks_command.rb
17
22
  lib/toodledo/command_line/parser_helper.rb
18
23
  lib/toodledo/command_line/setup_command.rb
24
+ lib/toodledo/command_line/sTdin_command.rb
25
+ lib/toodledo/command_line/task_formatter.rb
19
26
  lib/toodledo/context.rb
20
27
  lib/toodledo/folder.rb
21
28
  lib/toodledo/goal.rb
29
+ lib/toodledo/invalid_configuration_error.rb
22
30
  lib/toodledo/item_not_found_error.rb
23
31
  lib/toodledo/priority.rb
24
32
  lib/toodledo/repeat.rb
25
33
  lib/toodledo/server_error.rb
26
34
  lib/toodledo/session.rb
27
35
  lib/toodledo/task.rb
36
+ test/client_test.rb
28
37
  test/parser_helper_test.rb
29
38
  test/session_test.rb
30
39
  test/toodledo_functional_test.rb
data/README.txt CHANGED
@@ -13,8 +13,9 @@ The client allows you to work with Toodledo from the command line. It will
13
13
  work in either interactive or command line mode.
14
14
 
15
15
  You can also use the client in your shell scripts, or use the API directly
16
- as part of a web application. Want an RSS feed? Want to have the Mac read
17
- out your top priority? It can all happen.
16
+ as part of a web application. Custom private RSS feed? Want to have the Mac
17
+ read out your top priority? Input tasks through Quicksilver? Print out
18
+ tasks with a BetaBrite? It can all happen.
18
19
 
19
20
  == FEATURES/PROBLEMS:
20
21
 
@@ -25,43 +26,50 @@ out your top priority? It can all happen.
25
26
  * Easy configuration and automation (Quicksilver / Scripts / Automator)
26
27
 
27
28
  == SYNOPSIS:
29
+
30
+ === SETUP:
31
+
32
+ The first thing you should do is open up your browser and go to:
33
+
34
+ http://www.toodledo.com/info/api_doc.php
35
+
36
+ and retrieve your userid. You will need this for setup.
37
+
38
+ Then, install toodledo. This is either 'gem install toodledo' or
39
+ 'sudo gem install toodledo' depending on your platform.
40
+
41
+ Then, type 'toodledo setup' and enter your userid and password in
42
+ the spaces provided. Then save the file, and you're good to go.
43
+
44
+ === COMMAND LINE:
28
45
 
29
- Toodledo has a particularly rich model of a task, and allows full GTD
30
- type state to be attached to them. The client syntax for the client
31
- is as follows:
46
+ You can add tasks. The simplest form is here:
47
+
48
+ toodledo add 'This is a test'
49
+
50
+ But tasks don't have to be simple. Toodledo has a particularly rich model of
51
+ a task, and allows full GTD type state to be attached to them. The syntax
52
+ for the client is as follows:
32
53
 
33
54
  *Folder
34
55
  @Context
35
- $Goal
56
+ ^Goal
57
+ !Priority
36
58
 
37
- You can encase the symbol with square brackets if there is a space
38
- involved:
59
+ You can encase the symbol with square brackets if there is a space involved:
39
60
 
40
61
  *[Blue Sky]
41
62
  @[Someday / Maybe]
42
- $[Write Toodledo Ruby API]
63
+ ^[Write Toodledo Ruby API]
64
+ !top
43
65
 
44
66
  Let's use the command line client to list only the tasks you have in the office:
45
67
 
46
68
  toodledo list '@Office *Action'
47
69
 
48
- In interactive mode, the client will also allow you to set up filters. If you
49
- type 'folder', 'context' or 'goal' with an argument, then only tasks that match
50
- those criteria will be displayed. That is, if you do this:
51
-
52
- context Office
53
- folder Action
54
- list
55
-
56
- Then it produces the same results as the previous list command.
57
-
58
- You can add tasks. The simplest form is here:
59
-
60
- toodledo add 'This is a test'
61
-
62
70
  Now let's add a task with several symbols:
63
71
 
64
- toodledo add '*Action @Programming $[Write Toodledo Ruby API] Write documentation'
72
+ toodledo add '*Action @Programming ^[Write Toodledo Ruby API] Write docs'
65
73
 
66
74
  You can also edit tasks, using the task id. This sets the folder to Someday:
67
75
 
@@ -72,7 +80,28 @@ And finally you can complete or delete tasks, again using the task id.
72
80
  toodledo complete 15934131
73
81
  toodledo delete 15934131
74
82
 
75
- If you want to write your own scripts, working with Toodledo is very
83
+ === INTERACTIVE MODE:
84
+
85
+ Toodledo also comes with an interactive mode that is used if no arguments are
86
+ found:
87
+
88
+ toodledo
89
+ > add This is a test
90
+
91
+ You can type help at the prompt for a complete list of commands. The client
92
+ makes for a nice way to enter in tasks as you think of them.
93
+
94
+ The client will also allow you to set up filters. Filters are added with
95
+ the symbols, so in interactive mode
96
+
97
+ filter @Office *Action
98
+ list
99
+
100
+ Then it produces the same results as:
101
+
102
+ toodledo list '@Office *Action'
103
+
104
+ Finally, if you want to write your own scripts, working with Toodledo is very
76
105
  simple, since it will use the YAML config file:
77
106
 
78
107
  require 'rubygems'
@@ -102,7 +131,7 @@ this instead:
102
131
 
103
132
  * sudo gem install toodledo
104
133
  * toodledo setup (sets up the YAML file with your credentials)
105
- * toodledo interactive
134
+ * toodledo
106
135
 
107
136
  == LICENSE:
108
137
  GNU LESSER GENERAL PUBLIC LICENSE
data/Rakefile CHANGED
@@ -14,6 +14,7 @@ Hoe.new('toodledo', Toodledo::VERSION) do |p|
14
14
  p.url = 'http://rubyforge.org/projects/toodledo'
15
15
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
16
16
  p.rsync_args << ' --exclude=statsvn/'
17
+ p.test_globs = ["test/**/*_test.rb"]
17
18
  p.extra_deps = ['cmdparse', 'highline']
18
19
  end
19
20
 
data/lib/toodledo.rb CHANGED
@@ -5,7 +5,7 @@
5
5
  module Toodledo
6
6
 
7
7
  # Required for gem
8
- VERSION = '1.0.2'
8
+ VERSION = '1.1.0'
9
9
 
10
10
  # Returns the configuration object.
11
11
  def self.get_config()
@@ -55,6 +55,7 @@ end
55
55
 
56
56
  require 'toodledo/server_error'
57
57
  require 'toodledo/item_not_found_error'
58
+ require 'toodledo/invalid_configuration_error'
58
59
  require 'toodledo/task'
59
60
  require 'toodledo/context'
60
61
  require 'toodledo/goal'
@@ -1,10 +1,111 @@
1
1
 
2
2
  module Toodledo
3
3
  module CommandLine
4
- class AddCommand < BaseCommand
4
+
5
+ #
6
+ # Adds commands
7
+ #
8
+ class AddCommand < CmdParse::Command
9
+ def initialize(client)
10
+ super('add', true)
11
+ self.short_desc = 'Adds an object'
12
+
13
+ self.add_command(AddTaskCommand.new(client), true)
14
+ #self.add_command(AddFolderCommand.new(client))
15
+ #self.add_command(AddGoalCommand.new(client))
16
+ #self.add_command(AddContextCommand.new(client))
17
+
18
+ end
19
+
20
+ def execute(args)
21
+ puts "hello world!"
22
+ end
23
+ end
24
+
25
+ #
26
+ # Adds a folder from the command
27
+ #
28
+ class AddFolderCommand < BaseCommand
29
+
30
+ include Toodledo::CommandLine::ParserHelper
31
+
32
+ def initialize(client)
33
+ super(client, 'folder', false)
34
+ self.short_desc = "Add a folder"
35
+ self.description = "Adds a folder to Toodledo"
36
+ end
37
+
38
+ def execute(args)
39
+ return if (args == nil)
40
+ Toodledo.begin(client.logger) do |session|
41
+ line = args.join(' ')
42
+ client.add_folder(session, line)
43
+ end
44
+
45
+ return 0
46
+ rescue ItemNotFoundError => e
47
+ puts e.message
48
+ return -1
49
+ end
50
+ end
51
+
52
+ #
53
+ # Adds a goal from the command line
54
+ #
55
+ class AddGoalCommand < BaseCommand
5
56
 
6
57
  include Toodledo::CommandLine::ParserHelper
7
58
 
59
+ def initialize(client)
60
+ super(client, 'goal', false)
61
+ self.short_desc = "Add a goal"
62
+ self.description = "Adds a goal to Toodledo"
63
+ end
64
+
65
+ def execute(args)
66
+ return if (args == nil)
67
+ Toodledo.begin(client.logger) do |session|
68
+ line = args.join(' ')
69
+ client.add_goal(session, line)
70
+ end
71
+
72
+ return 0
73
+ rescue ItemNotFoundError => e
74
+ puts e.message
75
+ return -1
76
+ end
77
+ end
78
+
79
+ #
80
+ # Adds a context from the command line
81
+ #
82
+ class AddContextCommand < BaseCommand
83
+
84
+ def initialize(client)
85
+ super(client, 'context', false)
86
+ self.short_desc = "Adds context"
87
+ self.description = "Adds a context to Toodledo"
88
+ end
89
+
90
+ def execute(args)
91
+ return if (args == nil)
92
+ Toodledo.begin(client.logger) do |session|
93
+ line = args.join(' ')
94
+ client.add_context(session, line)
95
+ end
96
+
97
+ return 0
98
+ rescue ItemNotFoundError => e
99
+ puts e.message
100
+ return -1
101
+ end
102
+ end
103
+
104
+ #
105
+ # Adds a task from the command line.
106
+ #
107
+ class AddTaskCommand < BaseCommand
108
+
8
109
  def initialize(client)
9
110
  super(client, 'add', false)
10
111
  self.short_desc = "Add a task"
@@ -8,14 +8,32 @@ require 'yaml'
8
8
  require 'toodledo'
9
9
  require 'toodledo/command_line/parser_helper'
10
10
  require 'toodledo/command_line/base_command'
11
- require 'toodledo/command_line/main_command'
11
+ require 'toodledo/command_line/interactive_command'
12
+ require 'toodledo/command_line/stdin_command'
13
+ require 'toodledo/command_line/setup_command'
14
+
15
+ # CREATE
12
16
  require 'toodledo/command_line/add_command'
13
- require 'toodledo/command_line/list_command'
14
- require 'toodledo/command_line/edit_command'
17
+
18
+ # READ
15
19
  require 'toodledo/command_line/hotlist_command'
20
+ require 'toodledo/command_line/list_tasks_command'
21
+ require 'toodledo/command_line/list_folders_command'
22
+ require 'toodledo/command_line/list_contexts_command'
23
+ require 'toodledo/command_line/list_goals_command'
24
+
25
+ # UPDATE
26
+ require 'toodledo/command_line/edit_command'
16
27
  require 'toodledo/command_line/complete_command'
28
+
29
+ # DELETE
17
30
  require 'toodledo/command_line/delete_command'
18
- require 'toodledo/command_line/setup_command'
31
+
32
+ # FORMATTERS
33
+ require 'toodledo/command_line/task_formatter'
34
+ require 'toodledo/command_line/context_formatter'
35
+ require 'toodledo/command_line/folder_formatter'
36
+ require 'toodledo/command_line/goal_formatter'
19
37
 
20
38
  module Toodledo
21
39
  module CommandLine
@@ -50,6 +68,12 @@ module Toodledo
50
68
 
51
69
  @userconfig = test(?e, userconfig) ? IO::read(userconfig) : CONFIG
52
70
  @userconfig = YAML.load(@userconfig).merge(opts)
71
+ @formatters = {
72
+ :task => TaskFormatter.new,
73
+ :goal => GoalFormatter.new,
74
+ :context => ContextFormatter.new,
75
+ :folder => FolderFormatter.new
76
+ }
53
77
  end
54
78
 
55
79
  def debug?
@@ -91,113 +115,74 @@ module Toodledo
91
115
  user_id = session.user_id
92
116
  proxy = session.proxy
93
117
 
94
- puts "base_url = #{base_url}"
95
- puts "user_id = #{user_id}"
96
- puts "proxy = #{proxy.inspect}"
118
+ print "base_url = #{base_url}"
119
+ print "user_id = #{user_id}"
120
+ print "proxy = #{proxy.inspect}"
97
121
  end
98
122
 
99
123
  # Sets the context filter. Subsequent calls to show tasks
100
124
  # will only show tasks that have this context.
101
125
  #
102
- def set_context_filter(session, input)
103
- if (input == nil)
104
- input = ask("Selected context? > ") { |q| q.readline = true }
105
- end
126
+ def set_filter(session, input)
127
+ logger.debug("set_filter(#{input})")
106
128
 
107
129
  input.strip!
108
130
 
109
- if (input =~ /^(\d+)$/)
110
- context = session.get_context_by_id(input)
111
- if (context != nil)
112
- @filters[:context] = context.name
113
- else
114
- @filters[:context] = input
115
- end
116
- else
117
- @filters[:context] = input
118
- end
119
- end
120
-
121
- #
122
- # Sets the folder filter. Subsequent calls to show tasks
123
- # will only show tasks that are in this folder.
124
- #
125
- def set_folder_filter(session, input)
126
- if (input == nil)
127
- input = ask("Selected folder? > ") { |q| q.readline = true }
131
+ context = parse_context(input)
132
+ if (context != nil)
133
+ c = session.get_context_by_name(context)
134
+ if (c == nil)
135
+ print "No such context: #{context}"
136
+ return
137
+ end
138
+ @filters[:context] = c
128
139
  end
129
140
 
130
- input.strip!
131
-
132
- if (input =~ /^(\d+)$/)
133
- folder = session.get_folder_by_id(input)
134
- if (folder != nil)
135
- @filters[:folder] = folder.name
136
- else
137
- @filters[:folder] = input
138
- end
139
- else
140
- @filters[:folder] = input
141
- end
142
- end
143
-
144
- # Sets the goal filter. Subsequent calls to show tasks
145
- # will only show tasks that have this goal.
146
- #
147
- def set_goal_filter(session, input)
148
- if (input == nil)
149
- input = ask("Selected goal? > ") { |q| q.readline = true }
141
+ goal = parse_goal(input)
142
+ if (goal != nil)
143
+ g = session.get_goal_by_name(goal)
144
+ if (g == nil)
145
+ print "No such goal: #{goal}"
146
+ return
147
+ end
148
+ @filters[:goal] = g
150
149
  end
151
-
152
- input.strip!
153
150
 
154
- if (input =~ /^(\d+)$/)
155
- goal = session.get_goal_by_id(input)
156
- if (goal != nil)
157
- @filters[:goal] = goal.name
158
- else
159
- @filters[:goal] = input
160
- end
161
- else
162
- @filters[:goal] = input
163
- end
164
- end
165
-
166
- # Sets the context filter. Subsequent calls to show tasks
167
- # will only show tasks that have this context.
168
- #
169
- def set_priority_filter(session, input)
170
- if (input == nil)
171
- input = ask("Selected priority? > ") { |q| q.readline = true }
151
+ folder = parse_folder(input)
152
+ if (folder != nil)
153
+ f = session.get_folder_by_name(folder)
154
+ if (f == nil)
155
+ print "No such folder: #{folder}"
156
+ end
157
+ @filters[:folder] = f
172
158
  end
173
159
 
174
- input.strip!
175
-
176
- if (input =~ /^(\d+)$/)
177
- input = input.to_i
178
- else
179
- input = input.downcase
180
- input = Priority.convert(input)
160
+ priority = parse_priority(input)
161
+ if (priority != nil)
162
+ @filters[:priority] = priority
181
163
  end
182
164
 
183
- if (! Priority.valid?(input))
184
- puts "Unknown priority \"#{input}\" -- (valid range is between -1..3)"
165
+ if (logger)
166
+ logger.debug("@filters = #{@filters.inspect}")
185
167
  end
186
-
187
- @filters[:priority] = input
188
- end
168
+ end
189
169
 
190
170
  #
191
171
  # Shows all the filters.
192
172
  #
193
173
  def list_filters()
194
- if (@filters.empty?)
195
- puts "No filters."
174
+ if (@filters == nil || @filters.empty?)
175
+ print "No filters."
196
176
  return
197
177
  end
198
178
 
199
179
  @filters.each do |k, v|
200
- puts "#{k}: #{v}\n"
180
+ if (v.respond_to? :name)
181
+ name = v.name
182
+ else
183
+ name = v
184
+ end
185
+ print "#{k}: #{name}\n"
201
186
  end
202
187
  end
203
188
 
@@ -206,54 +191,9 @@ module Toodledo
206
191
  #
207
192
  def unfilter()
208
193
  @filters = {}
209
- puts "Filters cleared.\n"
210
- end
211
-
212
- #
213
- # Shows all the folders for this user.
214
- #
215
- def folders(session)
216
- my_folders = session.get_folders()
217
-
218
- my_folders.sort! do |a, b|
219
- a.name <=> b.name
220
- end
221
-
222
- for folder in my_folders
223
- puts "<#{folder.server_id}> -- #{folder}\n"
224
- end
225
- end
226
-
227
- #
228
- # Shows all the contexts for this user.
229
- #
230
- def contexts(session)
231
- my_contexts = session.get_contexts()
232
-
233
- my_contexts.sort! do |a, b|
234
- a.name <=> b.name
235
- end
236
-
237
- for context in my_contexts
238
- puts "<#{context.server_id}> -- #{context}\n"
239
- end
240
- end
241
-
242
- #
243
- # Shows all the goals for this user.
244
- #
245
- def goals(session)
246
- my_goals = session.get_goals()
247
-
248
- my_goals.sort! do |a, b|
249
- a.level <=> b.level
250
- end
251
-
252
- for goal in my_goals
253
- puts "<#{goal.server_id}> -- #{goal}\n"
254
- end
194
+ print "Filters cleared.\n"
255
195
  end
256
-
196
+
257
197
  #
258
198
  # Displays the 'hotlist' of tasks. This shows all the uncompleted items with
259
199
  # priority set to 3 or 2. There's no facility in the API for this, so we have
@@ -269,6 +209,7 @@ module Toodledo
269
209
  context = parse_context(input)
270
210
  folder = parse_folder(input)
271
211
  goal = parse_goal(input)
212
+ priority = parse_priority(input)
272
213
 
273
214
  params = { :notcomp => true }
274
215
 
@@ -285,6 +226,10 @@ module Toodledo
285
226
  params.merge!({ :goal => goal })
286
227
  end
287
228
 
229
+ if (priority != nil)
230
+ params.merge!({ :priority => priority })
231
+ end
232
+
288
233
  tasks = session.get_tasks(params)
289
234
 
290
235
  # Highest priority first
@@ -298,7 +243,7 @@ module Toodledo
298
243
 
299
244
  for task in tasks
300
245
  if (task.priority > not_important)
301
- puts "<#{task.server_id}> -- #{task}\n"
246
+ print @formatters[:task].format(task)
302
247
  end
303
248
  end
304
249
  end
@@ -307,6 +252,8 @@ module Toodledo
307
252
  # Lists tasks (subject to any filters that may be present).
308
253
  #
309
254
  def list_tasks(session, input)
255
+ logger.debug("list_tasks(#{input})")
256
+
310
257
  params = { :notcomp => true }
311
258
 
312
259
  params.merge!(@filters)
@@ -315,6 +262,7 @@ module Toodledo
315
262
  context = parse_context(input)
316
263
  folder = parse_folder(input)
317
264
  goal = parse_goal(input)
265
+ priority = parse_priority(input)
318
266
 
319
267
  # If there are, they override what we have set.
320
268
  if (folder != nil)
@@ -329,6 +277,10 @@ module Toodledo
329
277
  params.merge!({ :goal => goal })
330
278
  end
331
279
 
280
+ if (priority != nil)
281
+ params.merge!({ :priority => priority })
282
+ end
283
+
332
284
  tasks = session.get_tasks(params)
333
285
 
334
286
  # Highest priority first
@@ -337,7 +289,66 @@ module Toodledo
337
289
  end
338
290
 
339
291
  for task in tasks
340
- puts "<#{task.server_id}> -- #{task}\n"
292
+ print @formatters[:task].format(task)
293
+ end
294
+ end
295
+
296
+ #
297
+ # Lists the goals. Takes an optional argument of
298
+ # 'short', 'medium' or 'life'.
299
+ #
300
+ def list_goals(session, input)
301
+
302
+ input.strip!
303
+ input.downcase!
304
+
305
+ goals = session.get_goals()
306
+
307
+ goals.sort! do |a, b|
308
+ a.level <=> b.level
309
+ end
310
+
311
+ level_filter = nil
312
+ case input
313
+ when 'short'
314
+ level_filter = Goal::SHORT_LEVEL
315
+ when 'medium'
316
+ level_filter = Goal::MEDIUM_LEVEL
317
+ when 'life'
318
+ level_filter = Goal::LIFE_LEVEL
319
+ end
320
+
321
+ for goal in goals
322
+ if (level_filter != nil && goal.level != level_filter)
323
+ next # skip this goal if it doesn't meet the filter
324
+ end
325
+ print @formatters[:goal].format(goal)
326
+ end
327
+ end
328
+
329
+ #
330
+ # Lists the contexts.
331
+ #
332
+ def list_contexts(session, input)
333
+ params = { }
334
+
335
+ contexts = session.get_contexts()
336
+
337
+ for context in contexts
338
+ print @formatters[:context].format(context)
339
+ end
340
+ end
341
+
342
+ #
343
+ # Lists the folders.
344
+ #
345
+ def list_folders(session, input)
346
+ params = { }
347
+
348
+ folders = session.get_folders()
349
+
350
+ for folder in folders
351
+ print @formatters[:folder].format(folder)
341
352
  end
342
353
  end
343
354
 
@@ -348,7 +359,7 @@ module Toodledo
348
359
  # The order of symbols does not matter, but the title must be the last thing
349
360
  # on the line.
350
361
  #
351
- # add @[Deep Space] *Action $[For Great Justice] Take off every Zig
362
+ # add @[Deep Space] *Action ^[For Great Justice] Take off every Zig
352
363
  #
353
364
  # There is no priority handling in this method. It may be added if there is
354
365
  # demand for it.
@@ -356,9 +367,13 @@ module Toodledo
356
367
  context = parse_context(line)
357
368
  folder = parse_folder(line)
358
369
  goal = parse_goal(line)
370
+ priority = parse_priority(line)
359
371
  title = parse_remainder(line)
360
372
 
361
- params = { :priority => Priority::LOW }
373
+ params = {}
374
+ if (priority != nil)
375
+ params.merge!({ :priority => priority })
376
+ end
362
377
 
363
378
  if (folder != nil)
364
379
  params.merge!({ :folder => folder })
@@ -379,7 +394,61 @@ module Toodledo
379
394
 
380
395
  task_id = session.add_task(title, params)
381
396
 
382
- puts "Task #{task_id} added."
397
+ print "Task #{task_id} added."
398
+ end
399
+
400
+ def add_context(session, input)
401
+
402
+ title = input.strip
403
+
404
+ context_id = session.add_context(title)
405
+
406
+ print "Context #{context_id} added."
407
+ end
408
+
409
+ def add_goal(session, input)
410
+ input.strip!
411
+
412
+ # Assume that a goal is short, medium or life, and
413
+ # don't stick a symbol on it.
414
+ level = parse_level(input)
415
+ if (level == nil)
416
+ level = Toodledo::Goal::SHORT_LEVEL
417
+ else
418
+ input = clean(LEVEL_REGEXP, input)
419
+ input.strip!
420
+ end
421
+
422
+ goal_id = session.add_goal(input, level)
423
+
424
+ print "Goal #{goal_id} added."
425
+ end
426
+
427
+ def add_folder(session, input)
428
+
429
+ title = input.strip
430
+
431
+ folder_id = session.add_folder(title)
432
+
433
+ print "Folder #{folder_id} added."
434
+ end
435
+
436
+ #
437
+ # Archives a folder.
438
+ #
439
+ def archive_folder(session, line)
440
+
441
+ line.strip!
442
+
443
+ folder_id = line
444
+ params = { :archived => 1 }
445
+ session.edit_folder(folder_id, params)
446
+
447
+ print "Folder #{folder_id} archived."
448
+ end
449
+
450
+ def archive_goal(session, line)
451
+ # Not implemented! No way to edit a goal.
383
452
  end
384
453
 
385
454
  #
@@ -393,6 +462,7 @@ module Toodledo
393
462
  context = parse_context(input)
394
463
  folder = parse_folder(input)
395
464
  goal = parse_goal(input)
465
+ priority = parse_priority(input)
396
466
  task_id = parse_remainder(input)
397
467
 
398
468
  logger.debug("edit_task: task_id = #{task_id}")
@@ -417,9 +487,13 @@ module Toodledo
417
487
  params.merge!({ :goal => goal })
418
488
  end
419
489
 
490
+ if (priority != nil)
491
+ params.merge!({ :priority => priority })
492
+ end
493
+
420
494
  session.edit_task(task_id, params)
421
495
 
422
- puts "Task #{task_id} edited."
496
+ print "Task #{task_id} edited."
423
497
  end
424
498
 
425
499
  # Masks the task as completed. Uses a task id as argument.
@@ -437,12 +511,13 @@ module Toodledo
437
511
 
438
512
  params = { :completed => 1 }
439
513
  if (session.edit_task(task_id, params))
440
- puts "Task #{task_id} completed."
514
+ print "Task #{task_id} completed."
441
515
  else
442
- puts "Task #{task_id} could not be completed!"
516
+ print "Task #{task_id} could not be completed!"
443
517
  end
444
518
  end
445
519
 
520
+
446
521
  # Deletes a task, using the task id.
447
522
  #
448
523
  # delete 123
@@ -457,12 +532,178 @@ module Toodledo
457
532
  task_id.strip!
458
533
 
459
534
  if (session.delete_task(task_id))
460
- puts "Task #{task_id} deleted."
535
+ print "Task #{task_id} deleted."
536
+ else
537
+ print "Task #{task_id} could not be deleted!"
538
+ end
539
+ end
540
+
541
+ def delete_context(session, line)
542
+ id = line
543
+
544
+ id.strip!
545
+
546
+ if (session.delete_context(id))
547
+ print "Context #{id} deleted."
548
+ else
549
+ print "Context #{id} could not be deleted!"
550
+ end
551
+ end
552
+
553
+ def delete_goal(session, line)
554
+ id = line
555
+
556
+ id.strip!
557
+
558
+ if (session.delete_goal(id))
559
+ print "Goal #{id} deleted."
560
+ else
561
+ print "Goal #{id} could not be deleted!"
562
+ end
563
+ end
564
+
565
+ def delete_folder(session, line)
566
+ id = line
567
+
568
+ id.strip!
569
+
570
+ if (session.delete_folder(id))
571
+ print "Folder #{id} deleted."
572
+ else
573
+ print "Folder #{id} could not be deleted!"
574
+ end
575
+ end
576
+
577
+ # Prints out a single line.
578
+ def print(line = nil)
579
+ if (line == nil)
580
+ puts
461
581
  else
462
- puts "Task #{task_id} could not be deleted!"
582
+ puts line
463
583
  end
464
584
  end
465
585
 
586
+ #
587
+ # Displays the help message.
588
+ #
589
+ def help()
590
+ print "hotlist Shows the hotlist"
591
+ print "folders Shows all folders"
592
+ print "goals Shows all goals"
593
+ print "contexts Shows all contexts"
594
+ print "tasks Shows tasks ('tasks *Action @Home')"
595
+ print
596
+ print "add Adds task ('add *Action @Home Eat breakfast')"
597
+ print " folder Adds a folder ('add folder MyFolder')"
598
+ print " context Adds a context ('add context MyContext')"
599
+ print " goal Adds a goal ('add goal MyGoal')"
600
+ print "edit Edits a task ('edit *Action 1134')"
601
+ print "complete Completes a task ('complete 1234')"
602
+ print "delete Deletes a task ('delete 1134')"
603
+ print " folder Deletes a folder ('delete folder 1')"
604
+ print " context Deletes a context ('delete context 2')"
605
+ print " goal Deletes a goal ('delete goal 3')"
606
+ print
607
+ print "archive Archives a folder ('archive 1234')"
608
+ print "filter Defines filters ('filter *Action @Someday')"
609
+ print "unfilter Removes all filters"
610
+ print "filters Displays the list of filters"
611
+ print
612
+ print "help or ? Displays this help message"
613
+ print "quit or exit Leaves the application"
614
+ end
615
+
616
+ def clean(regexp, input)
617
+ return input.sub(regexp, '')
618
+ end
619
+
620
+ def execute_command(session, input)
621
+ case input
622
+ when /^help/, /^\s*\?/
623
+ help()
624
+
625
+ when /^add/
626
+ line = clean(/^add/, input)
627
+ line.strip!
628
+ case line
629
+ when /folder/
630
+ add_folder(session, clean(/folder/, line))
631
+ when /context/
632
+ add_context(session, clean(/context/, line))
633
+ when /goal/
634
+ add_goal(session, clean(/goal/, line))
635
+ else
636
+ add_task(session, line)
637
+ end
638
+
639
+ when /^edit/
640
+ line = clean(/^edit/, input)
641
+ edit_task(session, line)
642
+
643
+ when /^delete/
644
+ line = clean(/^delete/, input)
645
+ line.strip!
646
+ case line
647
+ when /folder/
648
+ delete_folder(session, clean(/folder/, line))
649
+ when /context/
650
+ delete_context(session, clean(/context/, line))
651
+ when /goal/
652
+ delete_goal(session, clean(/goal/, line))
653
+ else
654
+ delete_task(session, line)
655
+ end
656
+
657
+ when /^archive/
658
+ archive_folder(session, clean(/^archive/, input))
659
+
660
+ when /^hotlist/
661
+ line = clean(/^hotlist/, input)
662
+ hotlist(session, line)
663
+
664
+ when /^complete/
665
+ line = clean(/^complete/, input)
666
+ complete_task(session, line)
667
+
668
+ when /^tasks/
669
+ line = clean(/^(tasks)/, input)
670
+ list_tasks(session, line)
671
+
672
+ when /^folders/
673
+ line = clean(/^folders/, input)
674
+ list_folders(session,line)
675
+
676
+ when /^goals/
677
+ line = clean(/^goals/, input)
678
+ list_goals(session,line)
679
+
680
+ when /^contexts/
681
+ line = clean(/^contexts/, input)
682
+ list_contexts(session,line)
683
+
684
+ when /^filters/
685
+ list_filters()
686
+
687
+ when /^filter/
688
+ line = clean(/^filter/, input)
689
+ set_filter(session, line)
690
+
691
+ when /^config/
692
+ show_config(session)
693
+
694
+ when /^unfilter/
695
+ unfilter()
696
+
697
+ when /debug/
698
+ self.debug = ! self.debug?
699
+
700
+ when /^quit/, /^exit/
701
+ exit 0
702
+ else
703
+ print "'#{input}' is not a command: type help for a list"
704
+ end
705
+ end
706
+
466
707
  #
467
708
  # Runs the client main command. This is what gets run from 'toodledo'.
468
709
  # Ironically doesn't do much except for set up the commands and parse
@@ -485,25 +726,40 @@ module Toodledo
485
726
  opt.separator "Global options:"
486
727
  opt.on("--debug", "Print debugging information") {|t| self.debug = true }
487
728
  end
729
+
730
+ # this is the default command if we don't receive any options.
731
+ cmd.add_command(InteractiveCommand.new(self), true)
488
732
 
489
- cmd.add_command(MainCommand.new(self))
733
+ cmd.add_command(StdinCommand.new(self))
734
+
735
+ cmd.add_command(AddTaskCommand.new(self))
736
+
737
+ cmd.add_command(ListTasksCommand.new(self))
738
+ cmd.add_command(ListFoldersCommand.new(self))
739
+ cmd.add_command(ListGoalsCommand.new(self))
740
+ cmd.add_command(ListContextsCommand.new(self))
490
741
 
491
- cmd.add_command(AddCommand.new(self))
492
- cmd.add_command(ListCommand.new(self))
493
742
  cmd.add_command(EditCommand.new(self))
494
743
  cmd.add_command(CompleteCommand.new(self))
495
- cmd.add_command(DeleteCommand.new(self))
744
+ cmd.add_command(DeleteTaskCommand.new(self))
496
745
  cmd.add_command(HotlistCommand.new(self))
497
746
  cmd.add_command(SetupCommand.new(self))
498
-
499
- # this is the default command if we don't receive any options.
500
- cmd.add_command(CmdParse::HelpCommand.new, true)
747
+
748
+ cmd.add_command(CmdParse::HelpCommand.new)
501
749
  cmd.add_command(CmdParse::VersionCommand.new)
502
750
 
503
751
  cmd.parse
504
752
 
505
753
  # Return a good exit status.
506
754
  return 0
755
+ rescue InvalidConfigurationError => e
756
+ logger.debug(e)
757
+ print "The client is missing (or cannot use) the user id or password it needs to connect."
758
+ print "Run 'toodledo setup' and save the file to fix this."
759
+ return -1
760
+ rescue ServerError => e
761
+ print "The server returned a fatal error: #{e.message}"
762
+ return -1
507
763
  end
508
764
  end #class
509
765
  end