syc-task 0.0.1

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/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: []