syc-task 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = Simple task organizer
2
+ With syctask you can organize your tasks (inspired by David Bryant Copland's book <b>Build Awesome Command-Line Applications in Ruby</b>.
3
+
4
+ ==Install
5
+ The application can be installed with
6
+ $ gem install syc-task
7
+
8
+ == Usage
9
+ syctask provides basic task organizer functions as create, update, list und complete a task. Addtional functions are to plan tasks you want to accomplish today. If you are not sure in which sequence to conduct the task you can prioritize them with a pair wise comparisson. You can time tasks with start and stop and you can finally extract tasks from a minutes of meetings file.
10
+
11
+ ===Create tasks with new
12
+ Create a new task in the default task directory ~/.tasks
13
+ $ syctask "My first task"
14
+
15
+ Provide a description
16
+ $ syctask "My first task" --description "Explanation of my first task"
17
+
18
+ Schedule a task with a follow-up and due date
19
+ $ syctask "My first task" --follow-up "2013-02-25" --due "2013-03-11"
20
+
21
+ Set a proirity for a task
22
+ $ syctask "My first task" --prio 3
23
+
24
+ Except for --description you can also provide short forms for the options.
25
+
26
+ ===List tasks
27
+ List tasks that are not marked as done in short form
28
+ $ syctask list
29
+
30
+ List all tasks in long form
31
+ $ syctask list --all --complete
32
+
33
+ Search tasks that match a pattern
34
+ $ syctask list --id "<10" --follow_up ">2013-02-25" --title "My \w task"
35
+
36
+ ===Update tasks
37
+ Except for title and id all values can be updated. Note and tags are not
38
+ overridden rather supplemented with the update value.
39
+
40
+ Update task with ID 1 and provide some informative note
41
+ $ syctask update 1 --note "Some explanation about the progress on the task"
42
+
43
+ ===Complete tasks
44
+ Complete the task with ID 1 and provide a final note
45
+ $ syctask done 1 --note "Finalize my first task"
46
+
47
+ ==Supported platform
48
+ syc-task has been tested with 1.9.3
49
+
50
+ ==Notes
51
+ As with version 0.0.1 only new, update, list and done is implemented.
52
+
53
+ The test files live in the folder test and start with test_.
54
+
55
+ There is a rake file available to run all tests
56
+ $ rake test
57
+
58
+ ==License
59
+ syc-task is released under the {MIT License}[http://opensource.org/licenses/MIT]
60
+
61
+ ==Links
62
+ * [http://www.github.com/sugaryourcoffee/syc-task] - Source code on GitHub
63
+ * [http://syc.dyndns.org/drupal/wiki/syc-task] - Development notebook
64
+ * [https://rubygems.org/gems/syc-backup] - RubyGems
data/bin/syctask ADDED
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gli'
3
+ require 'syctask'
4
+ include GLI::App
5
+
6
+ program_desc 'A simple task manager'
7
+
8
+ version Syctask::VERSION
9
+
10
+ desc 'The directory where tasks are saved to'
11
+ default_value File.expand_path('~/.tasks')
12
+ arg_name 'TASK_DIR'
13
+ flag [:t,:taskdir]
14
+
15
+ desc 'Project name where tasks are saved'
16
+ arg_name 'PROJECT'
17
+ flag [:p, :project]
18
+
19
+ desc 'Create a new task'
20
+ arg_name 'TASK_TITLE'
21
+ command :new do |c|
22
+ c.desc 'Priority of the task, 1 highes priority'
23
+ c.default_value 3
24
+ c.arg_name 'PRIO'
25
+ c.flag [:p, :prio], :type => Integer
26
+
27
+ c.desc 'Follow-up date'
28
+ c.arg_name 'FOLLOW-UP'
29
+ c.flag [:f, :follow_up], :must_match => /\d{4}-\d{2}-\d{2}/
30
+
31
+ c.desc 'Due date'
32
+ c.arg_name 'DUE'
33
+ c.flag [:d, :due_date], :must_match => /\d{4}-\d{2}-\d{2}/
34
+
35
+ c.desc 'Description of the task'
36
+ c.arg_name 'DESCRIPTION'
37
+ c.flag :description
38
+
39
+ c.desc 'Note on progress of the task'
40
+ c.arg_name 'NOTE'
41
+ c.flag [:n, :note]
42
+
43
+ c.desc 'Tags that describe the task'
44
+ c.arg_name 'TAG1,TAG2,TAG3'
45
+ c.flag [:t, :tags], :must_match => /^\w+(?:,\w+)*/
46
+
47
+ c.action do |global_options,options,args|
48
+ filter = [:tags, :description, :prio, :due_date, :follow_up,
49
+ :note]
50
+ options.keep_if {|key, value| filter.find_index(key)}
51
+ if args.empty?
52
+ STDOUT.puts "Reading new tasks from STDIN now... (end with CTRL-D)"
53
+ args = STDIN.readlines.map {|t| t.chomp}
54
+ end
55
+ task_numbers = nil
56
+ args.each do |title|
57
+ task_number = @service.create(global_options[:t], options, title)
58
+ if task_numbers.nil?
59
+ task_numbers = task_number.to_s
60
+ else
61
+ task_numbers += ", #{task_number}"
62
+ end
63
+ end
64
+ if args.empty?
65
+ puts
66
+ help_now! "You have to provide at least a title to create a task"
67
+ else
68
+ STDOUT.puts sprintf("%s %s", "--> created tasks with task numbers",
69
+ " #{task_numbers}").color(:green)
70
+ end
71
+ end
72
+ end
73
+
74
+ desc 'Extract tasks from a file'
75
+ arg_name 'TASK_FILE'
76
+ command :scan do |c|
77
+ c.action do |global_options,options,args|
78
+ puts "scan command not implemented yet"
79
+ end
80
+ end
81
+
82
+ desc 'List tasks'
83
+ command :list do |c|
84
+
85
+ c.desc 'List all tasks done and open'
86
+ c.switch [:a, :all]
87
+
88
+ c.desc 'Print complete task'
89
+ c.switch [:c, :complete]
90
+
91
+ c.desc 'Filter for ID'
92
+ c.arg_name 'ID1,ID2,ID3|[<|=|>]ID'
93
+ c.flag [:i, :id], :must_match => /^\d+(?:,\d+)*|^[<|=|>]\d+/
94
+
95
+ c.desc 'REGEXP as filter for title'
96
+ c.arg_name 'REGEXP'
97
+ c.flag [:title]
98
+
99
+ c.desc 'Filter for priority'
100
+ c.arg_name '[<|=|>]PRIO'
101
+ c.flag [:p, :prio], :must_match => /^\d+|^[<|=|>]\d+/
102
+
103
+ c.desc 'Filter for follow-up date'
104
+ c.arg_name '[<|=|>]DATE'
105
+ c.flag [:f, :follow_up], :must_match => /^(?:[<|=|>])?\d{4}-\d{2}-\d{2}/
106
+
107
+ c.desc 'Filter for due date'
108
+ c.arg_name '[<|=|>]DATE'
109
+ c.flag [:d, :due_date], :must_match => /^(?:[<|=|>])?\d{4}-\d{2}-\d{2}/
110
+
111
+ c.desc 'REGEXP as filter for description'
112
+ c.arg_name 'REGEXP'
113
+ c.flag :description
114
+
115
+ c.desc 'REGEXP as filter for note'
116
+ c.arg_name 'REGEXP'
117
+ c.flag [:n, :note]
118
+
119
+ c.desc 'Tags or REGEXP as filter for tags'
120
+ c.arg_name 'TAG1,TAG2,TAG3|REGEXP'
121
+ c.flag [:t, :tags], :must_match => /^\w+(?:,\w+)*|\/.*\//
122
+
123
+ c.action do |global_options,options,args|
124
+ filter = [:id, :tags, :description, :prio, :due_date, :follow_up,
125
+ :note, :title]
126
+ all = options[:all]
127
+ complete = options[:complete]
128
+ options.keep_if {|key, value| filter.find_index(key) and value != nil}
129
+ count = 0
130
+ @service.find(global_options[:t], options, all).each do |task|
131
+ task.print_pretty(complete)
132
+ count += 1
133
+ end
134
+ STDOUT.puts sprintf("--> found %d tasks", count).color(:green)
135
+ end
136
+ end
137
+
138
+ desc 'Plan tasks for today'
139
+ command :plan do |c|
140
+ c.action do |global_options,options,args|
141
+ puts "plan command not implemented yet"
142
+ end
143
+ end
144
+
145
+ desc 'Prioritize tasks'
146
+ command :prio do |c|
147
+ c.action do |global_options,options,args|
148
+ puts "prio command not implemented yet"
149
+ end
150
+ end
151
+
152
+ desc 'Start and time a task'
153
+ arg_name 'TASK_NUMBER'
154
+ command :start do |c|
155
+ c.action do |global_options,options,args|
156
+ puts "start command not implemented yet"
157
+ end
158
+ end
159
+
160
+ desc 'Stop the running task'
161
+ command :stop do |c|
162
+ c.action do |global_options,options,args|
163
+ puts "stop command not implemented yet"
164
+ end
165
+ end
166
+
167
+ desc 'Update the task'
168
+ arg_name 'TASK_NUMBER'
169
+ command :update do |c|
170
+ c.desc 'Priority of the task, 1 highes priority'
171
+ c.arg_name 'PRIO'
172
+ c.flag [:p, :prio], :type => Integer
173
+
174
+ c.desc 'Follow-up date'
175
+ c.arg_name 'FOLLOW-UP'
176
+ c.flag [:f, :follow_up], :must_match => /\d{4}-\d{2}-\d{2}/
177
+
178
+ c.desc 'Due date'
179
+ c.arg_name 'DUE'
180
+ c.flag [:d, :due_date], :must_match => /\d{4}-\d{2}-\d{2}/
181
+
182
+ c.desc 'Description of the task'
183
+ c.arg_name 'DESCRIPTION'
184
+ c.flag :description
185
+
186
+ c.desc 'Note on progress of the task'
187
+ c.arg_name 'NOTE'
188
+ c.flag [:n, :note]
189
+
190
+ c.desc 'Tags that describe the task'
191
+ c.arg_name 'TAG1,TAG2,TAG3'
192
+ c.flag [:t, :tags], :must_match => /^\w+(?:,\w+)*/
193
+
194
+ c.action do |global_options,options,args|
195
+ help_now! "TASK_NUMBER required" if args.empty?
196
+ filter = [:tags, :description, :prio, :due_date, :follow_up,
197
+ :note]
198
+ options.keep_if {|key, value| filter.find_index(key) and value != nil}
199
+
200
+ success = @service.update(global_options[:t], args[0], options)
201
+ STDOUT.puts sprintf("sucessfully updated task with TASK_NUMBER ", args[0]).color(:green) if success
202
+ STDOUT.puts sprintf("could not update task with TASK_NUMBER ", args[0]).color(:red) unless success
203
+ end
204
+ end
205
+
206
+ desc 'Mark task as done'
207
+ arg_name 'TASK_NUMBER'
208
+ command :done do |c|
209
+ c.desc 'Print task after marked as done'
210
+ c.switch [:p, :print]
211
+ c.desc 'Print complete task'
212
+ c.switch [:c, :complete]
213
+
214
+ c.desc 'Final note for the task'
215
+ c.arg_name 'NOTE'
216
+ c.flag [:n, :note]
217
+
218
+ c.action do |global_options,options,args|
219
+ help_now!('TASK_NUMBER is required') if args.empty?
220
+ task = @service.read(global_options[:t], args[0])
221
+ exit_now!("Task with TASK_NUMBER #{args[0]} does not exist") unless task
222
+ task.done(options[:note])
223
+ @service.save(global_options[:t], task)
224
+ STDOUT.puts sprintf("Marked task with TASK_NUMBER %d as done", args[0]).color(:green)
225
+ task.print_pretty(options[:c]) if options[:p]
226
+ end
227
+ end
228
+
229
+ pre do |global,command,options,args|
230
+ # Pre logic here
231
+ # Return true to proceed; false to abort and not call the
232
+ # chosen command
233
+ # Use skips_pre before a command to skip this block
234
+ # on that command only
235
+
236
+ @service = Syctask::TaskService.new
237
+
238
+ dir = File.expand_path(global[:t])
239
+ dir += "/" + global[:p] if global[:p]
240
+ global[:taskdir] = global[:t] = dir.squeeze("/")
241
+
242
+
243
+ true
244
+ end
245
+
246
+ post do |global,command,options,args|
247
+ # Post logic here
248
+ # Use skips_post before a command to skip this
249
+ # block on that command only
250
+ end
251
+
252
+ on_error do |exception|
253
+ # Error logic here
254
+ # return false to skip default error handling
255
+ true
256
+ end
257
+
258
+ exit run(ARGV)
data/lib/syctask.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'syctask/version.rb'
2
+ require 'rainbow'
3
+ require 'syctask/task.rb'
4
+ require 'syctask/task_service.rb'
5
+
6
+ # Add requires for other files you add to your project here, so
7
+ # you just need to require this one file in your bin file
@@ -0,0 +1,80 @@
1
+ # Syctask provides functions for managing tasks in a task list.
2
+ module Syctask
3
+
4
+ # Evaluator provides different evaluatons for comparing numbers,
5
+ # dates and strings. Also provides methods to check whether a value is part
6
+ # of a list
7
+ class Evaluator
8
+ # Pattern to match operands <|=|> followed by a number or a single number
9
+ NUMBER_COMPARE_PATTERN = /^(<|=|>)(\d+)|^(\d+)/
10
+ # Pattern to match comma separated values
11
+ CSV_PATTERN = /\w+(?=,)|(?<!<|=|>)\w+$/
12
+ # Pattern to match comma separated numbers
13
+ NUMBER_CSV_PATTERN = /\d+(?=,)|\d+$/
14
+ # Pattern to match a date prepended by <|=|> or a single date
15
+ DATE_COMPARE_PATTERN = /^(<|=|>)(\d{4}-\d{2}-\d{2})|(\d{4}-\d{2}-\d{2})/
16
+ # Pattern to match a date in the form of yyyy-mm-dd
17
+ DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/
18
+ # Pattern that matches anything that is not prepended with <|=|>
19
+ NON_COMPARE_PATTERN = /[^<=>]*/
20
+
21
+ # Compares two numbers regarding <|=|>. Returns true if the comparisson
22
+ # succeeds otherwise false. If eather value or pattern is nil false is
23
+ # returned. If value is empty false is returned.
24
+ def compare_numbers(value, pattern)
25
+ return false if value.nil? or pattern.nil?
26
+ return false if value.class == String and value.empty?
27
+ result = pattern.match(NUMBER_COMPARE_PATTERN)
28
+ return false unless result
29
+ compare(value, result.captures)
30
+ end
31
+
32
+ # Compares two dates regarding <|=|>. Returns true if the comparisson
33
+ # succeeds otherwise false. If eather value or pattern is nil false is
34
+ # returned.
35
+ def compare_dates(value, pattern)
36
+ return false if value.nil? or pattern.nil?
37
+ result = pattern.match(DATE_COMPARE_PATTERN)
38
+ return false unless result
39
+ value = "'#{value}'"
40
+ captures = result.captures.collect! do |c|
41
+ c and c.match(DATE_PATTERN) ? "'#{c}'" : c
42
+ end
43
+ compare(value, captures)
44
+ end
45
+
46
+ # Compares two values regarding <|=|>. Returns true if the comparisson
47
+ # succeeds otherwise false. If eather value or operand is nil false is
48
+ # returned.
49
+ def compare(value, operands)
50
+
51
+ if operands[2]
52
+ operands[0] = "=="
53
+ operands[1] = operands[2]
54
+ elsif operands[0] == "="
55
+ operands[0] = "=="
56
+ end
57
+
58
+ expression = "#{value} #{operands[0]} #{operands[1]}"
59
+ eval(expression)
60
+ end
61
+
62
+ # Evaluates whether value is part of the provided csv pattern. Returns true
63
+ # if it evaluates to true otherwise false. If value or pattern is nil false
64
+ # is returned.
65
+ def includes?(value, pattern)
66
+ return false if value.nil? or pattern.nil?
67
+ captures = pattern.scan(CSV_PATTERN)
68
+ !captures.find_index(value.to_s).nil?
69
+ end
70
+
71
+ # Evaluates if value matches the provided regex. Returns true if a match is
72
+ # found. If value or regex is nil false is returned.
73
+ def matches?(value, regex)
74
+ return false if value.nil? or regex.nil?
75
+ !value.match(Regexp.new(regex, true)).nil?
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,229 @@
1
+ require 'fileutils'
2
+ require 'rainbow'
3
+ require_relative 'evaluator'
4
+
5
+ # Syctask provides functions for managing tasks in a task list
6
+ module Syctask
7
+
8
+ # A Task is the basic element of the task list and holds all information
9
+ # about a task.
10
+ class Task
11
+
12
+ # Holds the options of the task.
13
+ # Options are
14
+ # * description - additional information about the task
15
+ # * follow_up - follow-up date of the task
16
+ # * due - due date of the task
17
+ # * prio - priority of the task
18
+ # * note - information about the progress or state of the task
19
+ # * tags - can be used to search for tasks that belong to a certain category
20
+ attr_accessor :options
21
+ # Title of the class
22
+ attr_reader :title
23
+ # ID of the task
24
+ attr_reader :id
25
+ # Creation date
26
+ attr_reader :creation_date
27
+ # Update date
28
+ attr_reader :update_date
29
+ # Done date
30
+ attr_reader :done_date
31
+ # Directory where the file of the task is located
32
+ attr_reader :dir
33
+
34
+ # Creates a new task. If the options contain a note than the current date
35
+ # and time is added.
36
+ def initialize(options={}, title, id)
37
+ @creation_date = Time.now.strftime("%Y-%m-%d - %H:%M:%S")
38
+ @title = title
39
+ @options = options
40
+ @options[:note] =
41
+ "#{@creation_date}\n#{@options[:note]}\n" if @options[:note]
42
+ @id = id
43
+ end
44
+
45
+ # Updates the task with new values. Except for note and tags which are
46
+ # supplemented with the new values and not overridden.
47
+ def update(options)
48
+ @update_date = Time.now.strftime("%Y-%m-%d - %H:%M:%S")
49
+ options.keys.each do |key|
50
+ new_value = options[key]
51
+
52
+ case key
53
+ when :note
54
+ new_value = "#{@update_date}\n#{new_value}\n#{@options[key]}"
55
+ when :tags
56
+ if @options[key].include? new_value
57
+ new_value = @options[key]
58
+ else
59
+ new_value = "#{@options[key]},#{new_value}"
60
+ end
61
+ end
62
+
63
+ @options[key] = new_value
64
+ end
65
+ end
66
+
67
+ # Checks whether this task has been updated. Returns true if updated
68
+ # otherwise false
69
+ def update?
70
+ !@updated_date.nil?
71
+ end
72
+
73
+ # Marks the task as done. When done than the done date is set. Optionally a
74
+ # note can be provided.
75
+ def done(note="")
76
+ @done_date = Time.now.strftime("%Y-%m-%d - %H:%M:%S")
77
+ if note
78
+ options[:note] = "#{@done_date}\n#{note}\n#{@options[:note]}"
79
+ end
80
+ end
81
+
82
+ # Checks if this task is done. Returns true if done otherwise false
83
+ def done?
84
+ !@done_date.nil?
85
+ end
86
+
87
+ # Compares the provided elements in the filter with the correspondent
88
+ # elements in the task. When all comparissons match than true is returned.
89
+ # If one comparisson does not match false is returned. If filter is empty
90
+ # than true is returned. The values can be compared regarding <, =, > or
91
+ # whether the task's value is part of a list of provided values. It is also
92
+ # possible to provide a regex as a filter. Following comparissons are
93
+ # available
94
+ # Value Compare
95
+ # :title regex
96
+ # :description regex
97
+ # :id contains, <|=|> no operator same as =
98
+ # :prio contains, <|=|> no operator same as =
99
+ # :tags contains, regex
100
+ # :follow_up <|=|>
101
+ # :due <|=|>
102
+ def matches?(filter = {})
103
+ return true if filter.empty?
104
+ evaluator = Evaluator.new
105
+ filter.each do |key, value|
106
+ matches = false
107
+ case key
108
+ when :title, :t
109
+ matches = evaluator.matches?(@title, value)
110
+ when :description
111
+ matches = evaluator.matches?(@options[:description], value)
112
+ when :id, :i, "id", "i"
113
+ matches = (evaluator.includes?(@id, value) or
114
+ evaluator.compare_numbers(@id, value))
115
+ when :prio, :p
116
+ matches = (evaluator.includes?(@options[:prio], value) or
117
+ evaluator.compare(@options[:prio], value))
118
+ when :tags
119
+ matches = evaluator.matches?(@options[:tags], value)
120
+ when :follow_up, :f, :d, :due_date
121
+ matches = evaluator.compare_dates(@options[key], value)
122
+ end
123
+ return false unless matches
124
+ end
125
+ true
126
+ end
127
+
128
+ # Prints the task in a formatted way eather all values when long is true
129
+ # or only id, title, prio, follow-up and due date.
130
+ def print_pretty(long=false)
131
+ pretty_string(long)
132
+ end
133
+
134
+ # Prints the task as a CSV
135
+ def print_csv
136
+ STDOUT.puts(csv_string)
137
+ end
138
+
139
+ private
140
+
141
+ # Creates the directory if it does not exist
142
+ def create_dir(dir)
143
+ fileutils.mkdir_p dir unless file.exists? dir
144
+ end
145
+
146
+ # creates the task's id based on the tasks available in the task directory.
147
+ # the task's file name is in the form id.task. create_task_id determines
148
+ # the biggest number and adds one to create the task's id.
149
+ def create_task_id
150
+ tasks = dir.glob("#{@dir}/*")
151
+ ids = []
152
+ tasks.each {|task| ids << task.scan(/^\d+(?=\.task)/)[0].to_i }
153
+ if ids.empty?
154
+ @id = 1
155
+ elsif
156
+ @id = ids[ids.size-1] + 1
157
+ end
158
+ end
159
+
160
+ # Prints the task formatted. Values that are nil are not printed. A type all
161
+ # will print all available values. Otherwise only ID, title, description,
162
+ # prio, follow-up and due date are printed.
163
+ def pretty_string(long)
164
+ color = :default
165
+ color = :green if self.done?
166
+
167
+ puts sprintf("%04d - %s", @id, @title.bright).color(color)
168
+ puts sprintf("%6s %s", " ", @options[:description]).color(color) if @options[:description]
169
+ puts sprintf("%6s Prio: %s", " ", @options[:prio]).color(color) if @options[:prio]
170
+ puts sprintf("%6s Follow-up: %s", " ", @options[:follow_up]).color(color) if @options[:follow_up]
171
+ puts sprintf("%6s Due: %s", " ", @options[:due]).color(color) if @options[:due]
172
+ if long
173
+ if @options[:note]
174
+ note = split_lines(@options[:note].chomp, 70)
175
+ note = note.chomp.
176
+ gsub(/\n(?!\d{4}-\d{2}-\d{2} - \d{2}:\d{2}:\d{2})/, "\n#{' '*9}")
177
+ note = note.
178
+ gsub(/\n(?=\d{4}-\d{2}-\d{2} - \d{2}:\d{2}:\d{2})/, "\n#{' '*7}")
179
+ puts sprintf("%6s %s", " ", note.chomp).color(color)
180
+ end
181
+ puts sprintf("%6s Tags: %s", " ", @options[:tags]).color(color) if @options[:tags]
182
+ puts sprintf("%6s Created: %s", " ", @creation_date).color(color)
183
+ puts sprintf("%6s Updated: %s", " ", @update_date).color(color) if @update_date
184
+ puts sprintf("%6s Closed: %s", " ", @done_date).color(color) if @done_date
185
+ end
186
+ end
187
+
188
+ # Prints all values as a csv separated with ";". This string can be read by
189
+ # another application. The values are
190
+ # id;title;description;prio;follow-up;due;note;tags;created;
191
+ # updated|UNCHANGED;DONE|OPEN
192
+ def csv_string
193
+ string = "\n#{@id};#{@title};"
194
+ string +" #{@options[:description]};#{@options[:prio]};"
195
+ string += "#{@options[:follow_up]};#{@options[:due]};"
196
+ string += "#{@options[:note].gsub(/\n/, '\\n')};"
197
+ string += "#{@options[:tags]};"
198
+ string += "#{@creation_date};"
199
+ string += "#{@udpate_date ? "UPDATED" : "UNCHANGED"};"
200
+ string += "#{@done_date ? "DONE" : "OPEN"}\n"
201
+ string
202
+ end
203
+
204
+ # Splits a string to size (chars) less or equal to length
205
+ def split_lines(string, length)
206
+ lines = string.squeeze(" ").split("\n")
207
+ i = 0
208
+ new_lines = []
209
+ new_lines[i] = ""
210
+ lines.each do |line|
211
+ line.squeeze(" ").split.each do |w|
212
+ if new_lines[i].length + w.length < length
213
+ new_lines[i] += "#{w} "
214
+ else
215
+ i += 1
216
+ new_lines[i] = "#{w} "
217
+ end
218
+ end
219
+ i += 1
220
+ new_lines[i] = ""
221
+ end
222
+ text = ""
223
+ new_lines.each {|l| text << "#{l}\n"}
224
+ text.chomp
225
+ end
226
+
227
+ end
228
+
229
+ end
@@ -0,0 +1,108 @@
1
+ require 'yaml'
2
+
3
+ # Syctask provides functions for managing tasks in a task list
4
+ module Syctask
5
+
6
+ # Provides services to operate tasks as create, read, find, update and save
7
+ # Task objects
8
+ class TaskService
9
+
10
+ # Creates a new task in the specified directory, with the specified options
11
+ # and the specified title. If the directory doesn't exist it is created.
12
+ # When the task is created it is assigned a unique ID within the directory.
13
+ # Options are
14
+ # * description - additional information about the task
15
+ # * follow_up - follow-up date of the task
16
+ # * due - due date of the task
17
+ # * prio - priority of the task
18
+ # * note - information about the progress or state of the task
19
+ # * tags - can be used to searching tasks that belong to a certain category
20
+ def create(dir, options, title)
21
+ create_dir(dir)
22
+ task = Task.new(options, title, create_id(dir))
23
+ save(dir, task)
24
+ task.id
25
+ end
26
+
27
+ # Reads the task with given ID id located in given directory dir. If task
28
+ # does not exist nil is returned otherwise the task is returned
29
+ def read(dir, id)
30
+ task = nil
31
+ Dir.glob("#{dir}/*").each do |file|
32
+ task = YAML.load_file(file) if File.file? file
33
+ return task if task and task.id == id.to_i
34
+ end
35
+ nil
36
+ end
37
+
38
+ # Finds all tasks that match the given filter. The filter can be provided
39
+ # for :id, :title, :description, :follow_up, :due, :tags and :prio.
40
+ # id can be eather a selection of IDs ID1,ID2,ID3 or a comparison <|=|>ID.
41
+ # title and :description can be a REGEX as /look for \d+ examples/
42
+ # follow-up and :due can be <|=|>DATE
43
+ # tags can be eather a selection TAG1,TAG2,TAG3 or a REGEX /[Ll]ecture/
44
+ # prio can be <|=|>PRIO
45
+ def find(dir, filter={}, all=true)
46
+ tasks = []
47
+ Dir.glob("#{dir}/*").sort.each do |file|
48
+ File.file?(file) ? task = YAML.load_file(file) : next
49
+ next if task and not all and task.done?
50
+ next if not task
51
+ tasks << task if task.matches?(filter)
52
+ end
53
+ tasks
54
+ end
55
+
56
+ # Updates the task with the given id in the given directory dir with the
57
+ # provided options.
58
+ # Options are
59
+ # * description - additional information about the task
60
+ # * follow_up - follow-up date of the task
61
+ # * due - due date of the task
62
+ # * prio - priority of the task
63
+ # * note - information about the progress or state of the task
64
+ # * tags - can be used to searching tasks that belong to a certain category
65
+ # Except for note and tags the values of the task are overridden with the
66
+ # new value. If note and tags are provided these are added to the existing
67
+ # values.
68
+ def update(dir, id, options)
69
+ task_file = Dir.glob("#{dir}/#{id}.task")[0]
70
+ task = YAML.load_file(task_file) if task_file
71
+ updated = false
72
+ if task
73
+ task.update(options)
74
+ save(dir, task)
75
+ updated = true
76
+ end
77
+ updated
78
+ end
79
+
80
+ # Saves the task to the task directory
81
+ def save(dir, task)
82
+ File.open("#{dir}/#{task.id}.task", 'w') {|f| YAML.dump(task, f)}
83
+ end
84
+
85
+ private
86
+
87
+ # Creates the task directory if it does not exist
88
+ def create_dir(dir)
89
+ FileUtils.mkdir_p dir unless File.exists? dir
90
+ end
91
+
92
+ # Creates the task's ID based on the tasks available in the task directory.
93
+ # The task's file name is in the form ID.task. create_id determines
94
+ # the biggest number and adds one to create the task's ID.
95
+ def create_id(dir)
96
+ tasks = Dir.glob("#{dir}/*")
97
+ ids = []
98
+ tasks.each do |task|
99
+ id = File.basename(task).scan(/^\d+(?=\.task)/)[0]
100
+ ids << id.to_i if id
101
+ end
102
+ ids.empty? ? 1 : ids.sort[ids.size-1] + 1
103
+ end
104
+
105
+ end
106
+ end
107
+
108
+
@@ -0,0 +1,5 @@
1
+ # Syctask provides functions for managing tasks in a task list
2
+ module Syctask
3
+ #Holds the version number of syctask
4
+ VERSION = '0.0.1'
5
+ end
data/syctask.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = syctask
2
+
3
+ Generate this with
4
+ syctask rdoc
5
+ After you have described your command line interface
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: syc-task
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pierre Sugar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: aruba
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: gli
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 2.5.4
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 2.5.4
78
+ - !ruby/object:Gem::Dependency
79
+ name: rainbow
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ! "= Simple task organizer\nWith syctask you can organize your tasks
95
+ (inspired by David Bryant Copland's book <b>Build Awesome Command-Line Applications
96
+ in Ruby</b>.\n\n==Install\nThe application can be installed with\n $ gem install
97
+ syc-task\n\n== Usage\nsyctask provides basic task organizer functions as create,
98
+ update, list und complete a task. Addtional functions are to plan tasks you want
99
+ to accomplish today. If you are not sure in which sequence to conduct the task you
100
+ can prioritize them with a pair wise comparisson. You can time tasks with start
101
+ and stop and you can finally extract tasks from a minutes of meetings file.\n\n===Create
102
+ tasks with new\nCreate a new task in the default task directory ~/.tasks\n $
103
+ syctask \"My first task\"\n\nProvide a description\n $ syctask \"My first task\"
104
+ --description \"Explanation of my first task\"\n\nSchedule a task with a follow-up
105
+ and due date\n $ syctask \"My first task\" --follow-up \"2013-02-25\" --due \"2013-03-11\"\n\nSet
106
+ a proirity for a task\n $ syctask \"My first task\" --prio 3\n\nExcept for --description
107
+ you can also provide short forms for the options.\n\n===List tasks\nList tasks that
108
+ are not marked as done in short form\n $ syctask list\n\nList all tasks in long
109
+ form\n $ syctask list --all --complete\n\nSearch tasks that match a pattern\n
110
+ \ $ syctask list --id \"<10\" --follow_up \">2013-02-25\" --title \"My \\w task\"\n\n===Update
111
+ tasks\nExcept for title and id all values can be updated. Note and tags are not\noverridden
112
+ rather supplemented with the update value.\n\nUpdate task with ID 1 and provide
113
+ some informative note\n $ syctask update 1 --note \"Some explanation about the
114
+ progress on the task\"\n\n===Complete tasks\nComplete the task with ID 1 and provide
115
+ a final note\n $ syctask done 1 --note \"Finalize my first task\"\n\n==Supported
116
+ platform\nsyc-task has been tested with 1.9.3\n\n==Notes\nAs with version 0.0.1
117
+ only new, update, list and done is implemented.\n\nThe test files live in the folder
118
+ test and start with test_.\n\nThere is a rake file available to run all tests\n
119
+ \ $ rake test\n\n==License\nsyc-task is released under the {MIT License}[http://opensource.org/licenses/MIT]\n\n==Links\n*
120
+ [http://www.github.com/sugaryourcoffee/syc-task] - Source code on GitHub\n* [http://syc.dyndns.org/drupal/wiki/syc-task]
121
+ - Development notebook\n* [https://rubygems.org/gems/syc-backup] - RubyGems\n"
122
+ email: pierre@sugaryourcoffee.de
123
+ executables:
124
+ - syctask
125
+ extensions: []
126
+ extra_rdoc_files:
127
+ - README.rdoc
128
+ - syctask.rdoc
129
+ files:
130
+ - bin/syctask
131
+ - lib/syctask/version.rb
132
+ - lib/syctask/task.rb
133
+ - lib/syctask/task_service.rb
134
+ - lib/syctask/evaluator.rb
135
+ - lib/syctask.rb
136
+ - README.rdoc
137
+ - syctask.rdoc
138
+ homepage: http://syc.dyndns.org/drupal/syc-task
139
+ licenses: []
140
+ post_install_message:
141
+ rdoc_options:
142
+ - --title
143
+ - syctask
144
+ - --main
145
+ - README.rdoc
146
+ - -ri
147
+ require_paths:
148
+ - lib
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 1.8.25
165
+ signing_key:
166
+ specification_version: 3
167
+ summary: Simple task organizer
168
+ test_files: []