toodledo 1.0.2 → 1.1.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/History.txt +16 -0
- data/Manifest.txt +12 -3
- data/README.txt +55 -26
- data/Rakefile +1 -0
- data/lib/toodledo.rb +2 -1
- data/lib/toodledo/command_line/add_command.rb +102 -1
- data/lib/toodledo/command_line/client.rb +405 -149
- data/lib/toodledo/command_line/context_formatter.rb +9 -0
- data/lib/toodledo/command_line/delete_command.rb +75 -1
- data/lib/toodledo/command_line/folder_formatter.rb +9 -0
- data/lib/toodledo/command_line/goal_formatter.rb +26 -0
- data/lib/toodledo/command_line/interactive_command.rb +43 -0
- data/lib/toodledo/command_line/list_folders_command.rb +32 -0
- data/lib/toodledo/command_line/list_goals_command.rb +31 -0
- data/lib/toodledo/command_line/{list_command.rb → list_tasks_command.rb} +9 -2
- data/lib/toodledo/command_line/parser_helper.rb +65 -8
- data/lib/toodledo/command_line/sTdin_command.rb +31 -0
- data/lib/toodledo/command_line/task_formatter.rb +102 -0
- data/lib/toodledo/context.rb +1 -2
- data/lib/toodledo/folder.rb +2 -6
- data/lib/toodledo/goal.rb +2 -8
- data/lib/toodledo/invalid_configuration_error.rb +14 -0
- data/lib/toodledo/priority.rb +0 -17
- data/lib/toodledo/session.rb +22 -15
- data/lib/toodledo/task.rb +1 -82
- data/test/client_test.rb +192 -0
- data/test/parser_helper_test.rb +74 -6
- data/test/session_test.rb +2 -2
- data/test/toodledo_functional_test.rb +2 -2
- metadata +19 -8
- data/CHANGELOG +0 -1
- data/lib/toodledo/command_line/main_command.rb +0 -153
@@ -1,7 +1,79 @@
|
|
1
1
|
|
2
2
|
module Toodledo
|
3
3
|
module CommandLine
|
4
|
-
|
4
|
+
|
5
|
+
class DeleteCommand < CmdParse::Command
|
6
|
+
def initialize(client)
|
7
|
+
super('delete', true)
|
8
|
+
self.short_desc = "Deletes a context"
|
9
|
+
|
10
|
+
self.add_command(DeleteContextCommand.new(client))
|
11
|
+
self.add_command(DeleteGoalCommand.new(client))
|
12
|
+
self.add_command(DeleteFolderCommand.new(client))
|
13
|
+
|
14
|
+
self.add_command(DeleteTaskCommand.new(client), true)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class DeleteContextCommand < BaseCommand
|
20
|
+
def initialize(client)
|
21
|
+
super(client, 'context', false)
|
22
|
+
self.short_desc = "Deletes a context"
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute(args)
|
26
|
+
Toodledo.begin(client.logger) do |session|
|
27
|
+
line = args.join(' ')
|
28
|
+
client.delete_context(session, line)
|
29
|
+
end
|
30
|
+
|
31
|
+
return 0
|
32
|
+
rescue ItemNotFoundError => e
|
33
|
+
puts e.message
|
34
|
+
return -1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class DeleteFolderCommand < BaseCommand
|
39
|
+
def initialize(client)
|
40
|
+
super(client, 'folder', false)
|
41
|
+
self.short_desc = "Deletes a folder"
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute(args)
|
45
|
+
Toodledo.begin(client.logger) do |session|
|
46
|
+
line = args.join(' ')
|
47
|
+
client.delete_folder(session, line)
|
48
|
+
end
|
49
|
+
|
50
|
+
return 0
|
51
|
+
rescue ItemNotFoundError => e
|
52
|
+
puts e.message
|
53
|
+
return -1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class DeleteGoalCommand < BaseCommand
|
58
|
+
def initialize(client)
|
59
|
+
super(client, 'goal', false)
|
60
|
+
self.short_desc = "Deletes a goal"
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute(args)
|
64
|
+
Toodledo.begin(client.logger) do |session|
|
65
|
+
line = args.join(' ')
|
66
|
+
client.delete_goal(session, line)
|
67
|
+
end
|
68
|
+
|
69
|
+
return 0
|
70
|
+
rescue ItemNotFoundError => e
|
71
|
+
puts e.message
|
72
|
+
return -1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class DeleteTaskCommand < BaseCommand
|
5
77
|
def initialize(client)
|
6
78
|
super(client, 'delete', false)
|
7
79
|
self.short_desc = "Deletes a task"
|
@@ -20,5 +92,7 @@ module Toodledo
|
|
20
92
|
return -1
|
21
93
|
end
|
22
94
|
end
|
95
|
+
|
96
|
+
|
23
97
|
end
|
24
98
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'toodledo/goal'
|
2
|
+
|
3
|
+
module Toodledo
|
4
|
+
module CommandLine
|
5
|
+
class GoalFormatter
|
6
|
+
def format(goal)
|
7
|
+
msg = "<#{goal.server_id}> -- #{readable_level(goal.level)} ^[#{goal.name}]"
|
8
|
+
if (goal.contributes != Goal::NO_GOAL)
|
9
|
+
msg += " (Contributes to: ^[#{goal.contributes.name}])"
|
10
|
+
end
|
11
|
+
return msg
|
12
|
+
end
|
13
|
+
|
14
|
+
def readable_level(level)
|
15
|
+
case level
|
16
|
+
when Goal::LIFE_LEVEL
|
17
|
+
return 'life'
|
18
|
+
when Goal::MEDIUM_LEVEL
|
19
|
+
return 'medium'
|
20
|
+
when Goal::SHORT_LEVEL
|
21
|
+
return 'short'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'toodledo/command_line/parser_helper'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Toodledo
|
5
|
+
|
6
|
+
module CommandLine
|
7
|
+
# Runs the interactive client.
|
8
|
+
class InteractiveCommand < BaseCommand
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
super(client, 'interactive', false)
|
12
|
+
self.short_desc = "Interactive client"
|
13
|
+
self.description = "The interactive command line client."
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(args)
|
17
|
+
Toodledo.begin(client.logger) do |session|
|
18
|
+
command_loop(session)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def command_loop(session)
|
23
|
+
loop do
|
24
|
+
begin
|
25
|
+
input = ask("> ") do |q|
|
26
|
+
q.readline = true
|
27
|
+
end
|
28
|
+
|
29
|
+
input.strip!
|
30
|
+
|
31
|
+
client.execute_command(session, input)
|
32
|
+
rescue Toodledo::ItemNotFoundError => infe
|
33
|
+
puts "Item not found: #{infe}"
|
34
|
+
rescue Toodledo::ServerError => se
|
35
|
+
puts "Server Error: #{se}"
|
36
|
+
rescue RuntimeError => re
|
37
|
+
puts "Error: #{re}"
|
38
|
+
end
|
39
|
+
end # loop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
module Toodledo
|
7
|
+
module CommandLine
|
8
|
+
|
9
|
+
|
10
|
+
#
|
11
|
+
# List Folders
|
12
|
+
#
|
13
|
+
class ListFoldersCommand < BaseCommand
|
14
|
+
def initialize(client)
|
15
|
+
super(client, 'folders', false)
|
16
|
+
self.short_desc = "List folders"
|
17
|
+
self.description = "Lists the folders in Toodledo."
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute(args)
|
21
|
+
|
22
|
+
Toodledo.begin(client.logger) do |session|
|
23
|
+
line = args.join(' ')
|
24
|
+
return client.list_folders(session, line)
|
25
|
+
end
|
26
|
+
|
27
|
+
return 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
module Toodledo
|
7
|
+
module CommandLine
|
8
|
+
|
9
|
+
#
|
10
|
+
# List Goals
|
11
|
+
#
|
12
|
+
class ListGoalsCommand < BaseCommand
|
13
|
+
def initialize(client)
|
14
|
+
super(client, 'goals', false)
|
15
|
+
self.short_desc = "List goals"
|
16
|
+
self.description = "Lists the goals in Toodledo."
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(args)
|
20
|
+
|
21
|
+
Toodledo.begin(client.logger) do |session|
|
22
|
+
line = args.join(' ')
|
23
|
+
return client.list_goals(session, line)
|
24
|
+
end
|
25
|
+
|
26
|
+
return 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -1,14 +1,19 @@
|
|
1
1
|
|
2
2
|
module Toodledo
|
3
3
|
module CommandLine
|
4
|
-
|
4
|
+
|
5
|
+
#
|
6
|
+
# Lists the tasks.
|
7
|
+
#
|
8
|
+
class ListTasksCommand < BaseCommand
|
5
9
|
def initialize(client)
|
6
|
-
super(client, '
|
10
|
+
super(client, 'tasks', false)
|
7
11
|
self.short_desc = "List tasks"
|
8
12
|
self.description = "Lists the tasks in Toodledo."
|
9
13
|
end
|
10
14
|
|
11
15
|
def execute(args)
|
16
|
+
|
12
17
|
Toodledo.begin(client.logger) do |session|
|
13
18
|
line = args.join(' ')
|
14
19
|
return client.list_tasks(session, line)
|
@@ -17,5 +22,7 @@ module Toodledo
|
|
17
22
|
return 0
|
18
23
|
end
|
19
24
|
end
|
25
|
+
|
26
|
+
|
20
27
|
end
|
21
28
|
end
|
@@ -1,18 +1,32 @@
|
|
1
|
+
require 'toodledo/priority'
|
1
2
|
|
2
3
|
module Toodledo
|
3
4
|
|
4
5
|
module CommandLine
|
5
6
|
|
6
|
-
#
|
7
|
+
#
|
7
8
|
# Methods to parse a string and identify it as a Toodledo symbol.
|
8
|
-
#
|
9
|
+
#
|
9
10
|
module ParserHelper
|
10
|
-
|
11
|
+
|
11
12
|
FOLDER_REGEXP = /\*((\w+)|\[(.*?)\])/
|
12
13
|
|
13
|
-
GOAL_REGEXP =
|
14
|
+
GOAL_REGEXP = /\^((\w+)|\[(.*?)\])/
|
14
15
|
|
15
16
|
CONTEXT_REGEXP = /\@((\w+)|\[(.*?)\])/
|
17
|
+
|
18
|
+
PRIORITY_REGEXP = /!(top|high|medium|low|negative)/
|
19
|
+
|
20
|
+
# Note that level must exist at the beginning of the line
|
21
|
+
LEVEL_REGEXP = /^(life|medium|short)/
|
22
|
+
|
23
|
+
# Don't include level regexp
|
24
|
+
REGEXP_LIST = [
|
25
|
+
FOLDER_REGEXP,
|
26
|
+
GOAL_REGEXP,
|
27
|
+
CONTEXT_REGEXP,
|
28
|
+
PRIORITY_REGEXP
|
29
|
+
]
|
16
30
|
|
17
31
|
# Parses a context in the format @Context or @[Spaced Context]
|
18
32
|
def parse_context(input)
|
@@ -28,20 +42,63 @@ module Toodledo
|
|
28
42
|
return strip_brackets(match_data[1])
|
29
43
|
end
|
30
44
|
|
31
|
-
# Parses a goal in the format
|
45
|
+
# Parses a goal in the format ^Goal or ^[Spaced Goal]
|
32
46
|
def parse_goal(input)
|
33
47
|
match_data = GOAL_REGEXP.match(input)
|
34
48
|
return match_data if (match_data == nil)
|
35
49
|
return strip_brackets(match_data[1])
|
36
50
|
end
|
37
51
|
|
38
|
-
#
|
39
|
-
|
52
|
+
# Parses priority in the format !priority (top, high, medium, low,
|
53
|
+
# negative)
|
54
|
+
def parse_priority(input)
|
55
|
+
match_data = PRIORITY_REGEXP.match(input)
|
56
|
+
if (match_data == nil)
|
57
|
+
return nil
|
58
|
+
end
|
40
59
|
|
60
|
+
p = match_data[1]
|
61
|
+
case p
|
62
|
+
when 'top'
|
63
|
+
return Toodledo::Priority::TOP
|
64
|
+
when 'high'
|
65
|
+
return Toodledo::Priority::HIGH
|
66
|
+
when 'medium'
|
67
|
+
return Toodledo::Priority::MEDIUM
|
68
|
+
when 'low'
|
69
|
+
return Toodledo::Priority::LOW
|
70
|
+
when 'negative'
|
71
|
+
return Toodledo::Priority::NEGATIVE
|
72
|
+
else
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_level(input)
|
78
|
+
match_data = LEVEL_REGEXP.match(input)
|
79
|
+
if (match_data == nil)
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
|
83
|
+
p = match_data[1]
|
84
|
+
case p
|
85
|
+
when 'life'
|
86
|
+
return Toodledo::Goal::LIFE_LEVEL
|
87
|
+
when 'medium'
|
88
|
+
return Toodledo::Goal::MEDIUM_LEVEL
|
89
|
+
when 'short'
|
90
|
+
return Toodledo::Goal::SHORT_LEVEL
|
91
|
+
else
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the bit after we've looked for *Folder, @Context & ^Goal
|
97
|
+
def parse_remainder(line)
|
41
98
|
input = line
|
42
99
|
|
43
100
|
# Strip out anything that isn't folder, goal or context.
|
44
|
-
for regexp in
|
101
|
+
for regexp in REGEXP_LIST
|
45
102
|
input = input.sub(regexp, '')
|
46
103
|
end
|
47
104
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'toodledo/command_line/parser_helper'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Toodledo
|
5
|
+
|
6
|
+
module CommandLine
|
7
|
+
# Runs the stdin client.
|
8
|
+
class StdinCommand < BaseCommand
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
super(client, 'stdin', false)
|
12
|
+
self.short_desc = "Takes standard input"
|
13
|
+
self.description = "Useful for pipes and redirected files"
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(args)
|
17
|
+
Toodledo.begin(client.logger) do |session|
|
18
|
+
$stdin.each do |line|
|
19
|
+
line.strip!
|
20
|
+
|
21
|
+
if (line == nil || line.empty?)
|
22
|
+
return 0
|
23
|
+
end
|
24
|
+
|
25
|
+
client.execute_command(session, line)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
module Toodledo
|
3
|
+
module CommandLine
|
4
|
+
class TaskFormatter
|
5
|
+
|
6
|
+
# Formats the task for a command line.
|
7
|
+
def format(task)
|
8
|
+
fancyp = '!' + readable_priority(task.priority)
|
9
|
+
|
10
|
+
msg = "<#{task.server_id}> -- #{fancyp}"
|
11
|
+
|
12
|
+
if (task.folder != Folder::NO_FOLDER)
|
13
|
+
msg += " *[#{task.folder.name}]"
|
14
|
+
end
|
15
|
+
|
16
|
+
if (task.context != Context::NO_CONTEXT)
|
17
|
+
msg += " @[#{task.context.name}]"
|
18
|
+
end
|
19
|
+
|
20
|
+
if (task.goal != Goal::NO_GOAL)
|
21
|
+
msg += " ^[#{task.goal.name}]"
|
22
|
+
end
|
23
|
+
|
24
|
+
if (task.repeat != Repeat::NONE)
|
25
|
+
msg += " repeat[#{readable_repeat(task.repeat)}]"
|
26
|
+
end
|
27
|
+
|
28
|
+
if (task.duedate != nil)
|
29
|
+
fmt = '%m/%d/%Y %I:%M %p'
|
30
|
+
msg += " \#[#{task.duedatemodifier}#{task.duedate.strftime(fmt)}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
if (task.tag != nil)
|
34
|
+
msg += " tag[#{task.tag}]"
|
35
|
+
end
|
36
|
+
|
37
|
+
if (task.parent_id != nil)
|
38
|
+
msg += " parent[#{task.parent_id}]"
|
39
|
+
end
|
40
|
+
|
41
|
+
if (task.length != nil)
|
42
|
+
msg += " length[#{task.length}]"
|
43
|
+
end
|
44
|
+
|
45
|
+
if (task.timer != nil)
|
46
|
+
msg += " timer[#{task.timer}]"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Use the highline color library
|
50
|
+
msg += " #{task.title}"
|
51
|
+
|
52
|
+
if (task.note != nil)
|
53
|
+
msg += "\n #{task.note}"
|
54
|
+
end
|
55
|
+
|
56
|
+
return msg
|
57
|
+
end
|
58
|
+
|
59
|
+
def readable_priority(priority)
|
60
|
+
case priority
|
61
|
+
when Priority::TOP
|
62
|
+
return 'top'
|
63
|
+
when Priority::HIGH
|
64
|
+
return 'high'
|
65
|
+
when Priority::MEDIUM
|
66
|
+
return 'medium'
|
67
|
+
when Priority::LOW
|
68
|
+
return 'low'
|
69
|
+
when Priority::NEGATIVE
|
70
|
+
return 'negative'
|
71
|
+
else
|
72
|
+
return ''
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def readable_repeat(repeat)
|
77
|
+
case repeat
|
78
|
+
when Repeat::NONE
|
79
|
+
''
|
80
|
+
when Repeat::WEEKLY
|
81
|
+
"weekly"
|
82
|
+
when Repeat::MONTHLY
|
83
|
+
"monthly"
|
84
|
+
when Repeat::YEARLY
|
85
|
+
"yearly"
|
86
|
+
when Repeat::DAILY
|
87
|
+
"daily"
|
88
|
+
when Repeat::BIWEEKLY
|
89
|
+
"biweekly"
|
90
|
+
when Repeat::BIMONTHLY
|
91
|
+
"bimonthly"
|
92
|
+
when Repeat::SEMIANNUALLY
|
93
|
+
"semiannually"
|
94
|
+
when Repeat::QUARTERLY
|
95
|
+
"quarterly"
|
96
|
+
else
|
97
|
+
''
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|