syc-task 0.0.7 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +159 -15
- data/bin/console_timer +75 -0
- data/bin/syctask +246 -68
- data/lib/syctask.rb +4 -1
- data/lib/syctask/environment.rb +427 -2
- data/lib/syctask/schedule.rb +84 -21
- data/lib/syctask/settings.rb +43 -0
- data/lib/syctask/statistics.rb +196 -0
- data/lib/syctask/task.rb +58 -2
- data/lib/syctask/task_planner.rb +94 -13
- data/lib/syctask/task_scheduler.rb +5 -1
- data/lib/syctask/task_service.rb +55 -15
- data/lib/syctask/task_tracker.rb +27 -17
- data/lib/syctask/times.rb +29 -0
- data/lib/syctask/version.rb +1 -1
- data/lib/syctime/time_util.rb +46 -7
- data/lib/sycutil/console_timer.rb +75 -0
- metadata +215 -136
data/README.rdoc
CHANGED
@@ -6,7 +6,8 @@ The application can be installed with
|
|
6
6
|
$ gem install syc-task
|
7
7
|
|
8
8
|
== Usage
|
9
|
-
syctask provides basic task organizer functions as create, update, list and complete a task. Additional 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. The schedule task
|
9
|
+
syctask provides basic task organizer functions as create, update, list and complete a task. Additional 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. The schedule task
|
10
|
+
command will print a graphical timeline of the working day assigning the planned tasks to the timeline. Busy times are marked red. Meetings are listed with associated tasks that are assigned to the meetings. With the statistics command you can print statistical evaluation of tasks duration and count.
|
10
11
|
|
11
12
|
===Create tasks with new
|
12
13
|
Create a new task in the default task directory ~/.tasks
|
@@ -47,7 +48,8 @@ Invoke plan with a filter
|
|
47
48
|
Move tasks to another days plan
|
48
49
|
$ syctask plan today --move tomorrow --id 3,5
|
49
50
|
|
50
|
-
This will move the tasks with ID 3 and 5 from the today's plan to the tomorrow's plan
|
51
|
+
This will move the tasks with ID 3 and 5 from the today's plan to the tomorrow's plan. The duration will be set to the remaining processing time but at least to
|
52
|
+
30 minutes.
|
51
53
|
|
52
54
|
===Prioritize tasks
|
53
55
|
Planned tasks can be prioritized in a pair wise comparisson. So each task is
|
@@ -88,11 +90,21 @@ The plan or schedule command will print the tasks in the specified order
|
|
88
90
|
3: 9 - My second task
|
89
91
|
|
90
92
|
If only a part of the tasks is provided the rest of the tasks is appended to
|
91
|
-
the end of the task plan.
|
93
|
+
the end of the task plan. If you specify a position flag the prioritized tasks are added at the provided position.
|
94
|
+
$ syctask plan -o 7,9 -p 2
|
95
|
+
Tasks
|
96
|
+
-----
|
97
|
+
0: 3 - My first task
|
98
|
+
1: 10 - My fourth task
|
99
|
+
2: 7 - My third task
|
100
|
+
3: 9 - My second task
|
92
101
|
|
93
102
|
===Create schedule
|
94
103
|
The schedule command will print a graphical schedule with assigning the tasks
|
95
|
-
|
104
|
+
selected with plan. When schedule command is invoked the planned tasks are
|
105
|
+
added at or after the current time within the time schedule. Tasks that are done
|
106
|
+
and scheduled in the future are not shown. Tasks done and in the past are shown
|
107
|
+
with the actual processing time.
|
96
108
|
|
97
109
|
Create a schedule with working time from 8a.m. to 6p.m. and meetings between
|
98
110
|
9a.m. and 9.30a.m. and 1p.m. and 2.45p.m.
|
@@ -135,6 +147,24 @@ will print
|
|
135
147
|
-----
|
136
148
|
0: 1 - My first task
|
137
149
|
|
150
|
+
A task that is re-scheduled with
|
151
|
+
$ syctask update 1 -f tomorrow
|
152
|
+
|
153
|
+
will be shown as done (green) in the schedule and instead of separator - it
|
154
|
+
shows ~.
|
155
|
+
|
156
|
+
Tasks
|
157
|
+
----
|
158
|
+
0: 1 ~ My first task
|
159
|
+
|
160
|
+
A started task will be indicated by *
|
161
|
+
|
162
|
+
$ syctask start 1
|
163
|
+
$ syctask sche
|
164
|
+
Tasks
|
165
|
+
-----
|
166
|
+
0: 1 * My first task
|
167
|
+
|
138
168
|
===List tasks
|
139
169
|
List tasks that are not marked as done in short form
|
140
170
|
$ syctask list
|
@@ -145,6 +175,17 @@ List all tasks in long form
|
|
145
175
|
Search tasks that match a pattern
|
146
176
|
$ syctask list --id "<10" --follow_up ">2013-02-25" --title "My \w task"
|
147
177
|
|
178
|
+
===Inspect tasks
|
179
|
+
Lists each today's unplanned task and allows to edit, delete, mark as done or
|
180
|
+
plan
|
181
|
+
$ syctask inspect
|
182
|
+
0016 Create command for inspection
|
183
|
+
(e)dit, (d)one, de(l)ete, (p)lan, (c)omplete, (s)kip, (q)uit:
|
184
|
+
|
185
|
+
===Edit task
|
186
|
+
Edit a task with ID 10 in vi
|
187
|
+
$ syctask edit 10
|
188
|
+
|
148
189
|
===Update tasks
|
149
190
|
Except for title and id all values can be updated. Note and tags are not
|
150
191
|
overridden rather supplemented with the update value.
|
@@ -164,6 +205,36 @@ Delete tasks with ID 8 and 12 from the planned tasks of today. The tasks are
|
|
164
205
|
only removed from the planned tasks and not physically deleted.
|
165
206
|
$ syctask delete --plan today --id 8,12
|
166
207
|
|
208
|
+
===Settings
|
209
|
+
The settings command allows to define default values for task directory and to create general purpose tasks that can be used for tracking and later statistical evaluation.
|
210
|
+
|
211
|
+
Create general purpose tasks for phone and talk
|
212
|
+
$ syctask setting --general PHONE,TALK
|
213
|
+
|
214
|
+
List all settings
|
215
|
+
$ syctask setting --list
|
216
|
+
|
217
|
+
===Info
|
218
|
+
Info searches for the location of a task and lists all task directories
|
219
|
+
|
220
|
+
Search for task with id 102
|
221
|
+
$ syctask info --id 102
|
222
|
+
|
223
|
+
List all task directories
|
224
|
+
$ syctask info --taskdir
|
225
|
+
|
226
|
+
===Statistics
|
227
|
+
Shows statistics for work and meeting times as well as for task processing
|
228
|
+
|
229
|
+
Evaluate the complete log file
|
230
|
+
$ syctask statistics
|
231
|
+
|
232
|
+
Evaluate work times, meetings and tasks between 2013-01-01 and 2013-04-14
|
233
|
+
$ syctask statistics 2013-01-01 2013-04-14
|
234
|
+
|
235
|
+
Evaluate yesterday and today
|
236
|
+
$ syctask statistics yesterday today
|
237
|
+
|
167
238
|
===Task directory and project directory
|
168
239
|
The global options --taskdir and --project determine where the command finds
|
169
240
|
or creates the tasks. The default task directory is ~/.tasks, so if no task
|
@@ -182,6 +253,7 @@ In the table the relation of commands to --taskdir and --project are listed.
|
|
182
253
|
delete x x deletes the tasks in taskdir/project
|
183
254
|
done x x marks tasks in taskdir/project as done
|
184
255
|
help - -
|
256
|
+
inspect x x lists task to edit, done, delete, plan
|
185
257
|
list x x lists tasks in taskdir/project
|
186
258
|
new x x creates tasks in taskdir/project
|
187
259
|
plan x x retrieves tasks to plan from taskdir/projekt
|
@@ -189,22 +261,46 @@ In the table the relation of commands to --taskdir and --project are listed.
|
|
189
261
|
scan x x creates scanned tasks in taskdir/project
|
190
262
|
schedule - - schedules the planned tasks (see plan)
|
191
263
|
start - - starts task from planned tasks (see plan)
|
264
|
+
statistics - - shows statistics of time and count
|
192
265
|
stop - - stops task from planned task
|
193
266
|
update x x updates task in taskdir/project
|
194
267
|
|
195
268
|
===Files
|
196
269
|
|
270
|
+
* ID
|
271
|
+
id file contains the last issued id.
|
272
|
+
|
273
|
+
* IDS
|
274
|
+
ids file contains all issued ids.
|
275
|
+
|
197
276
|
* Task files
|
198
277
|
The tasks are named ID.task where ID is any Integer as 10.task. The files are
|
199
278
|
saved as YAML files and can be edited directly.
|
200
279
|
|
201
280
|
* Planned tasks files
|
202
|
-
The planned tasks are save to YYYY-MM-DD_planned_tasks in
|
203
|
-
directory. Each task is saved with the
|
281
|
+
The planned tasks are save to YYYY-MM-DD_planned_tasks in syctask's system
|
282
|
+
directory. Each task is saved with the task's directory and the ID.
|
204
283
|
|
205
284
|
* Schedule files
|
206
|
-
The schedule is saved to YYYY-MM-
|
207
|
-
|
285
|
+
The schedule is saved to YYYY-MM-DD_time_schedule in the default task directory.The files are saved as YAML files and can be changed manually.
|
286
|
+
|
287
|
+
* Log file
|
288
|
+
Creating schedule and task processings is logged to tasks.log. For example when a task is started and stopped this is action is saved to tasks.log.
|
289
|
+
|
290
|
+
* Tracked file
|
291
|
+
A started task is saved to tracked_tasks. A semaphore file is created with
|
292
|
+
ID.track when the task ID is started. When the task is stopped the semaphore
|
293
|
+
file is deleted.
|
294
|
+
|
295
|
+
* General purpose tasks
|
296
|
+
With syctask setting -g PHONE so called general purpose tasks can be created.
|
297
|
+
These tasks can be used for time tracking and later statistic evaluation to
|
298
|
+
determine the amount of disturbences e.g. by phone. These tasks are saved to
|
299
|
+
default_tasks. The general purpose tasks itself are also saved to the
|
300
|
+
.syc/syctask directory as regular task files.
|
301
|
+
|
302
|
+
* Default task dir
|
303
|
+
The default task that is used e.g. with list is saved to default_tasks_dir. This can be set with the setting command.
|
208
304
|
|
209
305
|
==Working with syctask
|
210
306
|
To work with syctask and get the most out of it there is to follow a certain
|
@@ -212,8 +308,8 @@ process.
|
|
212
308
|
|
213
309
|
===Creating a schedule
|
214
310
|
==== View tasks
|
215
|
-
In the morning before I start to work I scan my tasks with syctask list
|
216
|
-
an overview of my open tasks.
|
311
|
+
In the morning before I start to work I scan my tasks with syctask list or
|
312
|
+
syctask inspect to get an overview of my open tasks.
|
217
313
|
$ syctask list
|
218
314
|
|
219
315
|
==== Plan tasks
|
@@ -235,14 +331,20 @@ I assign the topics I want to discuss in the meetings to the meetings with
|
|
235
331
|
syctask schedule -a "A:1,3,6;B:3,5"
|
236
332
|
|
237
333
|
==== Start a task
|
238
|
-
To begin I start the first task in the schedule with syctask start
|
239
|
-
$ syctask start
|
334
|
+
To begin I start the first task in the schedule with syctask start -p ID (where ID is the ID of the planned (-p) tasks)
|
335
|
+
$ syctask start -p 10
|
240
336
|
|
241
337
|
==== End a task
|
242
338
|
To end the task I invoke
|
243
339
|
$ syctask stop
|
244
340
|
This will stop the last started task
|
245
341
|
|
342
|
+
==== Re-schedule a task
|
343
|
+
If I cannot finish a task than I update the task with a new follow-up date
|
344
|
+
$ syctask update 23 -f tomorrow
|
345
|
+
|
346
|
+
The task will be shown in the today's schedule as done.
|
347
|
+
|
246
348
|
==== Complete a task
|
247
349
|
When the task is done I call
|
248
350
|
$ syctask done 23
|
@@ -259,7 +361,7 @@ directory with the ID and save the files in this directory. If there is an
|
|
259
361
|
existing directory I link to the file from the ID directory
|
260
362
|
|
261
363
|
==Supported platform
|
262
|
-
syc-task has been tested with 1.9.3
|
364
|
+
syc-task has been tested with 1.9.3. It also works in Windows using Cygwin.
|
263
365
|
|
264
366
|
==Add TAB-completion to syctask
|
265
367
|
To activate bash's TAB-completion following lines have to be added to ~/.bashrc
|
@@ -308,9 +410,9 @@ To print to Dell-B1160w-Mono the following command can be used
|
|
308
410
|
|
309
411
|
$ syctask schedule | lpr -P Dell-B1160w-Mono
|
310
412
|
|
311
|
-
==Notes
|
413
|
+
==Release Notes
|
312
414
|
Version 0.0.1
|
313
|
-
new, update, list and done
|
415
|
+
Implementation of new, update, list and done commands.
|
314
416
|
|
315
417
|
Version 0.0.4
|
316
418
|
* delete: deleting tasks or remove tasks from a task plan
|
@@ -334,6 +436,48 @@ Version 0.0.6
|
|
334
436
|
Version 0.0.7
|
335
437
|
* updated rdoc
|
336
438
|
|
439
|
+
Version 0.1.15
|
440
|
+
* IDs are now unique independent of the task or project directory. After
|
441
|
+
upgrading from a version 0.0.7 or older the user asked whether to re-index
|
442
|
+
the tasks. It is adviced to tar the tasks before re-indexing with
|
443
|
+
$ tar cvfz tasks.tar.gz .tasks other_task_directories
|
444
|
+
* start will now show a timer in the upper right corner of the screen when
|
445
|
+
started with the -t (--timer) flag.
|
446
|
+
$ syctask start 10 -t
|
447
|
+
In order to use the task timer ncurses has to be installed as the task timer
|
448
|
+
uses tput from the ncurses library.
|
449
|
+
* The schedule has a heading with the schedule's date and the working time
|
450
|
+
* Planned tasks are now added at or after the current time if they are not done
|
451
|
+
yet. Done tasks are shown in the past with the actual processing time. Tasks
|
452
|
+
done before the start of the schedule are not shown in the schedule.
|
453
|
+
* Meetings that are at the current time are indicated with a *. Active tasks
|
454
|
+
are indicated with a star, re-scheduled tasks are indicated with a ~.
|
455
|
+
* Assigning tasks to meetings in a schedule is now done with the task ID
|
456
|
+
* Statistics show statistics about work time, meeting times, general purpose
|
457
|
+
tasks and task processing. Total, min, max and average time and count is
|
458
|
+
listed. If you have used version 0.0.7 it is adviced to delete tasks.log that
|
459
|
+
lives in ~/.tasks before upgrading or in ~/.syc/syctask after upgrading.
|
460
|
+
Otherwise the statistic results seem odd.
|
461
|
+
* Meeting time in time line now shows correct duration
|
462
|
+
* Info command searches for the location of a task and lists all task
|
463
|
+
task directories with the tasks contained.
|
464
|
+
* Plan move command sets the duration to the remaining processing time but at
|
465
|
+
least to 15 minutes
|
466
|
+
* With the setting command the default task directory can be set and general
|
467
|
+
purpose tasks can be created. A general purpose task can be used for tracking
|
468
|
+
to analyse how much time for phone calls is occupied.
|
469
|
+
setting -l list all general purpose tasks and the default task directory
|
470
|
+
* Prio command now takes a position flag together with the order flag to
|
471
|
+
determine where to insert the newly ordered tasks
|
472
|
+
* All commands that take an ID as argument (done, edit, start, update) look up
|
473
|
+
the task file associated to the id in the ids file. If it is found the
|
474
|
+
provided task directory is not considered for the task file. If the id is not
|
475
|
+
contained in the ids file the task is looked up in the provided directory
|
476
|
+
* Inspect command allows to list each today's unplanned task to edit, delete,
|
477
|
+
mark as done or plan
|
478
|
+
* Update command now has a duration flag to set the task's duration
|
479
|
+
|
480
|
+
==Tests
|
337
481
|
The test files live in the folder test and start with test_.
|
338
482
|
|
339
483
|
There is a rake file available to run all tests
|
data/bin/console_timer
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'rainbow'
|
5
|
+
|
6
|
+
# ConsoleTimer prints a task and the lead time at the upper right corner of the
|
7
|
+
# schreen. Invokation example:
|
8
|
+
# * Create a semaphore like id.track
|
9
|
+
# * Console time in a new ruby process
|
10
|
+
# semaphore = File.expand_path("~/.syc/syctask/#{id}.track"
|
11
|
+
# FileUtils.touch semaphore
|
12
|
+
# system "ruby lib/sycutil/console_timer.rb 60 10 semaphore"
|
13
|
+
# This will start the ConsoleTimer with a lead time of 1 minute for task 10.
|
14
|
+
# To stop the timer the semaphore has to be deleted
|
15
|
+
# FileUtils.rm semaphore
|
16
|
+
class ConsoleTimer
|
17
|
+
|
18
|
+
# Create a new ConsoleTimer with the time to count down, the task's ID and a
|
19
|
+
# semaphore. The semaphore is a file named id.track where id is equal to the
|
20
|
+
# provided id. The semaphore is checked for existence. If the semaphore is
|
21
|
+
# deleted than ConsoleTimer is stopped.
|
22
|
+
def initialize(time, id, semaphore)
|
23
|
+
@time = time.to_i
|
24
|
+
@id = id
|
25
|
+
@start = Time.now
|
26
|
+
@semaphore = semaphore
|
27
|
+
end
|
28
|
+
|
29
|
+
# Starts the timer. The timer is run as long the semaphore is available
|
30
|
+
def start
|
31
|
+
track = true
|
32
|
+
while track
|
33
|
+
sleep 1
|
34
|
+
output
|
35
|
+
track = File.exists? @semaphore
|
36
|
+
end
|
37
|
+
exit 0
|
38
|
+
end
|
39
|
+
|
40
|
+
# Prints the id and the lead time of the currently tracked task. As long as
|
41
|
+
# the provided time is greater than 0 the time is printed in green, otherwise
|
42
|
+
# red
|
43
|
+
def output
|
44
|
+
color = :green
|
45
|
+
difference = @time - (Time.now - @start).round
|
46
|
+
if difference < 0
|
47
|
+
difference = difference.abs
|
48
|
+
color = :red
|
49
|
+
end
|
50
|
+
seconds = difference % 60
|
51
|
+
minutes = difference / 60 % 60
|
52
|
+
hours = difference / 60 / 60 % 60
|
53
|
+
count_down = sprintf("%d: %02d:%02d:%02d", @id, hours, minutes, seconds)
|
54
|
+
size = count_down.size
|
55
|
+
count_down = count_down.color(color)
|
56
|
+
command = "tput sc;"+
|
57
|
+
"tput cup 0 $(($(tput cols) - #{size}));"+
|
58
|
+
"echo #{count_down};tput rc"
|
59
|
+
system command
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# Expects to receive parameters duration, id and semaphore
|
65
|
+
if ARGV.size == 3
|
66
|
+
duration = ARGV.shift
|
67
|
+
id = ARGV.shift
|
68
|
+
semaphore = ARGV.shift
|
69
|
+
timer = ConsoleTimer.new(duration, id, semaphore)
|
70
|
+
timer.start
|
71
|
+
else
|
72
|
+
exit -1
|
73
|
+
end
|
74
|
+
|
75
|
+
|
data/bin/syctask
CHANGED
@@ -3,13 +3,17 @@ require 'gli'
|
|
3
3
|
require 'syctask'
|
4
4
|
include GLI::App
|
5
5
|
include Syctime
|
6
|
+
include Syctask
|
6
7
|
|
7
8
|
program_desc 'A simple task manager'
|
8
9
|
|
9
10
|
version Syctask::VERSION
|
10
11
|
|
12
|
+
@settings = Syctask::Settings.new
|
13
|
+
@general_purpose_tasks = '|' + @settings.read_tasks.keys.join('|')
|
14
|
+
|
11
15
|
desc 'The directory where tasks are saved to'
|
12
|
-
default_value
|
16
|
+
default_value Syctask::WORK_DIR
|
13
17
|
arg_name 'TASK_DIR'
|
14
18
|
flag [:t,:taskdir]
|
15
19
|
|
@@ -17,6 +21,106 @@ desc 'Project name where tasks are saved'
|
|
17
21
|
arg_name 'PROJECT'
|
18
22
|
flag [:p, :project]
|
19
23
|
|
24
|
+
desc 'Settings for syctask'
|
25
|
+
command :settings do |c|
|
26
|
+
|
27
|
+
c.desc 'Define general purpose tasks to be used with the start command'
|
28
|
+
c.arg_name 'TASK'
|
29
|
+
c.flag [:g, :general], :must_match => /^\w+(?:,\w+)*/
|
30
|
+
|
31
|
+
c.desc 'Define the default task directory. System default is ~/.tasks'
|
32
|
+
c.arg_name 'DIR'
|
33
|
+
c.flag [:t, :taskdir]
|
34
|
+
|
35
|
+
c.desc 'List all settings'
|
36
|
+
c.switch [:l, :list]
|
37
|
+
|
38
|
+
c.action do |global_options,options,args|
|
39
|
+
settings = Syctask::Settings.new
|
40
|
+
filter = [:general, :taskdir, :list]
|
41
|
+
options.keep_if {|key, value| filter.find_index(key)}
|
42
|
+
if options[:general]
|
43
|
+
settings.tasks(options[:general])
|
44
|
+
end
|
45
|
+
|
46
|
+
if options[:taskdir]
|
47
|
+
set_taskdir = true
|
48
|
+
puts options[:taskdir]
|
49
|
+
dir = File.expand_path(options[:taskdir]) unless options[:taskdir].empty?
|
50
|
+
if dir
|
51
|
+
unless File.exists? dir
|
52
|
+
puts sprintf("Directory %s doesn't exist!", dir).color(:red)
|
53
|
+
print sprintf("%s ", "Create it (Y/n)?").color(:red)
|
54
|
+
set_taskdir = gets.chomp == 'Y'
|
55
|
+
if set_taskdir
|
56
|
+
FileUtils.mkdir_p dir
|
57
|
+
puts sprintf("--> Created directory %s", File.expand_path(dir)).
|
58
|
+
color(:green)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if set_taskdir
|
62
|
+
File.write(Syctask::DEFAULT_TASKS_DIR, dir)
|
63
|
+
puts sprintf("--> Set %s as default task directory", dir).
|
64
|
+
color(:green)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if options[:list]
|
70
|
+
puts sprintf("Default task directory: #{global_options[:taskdir]}").
|
71
|
+
color(:green)
|
72
|
+
general_purpose_tasks = settings.read_tasks
|
73
|
+
puts sprintf("%d general purpose tasks", general_purpose_tasks.size).
|
74
|
+
color(:green)
|
75
|
+
general_purpose_tasks.each do |key, value|
|
76
|
+
puts sprintf(" + %s (%s)", key, value).color(:green)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
desc 'Information about tasks and task directories'
|
85
|
+
command :info do |c|
|
86
|
+
|
87
|
+
c.desc 'Directory where to start searching the task'
|
88
|
+
c.arg_name 'DIR'
|
89
|
+
c.flag [:d, :dir]
|
90
|
+
|
91
|
+
c.desc 'Find task with the given ID'
|
92
|
+
c.arg_name 'ID'
|
93
|
+
c.flag [:i, :id], :must_match => /^\d+/
|
94
|
+
|
95
|
+
c.desc 'Show task directories'
|
96
|
+
c.switch [:t, :taskdir]
|
97
|
+
|
98
|
+
c.action do |global_options,options,args|
|
99
|
+
dir = options[:dir]
|
100
|
+
dir ||= "~"
|
101
|
+
dir = File.expand_path(dir)
|
102
|
+
help_now! "Directory #{dir} does not exists" unless File.exists? dir
|
103
|
+
id = options[:id]
|
104
|
+
if id
|
105
|
+
tasks = Syctask::get_files(dir, "#{id}.task")
|
106
|
+
tasks.each {|task| puts sprintf("%s", task).color(:green)}
|
107
|
+
puts sprintf("--> No task with ID %s found", id).
|
108
|
+
color(:red) if tasks.empty?
|
109
|
+
end
|
110
|
+
if options[:taskdir]
|
111
|
+
dirs = Syctask::get_task_dirs_and_count(dir)
|
112
|
+
task_count = 0
|
113
|
+
dirs.each do |key,value|
|
114
|
+
puts "#{key} (#{value})"
|
115
|
+
task_count += value
|
116
|
+
end
|
117
|
+
puts sprintf("--> %d task directories with %d tasks found",
|
118
|
+
dirs.length, task_count).color(:green)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
20
124
|
desc 'Create a new task'
|
21
125
|
arg_name 'TASK_TITLE'
|
22
126
|
command :new do |c|
|
@@ -27,11 +131,11 @@ command :new do |c|
|
|
27
131
|
|
28
132
|
c.desc 'Follow-up date'
|
29
133
|
c.arg_name 'FOLLOW-UP'
|
30
|
-
c.flag [:f, :follow_up], :must_match =>
|
134
|
+
c.flag [:f, :follow_up], :must_match => /today|tomorrow|\d{4}-\d{2}-\d{2}/
|
31
135
|
|
32
136
|
c.desc 'Due date'
|
33
137
|
c.arg_name 'DUE'
|
34
|
-
c.flag [:d, :due_date], :must_match =>
|
138
|
+
c.flag [:d, :due_date], :must_match => /today|tomorrow|\d{4}-\d{2}-\d{2}/
|
35
139
|
|
36
140
|
c.desc 'Description of the task'
|
37
141
|
c.arg_name 'DESCRIPTION'
|
@@ -46,6 +150,8 @@ command :new do |c|
|
|
46
150
|
c.flag [:t, :tags], :must_match => /^\w+(?:,\w+)*/
|
47
151
|
|
48
152
|
c.action do |global_options,options,args|
|
153
|
+
options[:follow_up] = extract_time(options[:f]) if options[:f]
|
154
|
+
options[:due_date] = extract_time(options[:d]) if options[:d]
|
49
155
|
filter = [:tags, :description, :prio, :due_date, :follow_up,
|
50
156
|
:note]
|
51
157
|
options.keep_if {|key, value| filter.find_index(key)}
|
@@ -55,6 +161,7 @@ command :new do |c|
|
|
55
161
|
end
|
56
162
|
task_numbers = nil
|
57
163
|
args.each do |title|
|
164
|
+
title.gsub!("\n","")
|
58
165
|
task_number = @service.create(global_options[:t], options, title)
|
59
166
|
add_task_to_plan @service.read(global_options[:t], task_number)
|
60
167
|
if task_numbers.nil?
|
@@ -178,33 +285,50 @@ command :list do |c|
|
|
178
285
|
end
|
179
286
|
end
|
180
287
|
|
181
|
-
desc 'Show
|
182
|
-
arg_name '
|
183
|
-
command :
|
288
|
+
desc 'Show statistics for time, count and optionally tags'
|
289
|
+
arg_name '[FROM[ TO]]'
|
290
|
+
command :statistics do |c|
|
184
291
|
|
185
|
-
c.desc '
|
186
|
-
c.switch [:
|
292
|
+
c.desc 'Show statistics for tags'
|
293
|
+
c.switch [:t, :tags]
|
187
294
|
|
188
295
|
c.action do |global_options,options,args|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
296
|
+
from = extract_time(args[0],true) if args[0]
|
297
|
+
from ||= ""
|
298
|
+
to = extract_time(args[1],true) if args[1]
|
299
|
+
to = from unless to
|
300
|
+
stats = Syctask::Statistics.new
|
301
|
+
STDOUT.puts stats.report(Syctask::TASKS_LOG, from, to)
|
302
|
+
puts "tags not implemented yet" if options[:tags]
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
desc 'Edit task in editor'
|
308
|
+
arg_name 'ID'
|
309
|
+
command :edit do |c|
|
310
|
+
|
311
|
+
c.action do |global_options,options,args|
|
312
|
+
task = @service.read(global_options[:taskdir], args[0])
|
313
|
+
if task
|
314
|
+
task_file = "#{task.dir}/#{task.id}.task"
|
315
|
+
if File.exists? task_file
|
316
|
+
system "vi #{task_file}" if File.exists? task_file
|
317
|
+
else
|
318
|
+
puts sprintf("--> Task %s doesn't exist", task_file).color(:red)
|
199
319
|
end
|
320
|
+
else
|
321
|
+
puts sprintf("--> Task with ID %s doesnt't exist", args[0]).color(:red)
|
200
322
|
end
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
desc 'Inspect tasks and edit, delete and mark as done'
|
328
|
+
command :inspect do |c|
|
329
|
+
|
330
|
+
c.action do |global_options,options,args|
|
331
|
+
@planner.inspect_tasks(@service.find(global_options[:t], options, false))
|
208
332
|
end
|
209
333
|
|
210
334
|
end
|
@@ -223,7 +347,7 @@ command :plan do |c|
|
|
223
347
|
|
224
348
|
c.desc 'Move planned task to another day'
|
225
349
|
c.arg_name 'DATE'
|
226
|
-
c.flag [:m, :move]
|
350
|
+
c.flag [:m, :move],:must_match => /yesterday|today|tomorrow|\d{4}-\d{2}-\d{2}/
|
227
351
|
|
228
352
|
c.desc 'Filter for ID'
|
229
353
|
c.arg_name 'ID1,ID2,ID3|[<|=|>]ID'
|
@@ -376,13 +500,19 @@ command :prio do |c|
|
|
376
500
|
c.arg_name 'ID1,ID2,ID3,...'
|
377
501
|
c.flag [:o, :order], :must_match => /^\d+(?:,\d+)*/
|
378
502
|
|
503
|
+
c.desc 'Put task at specified position'
|
504
|
+
c.arg_name 'POS'
|
505
|
+
c.flag [:p, :position], :must_match => /first|FIRST|last|LAST|\d+/
|
506
|
+
|
379
507
|
c.action do |global_options,options,args|
|
380
508
|
time = extract_time(args[0])
|
381
509
|
planner = Syctask::TaskPlanner.new
|
382
510
|
if options[:order]
|
383
|
-
|
384
|
-
|
385
|
-
|
511
|
+
options[:position] ||= 0
|
512
|
+
ordered, rest, pos = planner.order_tasks(time, options[:order].split(","),
|
513
|
+
options[:position])
|
514
|
+
puts sprintf("--> put %d task%s out of %s into new order at position %d",
|
515
|
+
ordered, "#{'s' if ordered > 1}", rest, pos).color(:green)
|
386
516
|
else
|
387
517
|
planner.prioritize_tasks(time)
|
388
518
|
end
|
@@ -390,43 +520,52 @@ command :prio do |c|
|
|
390
520
|
end
|
391
521
|
|
392
522
|
desc 'Start tracking a task'
|
393
|
-
arg_name
|
523
|
+
arg_name "TASK_ID#{@general_purpose_tasks}"
|
394
524
|
command :start do |c|
|
395
525
|
|
526
|
+
c.desc 'Show task timer'
|
527
|
+
c.switch [:t, :timer]
|
528
|
+
|
396
529
|
c.desc 'List currently tracked task'
|
397
530
|
c.switch [:l, :list]
|
398
531
|
|
399
|
-
c.desc '
|
532
|
+
c.desc 'ID of planned task'
|
400
533
|
c.switch [:p, :plan]
|
401
534
|
|
402
535
|
c.action do |global_options,options,args|
|
536
|
+
if args.empty? and not options[:l] and not options[:p]
|
537
|
+
help_now! sprintf("%s", "no arguments and options given").color(:red)
|
538
|
+
end
|
403
539
|
tracker = Syctask::TaskTracker.new
|
404
540
|
if args[0]
|
405
541
|
if options[:plan]
|
406
|
-
task = @planner.get_tasks
|
542
|
+
task = @planner.get_tasks(Time.now.strftime("%Y-%m-%d"),
|
543
|
+
{id: args[0]})[0]
|
407
544
|
else
|
408
545
|
task = @service.read(global_options[:taskdir], args[0])
|
409
546
|
end
|
410
547
|
help_now! sprintf("%s",
|
411
548
|
"no task with id #{args[0]}").color(:red) unless task
|
412
|
-
started, stopped = tracker.start(task)
|
549
|
+
started, stopped = tracker.start(task, options[:timer])
|
550
|
+
if stopped
|
551
|
+
puts sprintf("--> stopped %s",
|
552
|
+
"#{stopped.id} - #{stopped.title}").color(:yellow)
|
553
|
+
puts sprintf(" %s", "#{string_for_seconds(stopped.lead_time)}").
|
554
|
+
color(:yellow)
|
555
|
+
end
|
413
556
|
if started
|
414
|
-
puts sprintf("-->
|
557
|
+
puts sprintf("--> started %s",
|
415
558
|
"#{task.id} - #{task.title}").color(:green)
|
416
559
|
else
|
417
|
-
puts sprintf("--> allready
|
560
|
+
puts sprintf("--> allready started %s",
|
418
561
|
"#{task.id} - #{task.title}").color(:red)
|
419
562
|
end
|
420
|
-
|
421
|
-
puts sprintf("--> stopped %s",
|
422
|
-
"#{stopped.id} - #{stopped.title}").color(:green)
|
423
|
-
puts sprintf(" %s", "#{string_for_seconds(stopped.lead_time)}").color(:green)
|
424
|
-
end
|
425
|
-
end
|
563
|
+
end
|
426
564
|
if options[:list]
|
427
565
|
task = tracker.tracked_task
|
428
566
|
puts sprintf("%s", "--> no task tracked").color(:red) unless task
|
429
|
-
puts sprintf("%s", "--> #{task.id} - #{task.title}").
|
567
|
+
puts sprintf("%s", "--> #{task.id} - #{task.title}").
|
568
|
+
color(:green) if task
|
430
569
|
end
|
431
570
|
end
|
432
571
|
end
|
@@ -438,7 +577,8 @@ command :stop do |c|
|
|
438
577
|
task = tracker.stop
|
439
578
|
if task
|
440
579
|
puts sprintf("--> stopped %s", "#{task.id} - #{task.title}").color(:green)
|
441
|
-
puts sprintf(" %4s", "#{string_for_seconds(task.lead_time)}").
|
580
|
+
puts sprintf(" %4s", "#{string_for_seconds(task.lead_time)}").
|
581
|
+
color(:green)
|
442
582
|
else
|
443
583
|
puts sprintf("--> %s", "no task tracked").color(:red)
|
444
584
|
end
|
@@ -446,7 +586,7 @@ command :stop do |c|
|
|
446
586
|
end
|
447
587
|
|
448
588
|
desc 'Update the task'
|
449
|
-
arg_name '
|
589
|
+
arg_name 'ID'
|
450
590
|
command :update do |c|
|
451
591
|
c.desc 'Priority of the task, 1 highes priority'
|
452
592
|
c.arg_name 'PRIO'
|
@@ -454,11 +594,15 @@ command :update do |c|
|
|
454
594
|
|
455
595
|
c.desc 'Follow-up date'
|
456
596
|
c.arg_name 'FOLLOW-UP'
|
457
|
-
c.flag [:f, :follow_up], :must_match =>
|
597
|
+
c.flag [:f, :follow_up], :must_match => /today|tomorrow|\d{4}-\d{2}-\d{2}/
|
458
598
|
|
459
599
|
c.desc 'Due date'
|
460
600
|
c.arg_name 'DUE'
|
461
|
-
c.flag [:d, :due_date], :must_match =>
|
601
|
+
c.flag [:d, :due_date], :must_match => /today|tomorrow|\d{4}-\d{2}-\d{2}/
|
602
|
+
|
603
|
+
c.desc 'Duration'
|
604
|
+
c.arg_name 'DURATION'
|
605
|
+
c.flag :duration, :must_match => /\d+/
|
462
606
|
|
463
607
|
c.desc 'Description of the task'
|
464
608
|
c.arg_name 'DESCRIPTION'
|
@@ -473,8 +617,10 @@ command :update do |c|
|
|
473
617
|
c.flag [:t, :tags], :must_match => /^\w+(?:,\w+)*/
|
474
618
|
|
475
619
|
c.action do |global_options,options,args|
|
476
|
-
help_now! "
|
477
|
-
|
620
|
+
help_now! "ID required" if args.empty?
|
621
|
+
options[:follow_up] = extract_time(options[:f]) if options[:f]
|
622
|
+
options[:due_date] = extract_time(options[:d]) if options[:d]
|
623
|
+
filter = [:tags, :description, :prio, :due_date, :follow_up, :duration,
|
478
624
|
:note]
|
479
625
|
options.keep_if {|key, value| filter.find_index(key) and value != nil}
|
480
626
|
|
@@ -482,15 +628,15 @@ command :update do |c|
|
|
482
628
|
|
483
629
|
add_task_to_plan @service.read(global_options[:t], args[0]) if success
|
484
630
|
|
485
|
-
STDOUT.puts sprintf("-->
|
631
|
+
STDOUT.puts sprintf("--> updated task %s ",
|
486
632
|
args[0]).color(:green) if success
|
487
|
-
STDOUT.puts sprintf("--> could not update task
|
633
|
+
STDOUT.puts sprintf("--> could not update task %s ",
|
488
634
|
args[0]).color(:red) unless success
|
489
635
|
end
|
490
636
|
end
|
491
637
|
|
492
638
|
desc 'Mark task as done'
|
493
|
-
arg_name '
|
639
|
+
arg_name 'ID'
|
494
640
|
command :done do |c|
|
495
641
|
c.desc 'Print task after marked as done'
|
496
642
|
c.switch [:p, :print]
|
@@ -503,12 +649,12 @@ command :done do |c|
|
|
503
649
|
|
504
650
|
c.action do |global_options,options,args|
|
505
651
|
help_now! sprintf("%s",
|
506
|
-
'
|
652
|
+
'ID is required').color(:red) if args.empty?
|
507
653
|
task = @service.read(global_options[:t], args[0])
|
508
|
-
exit_now!("Task with
|
654
|
+
exit_now!("Task with ID #{args[0]} does not exist") unless task
|
509
655
|
task.done(options[:note])
|
510
|
-
@service.save(
|
511
|
-
STDOUT.puts sprintf("Marked task
|
656
|
+
@service.save(task.dir, task)
|
657
|
+
STDOUT.puts sprintf("--> Marked task %d as done", args[0]).color(:green)
|
512
658
|
task.print_pretty(options[:c]) if options[:p]
|
513
659
|
end
|
514
660
|
end
|
@@ -520,15 +666,33 @@ pre do |global,command,options,args|
|
|
520
666
|
# Use skips_pre before a command to skip this block
|
521
667
|
# on that command only
|
522
668
|
|
669
|
+
proceed = true
|
670
|
+
|
671
|
+
Syctask::check_environment
|
672
|
+
|
523
673
|
@service = Syctask::TaskService.new
|
524
674
|
@planner = Syctask::TaskPlanner.new
|
525
675
|
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
676
|
+
if command.name == :start and not args[0].nil? and @settings.
|
677
|
+
read_tasks[args[0].upcase]
|
678
|
+
global[:taskdir] = Syctask::SYC_DIR
|
679
|
+
args[0] = @settings.read_tasks[args[0].upcase].to_s
|
680
|
+
else
|
681
|
+
dir = File.expand_path(global[:t])
|
682
|
+
dir += "/" + global[:p] if global[:p]
|
683
|
+
global[:taskdir] = global[:t] = dir.squeeze("/")
|
684
|
+
end
|
530
685
|
|
531
|
-
|
686
|
+
if command.name == :new and not File.exists? global[:taskdir]
|
687
|
+
puts sprintf("Directory %s doesn't exist!", global[:taskdir]).color(:red)
|
688
|
+
print sprintf("%s", "Create it (Y/n)? ").color(:red)
|
689
|
+
proceed = STDIN.gets.chomp == 'Y'
|
690
|
+
if proceed
|
691
|
+
FileUtils.mkdir_p global[:taskdir]
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
proceed
|
532
696
|
end
|
533
697
|
|
534
698
|
post do |global,command,options,args|
|
@@ -540,27 +704,41 @@ end
|
|
540
704
|
on_error do |exception|
|
541
705
|
# Error logic here
|
542
706
|
# return false to skip default error handling
|
543
|
-
|
707
|
+
if exception.to_s == "exit" and exception.status == 0
|
708
|
+
false
|
709
|
+
else
|
710
|
+
true
|
711
|
+
end
|
544
712
|
end
|
545
713
|
|
546
|
-
# Extracts the time out of a time string. Accepts 'today', 'tomorrow'
|
547
|
-
# in the form 'YYYY-MM-DD'. Returns the date contained in
|
548
|
-
|
714
|
+
# Extracts the time out of a time string. Accepts 'today', 'tomorrow',
|
715
|
+
# 'yesterday' or a date in the form 'YYYY-MM-DD'. Returns the date contained in
|
716
|
+
# the time_string or if time = true in a Time object
|
717
|
+
def extract_time(time_string,time=false)
|
549
718
|
time_string = 'today' if time_string.nil?
|
550
719
|
case time_string.downcase
|
551
720
|
when 'today'
|
552
|
-
date = Time.now
|
721
|
+
date = Time.now
|
722
|
+
date = date.strftime("%Y-%m-%d") unless time
|
553
723
|
when 'tomorrow'
|
554
|
-
date =
|
724
|
+
date = Time.now + 60 * 60 * 24
|
725
|
+
date = date.strftime("%Y-%m-%d") unless time
|
726
|
+
when 'yesterday'
|
727
|
+
date = Time.now - (60 * 60 * 24)
|
728
|
+
date = date.strftime("%Y-%m-%d") unless time
|
555
729
|
else
|
556
730
|
if time_string.match(/\d{4}-\d{2}-\d{2}/)
|
557
731
|
date = time_string
|
732
|
+
date = Time.xmlschema("#{time_string}T00:00:00") if time
|
558
733
|
elsif nil
|
559
|
-
date = Time.now
|
734
|
+
date = Time.now
|
735
|
+
date = date.strftime("%Y-%m-%d") unless time
|
560
736
|
else
|
561
|
-
help_now! "Arguments may be 'today', 'tomorrow', YYYY-MM-DD
|
737
|
+
help_now! "Arguments may be 'yesterday', 'today', 'tomorrow', YYYY-MM-DD"+
|
738
|
+
" or <RETURN>"
|
562
739
|
end
|
563
740
|
end
|
741
|
+
date
|
564
742
|
end
|
565
743
|
|
566
744
|
# Add task to task plan
|