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/lib/syctask.rb
CHANGED
@@ -5,9 +5,12 @@ require 'syctask/task_service.rb'
|
|
5
5
|
require 'syctask/task_scheduler.rb'
|
6
6
|
require 'syctask/task_planner.rb'
|
7
7
|
require 'syctask/schedule.rb'
|
8
|
-
require 'sycutil/console.rb'
|
9
8
|
require 'syctask/task_tracker.rb'
|
9
|
+
require 'syctask/environment.rb'
|
10
|
+
require 'sycutil/console.rb'
|
10
11
|
require 'syctime/time_util.rb'
|
12
|
+
require 'syctask/settings.rb'
|
13
|
+
require 'syctask/statistics.rb'
|
11
14
|
|
12
15
|
# Add requires for other files you add to your project here, so
|
13
16
|
# you just need to require this one file in your bin file
|
data/lib/syctask/environment.rb
CHANGED
@@ -1,6 +1,431 @@
|
|
1
1
|
module Syctask
|
2
2
|
|
3
|
-
#
|
4
|
-
|
3
|
+
# System directory of syctask
|
4
|
+
SYC_DIR = File.expand_path('~/.syc/syctask')
|
5
|
+
# ID file where the last issued ID is saved
|
6
|
+
ID = SYC_DIR + "/id"
|
7
|
+
# File that contains all issued IDs
|
8
|
+
IDS = SYC_DIR + "/ids"
|
9
|
+
# File with tags
|
10
|
+
TAGS = SYC_DIR + "/tags"
|
11
|
+
# File with the general purpose tasks
|
12
|
+
DEFAULT_TASKS = SYC_DIR + "/default_tasks"
|
13
|
+
# File that holds the default task directory
|
14
|
+
DEFAULT_TASKS_DIR = SYC_DIR + "/default_tasks_dir"
|
15
|
+
# Log file that logs all activities of syctask like creation of tasks
|
16
|
+
TASKS_LOG = SYC_DIR + "/tasks.log"
|
17
|
+
# File that holds the tracked task
|
18
|
+
TRACKED_TASK = SYC_DIR + "/tracked_tasks"
|
19
|
+
# If files are re-indexed during re-indexing these tasks are save here
|
20
|
+
RIDX_LOG = SYC_DIR + "/reindex.log"
|
21
|
+
|
22
|
+
# Reads the default task directory from the DEFAULT_TASKS_DIR file if it
|
23
|
+
# exists. If it exist but doesn't contain a valid directory ~/.tasks is
|
24
|
+
# returned as default tasks directory
|
25
|
+
dir = File.read(DEFAULT_TASKS_DIR) if File.exists? DEFAULT_TASKS_DIR
|
26
|
+
# User specified default working directory
|
27
|
+
work_dir = dir if not dir.nil? and not dir.empty? and File.exists? dir
|
28
|
+
# Set eather user defined work directory or default
|
29
|
+
WORK_DIR = work_dir.nil? ? File.expand_path('~/.tasks') : work_dir
|
30
|
+
|
31
|
+
# Logs a task regarding create, update, done, delete
|
32
|
+
def log_task(type, task)
|
33
|
+
File.open(TASKS_LOG, 'a') do |file|
|
34
|
+
log_entry = "#{type.to_s};"
|
35
|
+
log_entry += "#{task.id};#{task.dir};"
|
36
|
+
log_entry += "#{task.title.gsub(';', '\'semicolon\'')};"
|
37
|
+
log_entry += "#{Time.now};"
|
38
|
+
log_entry += "#{Time.now}"
|
39
|
+
file.puts log_entry
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Logs the work time
|
44
|
+
def log_work_time(type, work_time)
|
45
|
+
today = Time.now
|
46
|
+
begins = Time.local(today.year,
|
47
|
+
today.mon,
|
48
|
+
today.day,
|
49
|
+
work_time[0],
|
50
|
+
work_time[1],
|
51
|
+
0)
|
52
|
+
ends = Time.local(today.year,
|
53
|
+
today.mon,
|
54
|
+
today.day,
|
55
|
+
work_time[2],
|
56
|
+
work_time[3],
|
57
|
+
0)
|
58
|
+
entry = "#{type};-1;;work;#{begins};#{ends}\n"
|
59
|
+
logs = File.read(TASKS_LOG)
|
60
|
+
return if logs.scan(entry)[0]
|
61
|
+
time_pat = "#{today.strftime("%Y-%m-%d")} \\d{2}:\\d{2}:\\d{2} [+-]\\d{4}"
|
62
|
+
pattern = %r{#{type};-1;;work;#{time_pat};#{time_pat}\n}
|
63
|
+
log = logs.scan(pattern)[0]
|
64
|
+
if log and logs.sub!(log, entry)
|
65
|
+
File.write(TASKS_LOG, logs)
|
66
|
+
else
|
67
|
+
File.open(TASKS_LOG, 'a') {|f| f.puts entry}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Logs meeting times
|
72
|
+
def log_meetings(type, busy_time, meetings)
|
73
|
+
today = Time.now
|
74
|
+
logs = File.read(TASKS_LOG)
|
75
|
+
time_pat = "#{today.strftime("%Y-%m-%d")} \\d{2}:\\d{2}:\\d{2} [+-]\\d{4}"
|
76
|
+
pattern = %r{#{type};-2;;.*?;#{time_pat};#{time_pat}\n}
|
77
|
+
logs.gsub!(pattern, "")
|
78
|
+
busy_time.each_with_index do |busy,i|
|
79
|
+
begins = Time.local(today.year,today.mon,today.day,busy[0],busy[1],0)
|
80
|
+
ends = Time.local(today.year,today.mon,today.day,busy[2],busy[3],0)
|
81
|
+
meeting = meetings[i] ? meetings[i] : "Meeting #{i}"
|
82
|
+
logs << "#{type};-2;;#{meeting};#{begins};#{ends}\n"
|
83
|
+
end
|
84
|
+
File.write(TASKS_LOG, logs)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Checks whether all files are available that are needed for syctask's
|
88
|
+
# operation
|
89
|
+
def check_environment
|
90
|
+
FileUtils.mkdir_p WORK_DIR unless File.exists? WORK_DIR
|
91
|
+
unless viable?
|
92
|
+
unless get_files(File.expand_path("~"), "*.task").empty?
|
93
|
+
# Backup ARGV content
|
94
|
+
args = []
|
95
|
+
ARGV.each {|arg| args << arg} unless ARGV.empty?
|
96
|
+
ARGV.clear
|
97
|
+
puts
|
98
|
+
puts "Warning:"
|
99
|
+
puts "-------"
|
100
|
+
puts "There are missing system files of syc-task, even though tasks "+
|
101
|
+
"are available."
|
102
|
+
puts "If you have upgraded from version 0.0.7 or below than this is "+
|
103
|
+
"due to a changed\nfile structure. For changes in version "+
|
104
|
+
"greater 0.0.7 see"
|
105
|
+
puts "--> https://rubygems.org/gems/syc-task"
|
106
|
+
puts "Or you have accidentially deleted system files. In both cases "+
|
107
|
+
"re-indexing\nwill recover syc-task."
|
108
|
+
print "Do you want to recover syc-task (y/n)? "
|
109
|
+
answer = gets.chomp
|
110
|
+
exit -1 unless answer.downcase == "y"
|
111
|
+
reindex_tasks(File.expand_path("~"))
|
112
|
+
puts "Successfully recovered syc-task"
|
113
|
+
puts "-> A log file of re-indexed tasks can be found at\n"+
|
114
|
+
"#{RIDX_LOG}" if File.exists? RIDX_LOG
|
115
|
+
print "Press any key to continue "
|
116
|
+
gets
|
117
|
+
# Restore ARGV content
|
118
|
+
args.each {|arg| ARGV << arg} unless args.empty?
|
119
|
+
else
|
120
|
+
FileUtils.mkdir_p SYC_DIR unless File.exists? SYC_DIR
|
121
|
+
File.write(ID, "0")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Checks if system files are available that are needed for running syc-task.
|
127
|
+
# Returns true if neccessary system files are available, otherwise false.
|
128
|
+
def viable?
|
129
|
+
File.exists? SYC_DIR and File.exists? ID
|
130
|
+
end
|
131
|
+
|
132
|
+
# Re-indexing of tasks is done when tasks are available but SYC_DIR or ID file
|
133
|
+
# is missing. The ID file contains the last issued task ID. The ID file is
|
134
|
+
# referenced for obtaining the next ID for a new task. Re-indexing is done as
|
135
|
+
# follows:
|
136
|
+
# * Retrieve all tasks in and below the given directory *root*
|
137
|
+
# * Determine the highest ID number and add it to the ID file
|
138
|
+
# * Determine all tasks that don't have a unique ID
|
139
|
+
# * Re-index all tasks not having a unique ID and rename the file names
|
140
|
+
# accordingly
|
141
|
+
# * Adjust the IDs in the planned_tasks, tasks.log and tracked_tasks files
|
142
|
+
# * Copy all system files planned_tasks, time_schedule, tasks.log, id to the
|
143
|
+
# SYC_DIR directory if not already in the SYC_DIR directory. This should
|
144
|
+
# only be if upgrading from version 0.0.7 and below.
|
145
|
+
def reindex_tasks(root)
|
146
|
+
FileUtils.mkdir_p SYC_DIR unless File.exists? SYC_DIR
|
147
|
+
new_id = {}
|
148
|
+
to_be_renamed = {}
|
149
|
+
root = File.expand_path(root)
|
150
|
+
puts "-> Collect task files..."
|
151
|
+
task_files = task_files(root)
|
152
|
+
puts "-> Restore ID counter..."
|
153
|
+
initialize_id(task_files)
|
154
|
+
print "-> Start re-indexing now..."
|
155
|
+
collect_by_id(task_files).each do |id, files|
|
156
|
+
next if files.size < 2
|
157
|
+
files.each_with_index do |file,i|
|
158
|
+
next if i == 0 # need to re-index only second and following tasks
|
159
|
+
result = reindex_task(file)
|
160
|
+
# associate old id to new id and dir name
|
161
|
+
if new_id[result[:old_id]].nil?
|
162
|
+
new_id[result[:old_id]] = {result[:dirname] => result[:new_id]}
|
163
|
+
else
|
164
|
+
new_id[result[:old_id]][result[:dirname]] = result[:new_id]
|
165
|
+
end
|
166
|
+
# assign tmp_file to new_file for later renaming
|
167
|
+
to_be_renamed[result[:tmp_file]] = result[:new_file]
|
168
|
+
# document the re-indexing of tasks
|
169
|
+
log_reindexing(result[:old_id], result[:new_id], result[:new_file])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
to_be_renamed.each {|old_name,new_name| File.rename(old_name, new_name)}
|
173
|
+
puts
|
174
|
+
puts "-> Update task log file"
|
175
|
+
update_tasks_log(root, new_id)
|
176
|
+
puts "-> Update planned tasks files"
|
177
|
+
update_planned_tasks(root, new_id)
|
178
|
+
puts "-> Move schedule files..."
|
179
|
+
move_time_schedule_files(root)
|
180
|
+
puts "-> Update tracked task file..."
|
181
|
+
update_tracked_task(root)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Re-indexes the tasks' IDs and renames the task files to match the new ID.
|
185
|
+
# The orginal file is deleted. Returns old_id, new_id, tmp_file_name and
|
186
|
+
# new_file_name. The task is save with the tmp_file_name in case the new ID
|
187
|
+
# and hence the new_file_name exists already from a not yet re-indexed task.
|
188
|
+
# After all tasks are re-indexed the tmp_file_names have to be renamed to the
|
189
|
+
# new_file_names. The renaming is in the responsibility of the calling method.
|
190
|
+
def reindex_task(file)
|
191
|
+
print "."
|
192
|
+
task = File.read(file)
|
193
|
+
old_id = task.scan(/(?<=^id: )\d+$/)[0]
|
194
|
+
new_id = next_id.to_s
|
195
|
+
task.gsub!(/(?<=^id: )\d+$/, new_id)
|
196
|
+
dirname = File.dirname(file)
|
197
|
+
new_file = "#{dirname}/#{new_id}.task"
|
198
|
+
tmp_file = "#{new_file}_"
|
199
|
+
File.write(tmp_file, task)
|
200
|
+
File.delete(file)
|
201
|
+
{old_id: old_id,
|
202
|
+
new_id: new_id,
|
203
|
+
tmp_file: tmp_file,
|
204
|
+
new_file: new_file,
|
205
|
+
dirname: dirname}
|
206
|
+
end
|
207
|
+
|
208
|
+
# Determines the greatest task ID out of the provided tasks and saves it to
|
209
|
+
# the ID file
|
210
|
+
def initialize_id(tasks)
|
211
|
+
pattern = %r{(?<=\/)\d+(?=\.task)}
|
212
|
+
tasks.sort_by! {|t| t.scan(pattern)[0].to_i}
|
213
|
+
save_id(tasks[tasks.size-1].scan(pattern)[0].to_i)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Saves the ids to ids file
|
217
|
+
def save_ids(id, file)
|
218
|
+
entry = "#{id},#{file}"
|
219
|
+
return if File.exists? IDS and not File.read(IDS).scan(entry).empty?
|
220
|
+
File.open(IDS, 'a') {|f| f.puts entry}
|
221
|
+
end
|
222
|
+
|
223
|
+
# Save the id to the ID file. Returns the id when save was successful
|
224
|
+
def save_id(id)
|
225
|
+
File.write(ID,id)
|
226
|
+
id
|
227
|
+
end
|
228
|
+
|
229
|
+
# Retrieve the next unassigned task id
|
230
|
+
def next_id
|
231
|
+
id = File.read(ID).to_i + 1
|
232
|
+
save_id(id)
|
233
|
+
id
|
234
|
+
end
|
235
|
+
|
236
|
+
# Logs if a task is re-indexed
|
237
|
+
def log_reindexing(old_id, new_id, file)
|
238
|
+
entry = "#{old_id},#{new_id},#{file}"
|
239
|
+
return if File.exists? RIDX_LOG and not File.read(RIDX_LOG).
|
240
|
+
scan(entry).empty?
|
241
|
+
File.open(RIDX_LOG, 'a') {|f| f.puts entry}
|
242
|
+
end
|
243
|
+
|
244
|
+
# Updates the tasks.log file if tasks are re-indexed with the task's new ids
|
245
|
+
def update_tasks_log(dir, new_ids)
|
246
|
+
tasks_log_files(dir).each do |file|
|
247
|
+
logs = File.readlines(file)
|
248
|
+
logs.each_with_index do |log,i|
|
249
|
+
type = log.scan(/^.*?(?=;)/)[0]
|
250
|
+
logs[i] = log.sub!("-",";") if log.scan(/(?<=^#{type};)\d+-/)[0]
|
251
|
+
old_id = log.scan(/(?<=^#{type};)\d+(?=;)/)[0]
|
252
|
+
next unless new_ids[old_id]
|
253
|
+
task_dir = log.scan(/(?<=^#{type};#{old_id};).*?(?=;)/)[0]
|
254
|
+
next unless new_ids[old_id][task_dir]
|
255
|
+
logs[i] = log.sub("#{old_id};#{task_dir}",
|
256
|
+
"#{new_ids[old_id][task_dir]};#{task_dir}")
|
257
|
+
end
|
258
|
+
if file == TASKS_LOG
|
259
|
+
File.write(TASKS_LOG, logs.join)
|
260
|
+
else
|
261
|
+
#TODO only append a line if it is not already available in TASKS_LOG
|
262
|
+
File.open(TASKS_LOG, 'a') {|f| f.puts logs.join}
|
263
|
+
FileUtils.rm file
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# TODO delete
|
269
|
+
def update_tasks_log_old(dir, old_id, new_id, file)
|
270
|
+
old_entry = "#{old_id}-#{File.dirname(file)}"
|
271
|
+
# Append '/' to dir name so already updated task is not subsequently updated
|
272
|
+
new_entry = "#{new_id}-#{File.dirname(file)}/"
|
273
|
+
@tasks_log_files = tasks_log_files(dir) if @tasks_log_files.nil?
|
274
|
+
@tasks_log_files.each do |f|
|
275
|
+
tasks_log = File.read(f).gsub(old_entry, new_entry)
|
276
|
+
File.write(f, tasks_log)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Replaces the old ids with the new ids in the planned tasks files. A planned
|
281
|
+
# tasks file has the form '2013-03-03_planned_tasks' and lives until syctask's
|
282
|
+
# version 0.0.7 in ~/.tasks directory. From version 0.1.0 on the planned tasks
|
283
|
+
# files live in the ~/.syc/syctask directory. So the calling method has the
|
284
|
+
# responsibility to copy or move the planned tasks files after they have been
|
285
|
+
# updated to the new planned tasks directory.
|
286
|
+
def update_planned_tasks(dir, new_ids)
|
287
|
+
planned_tasks_files(dir).each do |file|
|
288
|
+
tasks = File.readlines(file)
|
289
|
+
tasks.each_with_index do |task,i|
|
290
|
+
task_dir, old_id = task.chomp.split(',')
|
291
|
+
next unless new_ids[old_id]
|
292
|
+
next unless new_ids[old_id][task_dir]
|
293
|
+
tasks[i] = "#{task_dir},#{new_ids[old_id][task_dir]}"
|
294
|
+
end
|
295
|
+
File.write("#{SYC_DIR}/#{File.basename(file)}", tasks.join("\n"))
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# TODO delete
|
300
|
+
def update_planned_tasks_old(dir, old_id, new_id, file)
|
301
|
+
old_entry = "#{File.dirname(file)},#{old_id}"
|
302
|
+
# Append '/' to dir name so already updated task is not subsequently updated
|
303
|
+
new_entry = "#{File.dirname(file)}/,#{new_id}"
|
304
|
+
@planned_tasks_files = planned_tasks_files(dir) if @planned_tasks_files.nil?
|
305
|
+
@planned_tasks_files.each do |file|
|
306
|
+
planned_tasks = File.read(file).gsub(old_entry, new_entry)
|
307
|
+
File.write(file, planned_tasks)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Updates tracked_tasks file if task has been re-indexed with new ID
|
312
|
+
def update_tracked_task(dir)
|
313
|
+
@tracked = get_files(dir, "tracked_tasks") if @tracked.nil?
|
314
|
+
return if @tracked.empty?
|
315
|
+
task = File.read(@tracked[0])
|
316
|
+
if File.exists? RIDX_LOG
|
317
|
+
old_id = task.scan(/(?<=id: )\d+$/)
|
318
|
+
old_dir = task.scan(/(?<=dir: ).*$/)
|
319
|
+
return if old_id.empty? or old_dir.empty?
|
320
|
+
pattern = %r{(?<=#{old_id[0]},)\d+(?=,#{old_dir[0]}\/\d+\.task)}
|
321
|
+
new_id = File.read(RIDX_LOG).scan(pattern)
|
322
|
+
task.gsub!("id: #{old_id}", "id: #{new_id}")
|
323
|
+
end
|
324
|
+
File.write(TRACKED_TASK, task)
|
325
|
+
FileUtils.rm @tracked[0] unless TRACKED_TASK == @tracked[0]
|
326
|
+
end
|
327
|
+
|
328
|
+
# Extracts tasks that have no unique id
|
329
|
+
def collect_by_id(tasks)
|
330
|
+
extract = {}
|
331
|
+
tasks.each do |task|
|
332
|
+
id = task.scan(/(?<=\/)\d+(?=\.task$)/)[0]
|
333
|
+
extract[id].nil? ? extract[id] = [task] : extract[id] << task
|
334
|
+
end
|
335
|
+
extract
|
336
|
+
end
|
337
|
+
|
338
|
+
# Retrieves all task files in and below the provided dir. Returns an array of
|
339
|
+
# task files
|
340
|
+
def task_files(dir)
|
341
|
+
get_files(dir, "*.task").keep_if {|file| file.match /\d+\.task$/}
|
342
|
+
end
|
343
|
+
|
344
|
+
# Retrieves all planned task files in and below the given directory
|
345
|
+
def planned_tasks_files(dir)
|
346
|
+
pattern = %r{\d{4}-\d{2}-\d{2}_planned_tasks}
|
347
|
+
get_files(dir, "*planned_tasks").keep_if {|f| f.match(pattern)}
|
348
|
+
end
|
349
|
+
|
350
|
+
# Retrieves all schedule files in and below the given directory
|
351
|
+
def time_schedule_files(dir)
|
352
|
+
pattern = %r{\d{4}-\d{2}-\d{2}_time_schedule}
|
353
|
+
get_files(dir, "*time_schedule").keep_if {|f| f.match(pattern)}
|
354
|
+
end
|
355
|
+
|
356
|
+
# Retrieves als tasks.log files in and below the given directory
|
357
|
+
def tasks_log_files(dir)
|
358
|
+
get_files(dir, "tasks.log")
|
359
|
+
end
|
360
|
+
|
361
|
+
# Retrieves all files that meet the pattern in and below the given directory
|
362
|
+
def get_files(dir, pattern)
|
363
|
+
original_dir = File.expand_path(".")
|
364
|
+
Dir.chdir(dir)
|
365
|
+
files = Dir.glob("**/#{pattern}", File::FNM_DOTMATCH).map do |f|
|
366
|
+
File.expand_path(f)
|
367
|
+
end
|
368
|
+
Dir.chdir(original_dir)
|
369
|
+
files
|
370
|
+
end
|
371
|
+
|
372
|
+
# Retrieve all directories that contain tasks
|
373
|
+
def get_task_dirs(dir)
|
374
|
+
original_dir = File.expand_path(".")
|
375
|
+
Dir.chdir(dir)
|
376
|
+
dirs = Dir.glob("**/*.task", File::FNM_DOTMATCH).map do |f|
|
377
|
+
File.dirname(File.expand_path(f))
|
378
|
+
end
|
379
|
+
Dir.chdir(original_dir)
|
380
|
+
dirs.uniq
|
381
|
+
end
|
382
|
+
|
383
|
+
# Retrieves all directories that contain tasks and the count of contained
|
384
|
+
# tasks in and below the provided directory
|
385
|
+
def get_task_dirs_and_count(dir)
|
386
|
+
original_dir = File.expand_path(".")
|
387
|
+
Dir.chdir(dir)
|
388
|
+
dirs_and_count = Hash.new(0)
|
389
|
+
Dir.glob("**/*.task", File::FNM_DOTMATCH).each do |f|
|
390
|
+
dirname = File.dirname(File.expand_path(f))
|
391
|
+
dirs_and_count[dirname] += 1
|
392
|
+
end
|
393
|
+
Dir.chdir(original_dir)
|
394
|
+
dirs_and_count
|
395
|
+
end
|
396
|
+
|
397
|
+
# Moves the tasks.log file to the system directory if not there. Should only
|
398
|
+
# be if upgrading from version 0.0.7 and below
|
399
|
+
def move_task_log_file(dir)
|
400
|
+
@tasks_log_files = tasks_log_files(dir) if @tasks_log_files.nil?
|
401
|
+
@tasks_log_files.each do |f|
|
402
|
+
next if f == TASKS_LOG
|
403
|
+
tasks_log = File.read(f)
|
404
|
+
File.open(TASKS_LOG, 'a') {|t| t.puts tasks_log}
|
405
|
+
FileUtils.mv(f, "#{f}_#{Time.now.strftime("%y%m%d")}")
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Moves the planned tasks file to the system directory if not there. Should
|
410
|
+
# only be if upgrading from version 0.0.7 and below
|
411
|
+
def move_planned_tasks_files(dir)
|
412
|
+
@planned_tasks_files = planned_tasks_files(dir) if @planned_tasks_files.nil?
|
413
|
+
@planned_tasks_files.each do |file|
|
414
|
+
to_file = "#{SYC_DIR}/#{File.basename(file)}"
|
415
|
+
next if file == to_file
|
416
|
+
FileUtils.mv file, to_file
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Moves the schedule file to the system directory if not there. Should
|
421
|
+
# only be if upgrading from version 0.0.7 and below
|
422
|
+
def move_time_schedule_files(dir)
|
423
|
+
@time_schedule_files = time_schedule_files(dir) if @time_schedule_files.nil?
|
424
|
+
@time_schedule_files.each do |file|
|
425
|
+
to_file = "#{SYC_DIR}/#{File.basename(file)}"
|
426
|
+
next if file == to_file
|
427
|
+
FileUtils.mv file, to_file
|
428
|
+
end
|
429
|
+
end
|
5
430
|
|
6
431
|
end
|
data/lib/syctask/schedule.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require_relative 'times.rb'
|
2
2
|
require_relative 'meeting.rb'
|
3
3
|
require_relative '../sycstring/string_util.rb'
|
4
|
+
require_relative '../syctime/time_util.rb'
|
5
|
+
|
4
6
|
include Sycstring
|
7
|
+
include Syctime
|
5
8
|
|
6
9
|
module Syctask
|
7
10
|
|
@@ -85,7 +88,10 @@ module Syctask
|
|
85
88
|
title = titles[index] ? titles[index] : "Meeting #{index}"
|
86
89
|
@meetings << Syctask::Meeting.new(busy, title)
|
87
90
|
end
|
88
|
-
raise Exception,
|
91
|
+
raise Exception,
|
92
|
+
"Busy times have to be within work time" unless within?(@meetings,
|
93
|
+
@starts,
|
94
|
+
@ends)
|
89
95
|
@tasks = tasks
|
90
96
|
end
|
91
97
|
|
@@ -95,8 +101,10 @@ module Syctask
|
|
95
101
|
assignments.each do |assignment|
|
96
102
|
number = assignment[0].upcase.ord - "A".ord
|
97
103
|
return false if number < 0 or number > @meetings.size
|
98
|
-
|
99
|
-
|
104
|
+
@meetings[number].tasks.clear
|
105
|
+
assignment[1].split(',').each do |id|
|
106
|
+
index = @tasks.find_index{|task| task.id == id.to_i}
|
107
|
+
@meetings[number].tasks << @tasks[index] if index and @tasks[index]
|
100
108
|
end
|
101
109
|
@meetings[number].tasks.uniq!
|
102
110
|
end
|
@@ -109,7 +117,13 @@ module Syctask
|
|
109
117
|
list << sprintf("%s", "--------\n").color(:red)
|
110
118
|
meeting_number = "A"
|
111
119
|
@meetings.each do |meeting|
|
112
|
-
|
120
|
+
hint = "-"
|
121
|
+
hint = "*" if time_between?(Time.now,
|
122
|
+
meeting.starts.time,
|
123
|
+
meeting.ends.time)
|
124
|
+
list << sprintf("%s %s %s\n", meeting_number,
|
125
|
+
hint,
|
126
|
+
meeting.title).color(:red)
|
113
127
|
meeting_number.next!
|
114
128
|
meeting.tasks.each do |task|
|
115
129
|
task_color = task.done? ? :green : :blue
|
@@ -156,14 +170,19 @@ module Syctask
|
|
156
170
|
# list
|
157
171
|
def graph
|
158
172
|
work_time, meeting_times = get_times
|
173
|
+
|
174
|
+
heading = sprintf("+++ %s - %s-%s +++", Time.now.strftime("%Y-%m-%d"),
|
175
|
+
@starts.time.strftime("%H:%M"),
|
176
|
+
@ends.time.strftime("%H:%M")).color(:blue)
|
177
|
+
|
159
178
|
time_line = "|---" * (work_time[1]-work_time[0]) + "|"
|
160
179
|
meeting_times.each do |time|
|
161
|
-
time_line[time[0]..time[1]] = '/' * (time[1] - time[0]
|
180
|
+
time_line[time[0]..time[1]-1] = '/' * (time[1] - time[0])
|
162
181
|
end
|
163
182
|
|
164
183
|
task_list, task_caption = assign_tasks_to_graph(time_line)
|
165
184
|
|
166
|
-
[meeting_list, meeting_caption,
|
185
|
+
[heading.center(80), meeting_list, meeting_caption,
|
167
186
|
colorize(time_line), time_caption,
|
168
187
|
task_caption, task_list]
|
169
188
|
end
|
@@ -214,23 +233,50 @@ module Syctask
|
|
214
233
|
# Assigns the tasks to the timeline in alternation x and o subsequent tasks.
|
215
234
|
# Returns the task list and the task caption
|
216
235
|
def assign_tasks_to_graph(time_line)
|
236
|
+
done_tasks = []
|
217
237
|
unscheduled_tasks = []
|
218
238
|
signs = ['x','o']
|
219
239
|
positions = {}
|
220
|
-
|
240
|
+
current_time = Time.now
|
221
241
|
unassigned_tasks.each.with_index do |task, index|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
242
|
+
if task.done? or not task.today?
|
243
|
+
done_tasks << task
|
244
|
+
next
|
245
|
+
else
|
246
|
+
round = task.remaining.to_i % 900 == 0 ? 0 : 0.5
|
247
|
+
duration = [(task.remaining.to_i/900+round).round, 1].max
|
248
|
+
position = [0, position_for_time(current_time)].max
|
249
|
+
end
|
250
|
+
free_time = scan_free(time_line, 1, position)
|
251
|
+
if free_time[0].nil?
|
226
252
|
unscheduled_tasks << task
|
227
253
|
next
|
228
254
|
end
|
229
|
-
|
230
|
-
|
231
|
-
|
255
|
+
0.upto(duration-1) do |i|
|
256
|
+
break unless free_time[i]
|
257
|
+
time_line[free_time[i]] = signs[index%2]
|
258
|
+
end
|
259
|
+
positions[free_time[0]] = task.id
|
232
260
|
end
|
233
261
|
|
262
|
+
unless done_tasks.empty?
|
263
|
+
end_position = position_for_time(current_time)
|
264
|
+
total_duration = 0
|
265
|
+
done_tasks.each_with_index do |task,index|
|
266
|
+
free_time = scan_free(time_line, 1, 0, end_position)
|
267
|
+
lead_time = task.duration.to_i - task.remaining.to_i + 0.0
|
268
|
+
max_duration = [free_time.size - (done_tasks.size - index - 1), 1].max
|
269
|
+
duration = [(lead_time/900).round, 1].max
|
270
|
+
total_duration += duration = [duration, max_duration].min
|
271
|
+
0.upto(duration-1) do |i|
|
272
|
+
break unless free_time[i]
|
273
|
+
time_line[free_time[i]] = signs[index%2]
|
274
|
+
end
|
275
|
+
positions[free_time[0]] = task.id if free_time[0]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Create task list
|
234
280
|
max_id_size = 1
|
235
281
|
@tasks.each {|task| max_id_size = [task.id.to_s.size, max_id_size].max}
|
236
282
|
max_ord_size = (@tasks.size - 1).to_s.size
|
@@ -238,20 +284,26 @@ module Syctask
|
|
238
284
|
task_list = sprintf("%s", "Tasks\n").color(:blue)
|
239
285
|
task_list << sprintf("%s", "-----\n").color(:blue)
|
240
286
|
@tasks.each.with_index do |task, i|
|
241
|
-
if task.done?
|
287
|
+
if task.done? or not task.today?
|
242
288
|
color = :green
|
243
289
|
elsif unscheduled_tasks.find_index(task)
|
244
290
|
color = UNSCHEDULED_COLOR
|
245
291
|
else
|
246
292
|
color = WORK_COLOR
|
247
293
|
end
|
294
|
+
|
295
|
+
hint = "-"
|
296
|
+
hint = "~" unless task.today?
|
297
|
+
hint = "*" if task.tracked?
|
298
|
+
|
248
299
|
offset = max_ord_size + max_id_size + 5
|
249
300
|
title = split_lines(task.title, 80-offset)
|
250
301
|
title = title.chomp.gsub(/\n/, "\n#{' '*offset}")
|
251
|
-
task_list << sprintf("%#{max_ord_size}d: %#{max_id_size}s
|
252
|
-
i, task.id, title).color(color)
|
302
|
+
task_list << sprintf("%#{max_ord_size}d: %#{max_id_size}s %s %s\n",
|
303
|
+
i, task.id, hint, title).color(color)
|
253
304
|
end
|
254
305
|
|
306
|
+
# Create task caption
|
255
307
|
task_caption = ""
|
256
308
|
create_caption(positions).each do |caption|
|
257
309
|
task_caption << sprintf("%s\n", caption).color(WORK_COLOR)
|
@@ -267,6 +319,7 @@ module Syctask
|
|
267
319
|
counter = 0
|
268
320
|
lines = [""]
|
269
321
|
positions.each do |position,id|
|
322
|
+
next unless position
|
270
323
|
line_id = next_line(position,lines,counter)
|
271
324
|
legend = ' ' * [0, position - lines[line_id].size].max + id.to_s
|
272
325
|
lines[line_id] += legend
|
@@ -295,18 +348,28 @@ module Syctask
|
|
295
348
|
return lines.size - 1
|
296
349
|
end
|
297
350
|
|
351
|
+
# Determines the position within the time line for the given time. Each
|
352
|
+
# position represents a 15 minute duration. Minutes below 8 will be rounded
|
353
|
+
# down otherwise rounded up.
|
354
|
+
def position_for_time(time)
|
355
|
+
diff = @starts.diff(time)
|
356
|
+
# as the time line is always rounded down to the full hour we have to add
|
357
|
+
# the minutes with @starts.m / 15 to get the position for the current time
|
358
|
+
((diff[0] * 60 + diff[1]) / 15.0).round + @starts.m / 15
|
359
|
+
end
|
360
|
+
|
298
361
|
# Scans the schedule for free time where a task can be added to. Count
|
299
362
|
# specifies the length of the free time and the position where to start
|
300
363
|
# scanning within the graph
|
301
|
-
def scan_free(graph, count,
|
364
|
+
def scan_free(graph, count, starts, ends=graph.size)
|
302
365
|
pattern = /(?!\/)[\|-]{#{count}}(?<=-|\||\/)/
|
303
366
|
|
304
367
|
positions = []
|
305
|
-
index =
|
306
|
-
while index and index <
|
368
|
+
index = starts
|
369
|
+
while index and index < ends
|
307
370
|
index = graph.index(pattern, index)
|
308
371
|
if index
|
309
|
-
positions << index
|
372
|
+
positions << index if index < ends
|
310
373
|
index += 1
|
311
374
|
end
|
312
375
|
end
|