Module: Syctask

Defined in:
lib/syctask/task.rb,
lib/syctask/times.rb,
lib/syctask/meeting.rb,
lib/syctask/scanner.rb,
lib/syctask/version.rb,
lib/syctask/schedule.rb,
lib/syctask/settings.rb,
lib/syctask/evaluator.rb,
lib/syctask/statistics.rb,
lib/syctask/environment.rb,
lib/syctask/task_planner.rb,
lib/syctask/task_service.rb,
lib/syctask/task_tracker.rb,
lib/syctask/task_scheduler.rb

Overview

require_relative ‘../sycutil/console_timer.rb’

Defined Under Namespace

Classes: Evaluator, Meeting, Scanner, Schedule, Settings, Statistics, Task, TaskPlanner, TaskScheduler, TaskService, TaskTracker, Times, Track

Constant Summary collapse

VERSION =

Holds the version number of syctask

'0.4.2'
SYC_DIR =

System directory of syctask

File.join(ENV['HOME'], '.syc/syctask')
ID =

ID file where the last issued ID is saved

SYC_DIR + "/id"
IDS =

File that contains all issued IDs

SYC_DIR + "/ids"
TAGS =

File with tags

SYC_DIR + "/tags"
DEFAULT_TASKS =

File with the general purpose tasks

SYC_DIR + "/default_tasks"
DEFAULT_TASKS_DIR =

File that holds the default task directory

SYC_DIR + "/default_tasks_dir"
TASKS_LOG =

Log file that logs all activities of syctask like creation of tasks

SYC_DIR + "/tasks.log"
TRACKED_TASK =

File that holds the tracked task

SYC_DIR + "/tracked_tasks"
RIDX_LOG =

If files are re-indexed during re-indexing these tasks are save here

SYC_DIR + "/reindex.log"
WORK_DIR =

Set eather user defined work directory or default

work_dir.nil? ? File.join(ENV['HOME'], '.tasks') : work_dir
PROMPT_STRING =

String that is prompted during planning

'(a)dd, (c)omplete, (s)kip, (q)uit: '
INSPECT_STRING =

String that is prompted during inspect

'(e)dit, (d)one, de(l)ete, (p)lan, da(t)e, (c)omplete, '+
'(s)kip, (b)ack, (q)uit: '
PRIORITIZE_STRING =

String that is prompted during prioritization

'Task 1 has (h)igher or (l)ower priority, or (q)uit: '

Instance Method Summary collapse

Instance Method Details

#check_environmentObject

Checks whether all files are available that are needed for syctask’s operation



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/syctask/environment.rb', line 91

def check_environment
  FileUtils.mkdir_p WORK_DIR unless File.exists? WORK_DIR
  unless viable?
    recover, whitelisted_dirs, blacklisted_dirs = initialize_or_recover_system
    case recover
    when 0
      FileUtils.mkdir_p SYC_DIR unless File.exists? SYC_DIR
      File.write(ID, "0")
    when 1
      # Backup ARGV content
      args = []
      ARGV.each {|arg| args << arg} unless ARGV.empty?
      ARGV.clear
      reindex_tasks(whitelisted_dirs, blacklisted_dirs)
      puts "Successfully recovered syc-task"
      puts "-> A log file of re-indexed tasks can be found at\n"+
           "#{RIDX_LOG}" if File.exists? RIDX_LOG
      print "Press any key to continue "
      gets
      # Restore ARGV content
      args.each {|arg| ARGV << arg} unless args.empty?
    when 2
      puts "o.k. - don't do nothing"
      exit -1
    end
  end
end

#collect_by_id(tasks) ⇒ Object

Extracts tasks that have no unique id



364
365
366
367
368
369
370
371
# File 'lib/syctask/environment.rb', line 364

def collect_by_id(tasks)
  extract = {}
  tasks.each do |task|
    id = task.scan(/(?<=\/)\d+(?=\.task$)/)[0]
    extract[id].nil? ? extract[id] = [task] : extract[id] << task
  end
  extract
end

#get_files(included, excluded = [], pattern) ⇒ Object

Retrieves all files that meet the pattern in and below the given directory



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/syctask/environment.rb', line 397

def get_files(included, excluded=[], pattern)
  files = []
  Find.find(*included) do |path|
    if FileTest.directory?(path)
      if excluded.include?(path)
        Find.prune
      else
        next
      end
    else
      files << File.expand_path(path) if File.basename(path) =~ pattern  
    end
  end
  files
end

#get_task_dirs(dir) ⇒ Object

Retrieve all directories that contain tasks



414
415
416
417
418
419
420
421
422
# File 'lib/syctask/environment.rb', line 414

def get_task_dirs(dir)
  original_dir = File.expand_path(".")
  Dir.chdir(dir)
  dirs = Dir.glob("**/*.task", File::FNM_DOTMATCH).map do |f|
    File.dirname(File.expand_path(f))
  end
  Dir.chdir(original_dir)
  dirs.uniq
end

#get_task_dirs_and_count(dir) ⇒ Object

Retrieves all directories that contain tasks and the count of contained tasks in and below the provided directory



426
427
428
429
430
431
432
433
434
435
436
# File 'lib/syctask/environment.rb', line 426

def get_task_dirs_and_count(dir)
  original_dir = File.expand_path(".")
  Dir.chdir(dir)
  dirs_and_count = Hash.new(0)
  Dir.glob("**/*.task", File::FNM_DOTMATCH).each do |f|
    dirname = File.dirname(File.expand_path(f))
    dirs_and_count[dirname] += 1
  end
  Dir.chdir(original_dir)
  dirs_and_count
end

#initialize_id(tasks) ⇒ Object

Determines the greatest task ID out of the provided tasks and saves it to the ID file



269
270
271
272
273
# File 'lib/syctask/environment.rb', line 269

def initialize_id(tasks)
  pattern = %r{(?<=\/)\d+(?=\.task)}
  tasks.sort_by! {|t| t.scan(pattern)[0].to_i}
  save_id(tasks[tasks.size-1].scan(pattern)[0].to_i)
end

#initialize_or_recover_systemObject

Asks the user whether this is a fresh install because of missing system files. If it is not a fresh install then this might be because of an upgrade to a version > 0.0.7 or the user accidentally has deleted the system files. If it is a fresh install the system files are created. Otherwise the user can select to search for task files and recover the system.

intialize_or_recover_system #=> recover, whitelisted_dirs, blacklisted_dirs recover = 0 just creates the system files as it is fresh install recover = 1 recover task files recover = 2 abort, don’t recover task files whitelisted_dirs = array of directories where to search for task files blacklisted_dirs = array of directories where not to search for task files



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/syctask/environment.rb', line 137

def initialize_or_recover_system
  whitelisted_dirs = []
  blacklisted_dirs = []

  puts "This seems to be a fresh install because there are no system files "+
       "available."
  puts "* If this is a fresh install just hit 'y'. "
  puts "* Otherwise hit 'n' to go to the recovery step."
  print "Is this a fresh install (y/n)? "
  answer = gets.chomp
  if answer.downcase == "y"
    [0, nil, nil]
  else
    puts
    puts "If you have upgraded from version 0.0.7 or below than this is "+
         "due to a changed\nfile structure. For changes in version "+
         "greater 0.0.7 see"
    puts "--> https://rubygems.org/gems/syc-task"
    puts "Or you have accidentially deleted system files. In both cases "+
         "re-indexing\nwill recover syc-task."
    print "Do you want to recover syc-task (y/n)? "
    answer = gets.chomp
    if answer.downcase == "y"
      puts
      puts "If you know where your task files are located then you can "+
           "specify the\ndirectories. Search starts in your home directory."
      print "Do you want to specify the directories (y/n)? "
      answer = gets.chomp
      if answer.downcase == "y"
        puts "Please enter directories, e.g. ~/.my-tasks ~/work-tasks"
        whitelisted_dirs = gets.chomp.split(/\s+/)
                                     .map { |f| File.expand_path(f) }
      else
        puts "You don't want to select task directories. It is adviced to "+
             "exclude mounted \ndirectories as this might take very long to "+
             "search all directories for task files. Also if it is no " +
             "stable connection\n the recovery process might be aborted"
        print "Do you want to exclude directories (y/n)? "
        if answer.downcase == "y"
          puts "Please enter directories, e.g. ~/mount ~/.no-tasks"
          blacklisted_dirs = gets.chomp.split(/\s+/)
                                       .map { |f| File.expand_path(f) }
        else
          whitelisted_dir = [ENV['HOME']]
          puts "Searching directories and all sub-directories starting in\n"+
               "#{ENV['HOME']}"
        end
      end
      [1, whitelisted_dirs, blacklisted_dirs]
    else
      [2, nil, nil]
    end
  end
end

#log_meetings(type, busy_time, meetings) ⇒ Object

Logs meeting times



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/syctask/environment.rb', line 74

def log_meetings(type, busy_time, meetings)
  today = Time.now
  logs = File.read(TASKS_LOG)
  time_pat = "#{today.strftime("%Y-%m-%d")} \\d{2}:\\d{2}:\\d{2} [+-]\\d{4}"
  pattern = %r{#{type};-2;;.*?;#{time_pat};#{time_pat}\n}
  logs.gsub!(pattern, "")
  busy_time.each_with_index do |busy,i|
    begins = Time.local(today.year,today.mon,today.day,busy[0],busy[1],0)
    ends   = Time.local(today.year,today.mon,today.day,busy[2],busy[3],0)
    meeting = meetings[i] ? meetings[i] : "Meeting #{i}"
    logs << "#{type};-2;;#{meeting};#{begins};#{ends}\n"
  end
  File.write(TASKS_LOG, logs)
end

#log_reindexing(old_id, new_id, file) ⇒ Object

Logs if a task is re-indexed



296
297
298
299
300
301
# File 'lib/syctask/environment.rb', line 296

def log_reindexing(old_id, new_id, file)
  entry = "#{old_id},#{new_id},#{file}"
  return if File.exists? RIDX_LOG and not File.read(RIDX_LOG).
    scan(entry).empty?
  File.open(RIDX_LOG, 'a') {|f| f.puts entry}
end

#log_task(type, task) ⇒ Object

Logs a task regarding create, update, done, delete



34
35
36
37
38
39
40
41
42
43
# File 'lib/syctask/environment.rb', line 34

def log_task(type, task)
  File.open(TASKS_LOG, 'a') do |file|
    log_entry =  "#{type.to_s};"
    log_entry += "#{task.id};#{task.dir};"
    log_entry += "#{task.title.gsub(';', '\'semicolon\'')};"
    log_entry += "#{Time.now};"
    log_entry += "#{Time.now}" 
    file.puts log_entry
  end
end

#log_work_time(type, work_time) ⇒ Object

Logs the work time



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/syctask/environment.rb', line 46

def log_work_time(type, work_time)
  today  = Time.now
  begins = Time.local(today.year,
                      today.mon,
                      today.day,
                      work_time[0],
                      work_time[1],
                      0)
  ends   = Time.local(today.year,
                      today.mon,
                      today.day,
                      work_time[2],
                      work_time[3],
                      0)
  entry = "#{type};-1;;work;#{begins};#{ends}\n"
  logs = File.read(TASKS_LOG)
  return if logs.scan(entry)[0]
  time_pat = "#{today.strftime("%Y-%m-%d")} \\d{2}:\\d{2}:\\d{2} [+-]\\d{4}"
  pattern = %r{#{type};-1;;work;#{time_pat};#{time_pat}\n}
  log = logs.scan(pattern)[0]
  if log and logs.sub!(log, entry)
    File.write(TASKS_LOG, logs)
  else
    File.open(TASKS_LOG, 'a') {|f| f.puts entry}
  end
end

#move_planned_tasks_files(dirs, excluded) ⇒ Object

Moves the planned tasks file to the system directory if not there. Should only be if upgrading from version 0.0.7 and below



454
455
456
457
458
459
460
461
462
463
# File 'lib/syctask/environment.rb', line 454

def move_planned_tasks_files(dirs, excluded)
  if @planned_tasks_files.nil?
    @planned_tasks_files = planned_tasks_files(dirs, excluded) 
  end
  @planned_tasks_files.each do |file|
    to_file = "#{SYC_DIR}/#{File.basename(file)}"
    next if file == to_file
    FileUtils.mv file, to_file
  end
end

#move_task_log_file(dirs, excluded) ⇒ Object

Moves the tasks.log file to the system directory if not there. Should only be if upgrading from version 0.0.7 and below



440
441
442
443
444
445
446
447
448
449
450
# File 'lib/syctask/environment.rb', line 440

def move_task_log_file(dirs, excluded)
  if @tasks_log_files.nil?    
    @tasks_log_files = tasks_log_files(dirs, excluded) 
  end
  @tasks_log_files.each do |f|
    next if f == TASKS_LOG
    tasks_log = File.read(f)
    File.open(TASKS_LOG, 'a') {|t| t.puts tasks_log}
    FileUtils.mv(f, "#{f}_#{Time.now.strftime("%y%m%d")}")
  end
end

#move_time_schedule_files(dirs, excluded) ⇒ Object

Moves the schedule file to the system directory if not there. Should only be if upgrading from version 0.0.7 and below



467
468
469
470
471
472
473
474
475
476
# File 'lib/syctask/environment.rb', line 467

def move_time_schedule_files(dirs, excluded)
  if @time_schedule_files.nil?
    @time_schedule_files = time_schedule_files(dirs, excluded) 
  end
  @time_schedule_files.each do |file|
    to_file = "#{SYC_DIR}/#{File.basename(file)}" 
    next if file == to_file
    FileUtils.mv file, to_file
  end 
end

#next_idObject

Retrieve the next unassigned task id



289
290
291
292
293
# File 'lib/syctask/environment.rb', line 289

def next_id
  id = File.read(ID).to_i + 1
  save_id(id)
  id
end

#planned_tasks_files(dirs, excluded = []) ⇒ Object

Retrieves all planned task files in and below the given directory



380
381
382
383
# File 'lib/syctask/environment.rb', line 380

def planned_tasks_files(dirs, excluded=[])
  pattern = %r{\d{4}-\d{2}-\d{2}_planned_tasks}
  get_files(dirs, excluded, pattern)
end

#reindex_task(file) ⇒ Object

Re-indexes the tasks’ IDs and renames the task files to match the new ID. The orginal file is deleted. Returns old_id, new_id, tmp_file_name and new_file_name. The task is save with the tmp_file_name in case the new ID and hence the new_file_name exists already from a not yet re-indexed task. After all tasks are re-indexed the tmp_file_names have to be renamed to the new_file_names. The renaming is in the responsibility of the calling method.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/syctask/environment.rb', line 249

def reindex_task(file)
  print "."
  task = File.read(file)
  old_id = task.scan(/(?<=^id: )\d+$/)[0]
  new_id = next_id.to_s
  task.gsub!(/(?<=^id: )\d+$/, new_id)
  dirname = File.dirname(file)
  new_file = "#{dirname}/#{new_id}.task"
  tmp_file = "#{new_file}_"
  File.write(tmp_file, task)
  File.delete(file)
  {old_id: old_id, 
   new_id: new_id, 
   tmp_file: tmp_file, 
   new_file: new_file,
   dirname: dirname}
end

#reindex_tasks(dirs, excluded) ⇒ Object

Re-indexing of tasks is done when tasks are available but SYC_DIR or ID file is missing. The ID file contains the last issued task ID. The ID file is referenced for obtaining the next ID for a new task. Re-indexing is done as follows:

  • Retrieve all tasks in and below the given directory root

  • Determine the highest ID number and add it to the ID file

  • Determine all tasks that don’t have a unique ID

  • Re-index all tasks not having a unique ID and rename the file names accordingly

  • Adjust the IDs in the planned_tasks, tasks.log and tracked_tasks files

  • Copy all system files planned_tasks, time_schedule, tasks.log, id to the SYC_DIR directory if not already in the SYC_DIR directory. This should only be if upgrading from version 0.0.7 and below.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/syctask/environment.rb', line 205

def reindex_tasks(dirs, excluded) #root)
  FileUtils.mkdir_p SYC_DIR unless File.exists? SYC_DIR
  new_id = {}
  to_be_renamed = {}
  puts "-> Collect task files..."
  task_files = task_files(dirs, excluded)
  puts "-> Restore ID counter..."
  initialize_id(task_files)
  print "-> Start re-indexing now..."
  collect_by_id(task_files).each do |id, files|
    next if files.size < 2
    files.each_with_index do |file,i|
      next if i == 0 # need to re-index only second and following tasks
      result = reindex_task(file)
      # associate old id to new id and dir name
      if new_id[result[:old_id]].nil?
        new_id[result[:old_id]] = {result[:dirname] => result[:new_id]}
      else
        new_id[result[:old_id]][result[:dirname]] = result[:new_id]
      end 
      # assign tmp_file to new_file for later renaming
      to_be_renamed[result[:tmp_file]] = result[:new_file]
      # document the re-indexing of tasks
      log_reindexing(result[:old_id], result[:new_id], result[:new_file]) 
    end
  end 
  to_be_renamed.each {|old_name,new_name| File.rename(old_name, new_name)}
  puts
  puts "-> Update task log file"
  update_tasks_log(dirs, excluded, new_id)
  puts "-> Update planned tasks files"
  update_planned_tasks(dirs, excluded, new_id)
  puts "-> Move schedule files..."
  move_time_schedule_files(dirs, excluded)
  puts "-> Update tracked task file..."
  update_tracked_task(dirs, excluded)
end

#save_id(id) ⇒ Object

Save the id to the ID file. Returns the id when save was successful



283
284
285
286
# File 'lib/syctask/environment.rb', line 283

def save_id(id)
  File.write(ID,id)
  id
end

#save_ids(id, file) ⇒ Object

Saves the ids to ids file



276
277
278
279
280
# File 'lib/syctask/environment.rb', line 276

def save_ids(id, file)
  entry = "#{id},#{file}"
  return if File.exists? IDS and not File.read(IDS).scan(entry).empty?
  File.open(IDS, 'a') {|f| f.puts entry}
end

#task_files(dirs, excluded = []) ⇒ Object

Retrieves all task files in and below the provided dir. Returns an array of task files



375
376
377
# File 'lib/syctask/environment.rb', line 375

def task_files(dirs, excluded=[])
  get_files(dirs, excluded, /\d+\.task$/)
end

#tasks_log_files(dirs, excluded = []) ⇒ Object

Retrieves als tasks.log files in and below the given directory



392
393
394
# File 'lib/syctask/environment.rb', line 392

def tasks_log_files(dirs, excluded=[])
  get_files(dirs, excluded, /tasks\.log/)
end

#time_schedule_files(dirs, excluded = []) ⇒ Object

Retrieves all schedule files in and below the given directory



386
387
388
389
# File 'lib/syctask/environment.rb', line 386

def time_schedule_files(dirs, excluded=[])
  pattern = %r{\d{4}-\d{2}-\d{2}_time_schedule}
  get_files(dirs, excluded, pattern)
end

#update_planned_tasks(dirs, excluded, new_ids) ⇒ Object

Replaces the old ids with the new ids in the planned tasks files. A planned tasks file has the form ‘2013-03-03_planned_tasks’ and lives until syctask’s version 0.0.7 in ~/.tasks directory. From version 0.1.0 on the planned tasks files live in the ~/.syc/syctask directory. So the calling method has the responsibility to copy or move the planned tasks files after they have been updated to the new planned tasks directory.



333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/syctask/environment.rb', line 333

def update_planned_tasks(dirs, excluded, new_ids)
  planned_tasks_files(dirs, excluded).each do |file|
    tasks = File.readlines(file)
    tasks.each_with_index do |task,i|
      task_dir, old_id = task.chomp.split(',')
      next unless new_ids[old_id]
      next unless new_ids[old_id][task_dir]
      tasks[i] = "#{task_dir},#{new_ids[old_id][task_dir]}"   
    end
    File.write("#{SYC_DIR}/#{File.basename(file)}", tasks.join("\n"))
  end
end

#update_tasks_log(dirs, excluded = [], new_ids) ⇒ Object

Updates the tasks.log file if tasks are re-indexed with the task’s new ids



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/syctask/environment.rb', line 304

def update_tasks_log(dirs, excluded=[], new_ids)
  tasks_log_files(dirs, excluded).each do |file|
    logs = File.readlines(file)
    logs.each_with_index do |log,i|
      type = log.scan(/^.*?(?=;)/)[0]
      logs[i] = log.sub!("-",";") if log.scan(/(?<=^#{type};)\d+-/)[0]
      old_id = log.scan(/(?<=^#{type};)\d+(?=;)/)[0]
      next unless new_ids[old_id]
      task_dir = log.scan(/(?<=^#{type};#{old_id};).*?(?=;)/)[0]
      next unless new_ids[old_id][task_dir]
      logs[i] = log.sub("#{old_id};#{task_dir}", 
                        "#{new_ids[old_id][task_dir]};#{task_dir}")
    end
    if file == TASKS_LOG
      File.write(TASKS_LOG, logs.join)
    else
      #TODO only append a line if it is not already available in TASKS_LOG
      File.open(TASKS_LOG, 'a') {|f| f.puts logs.join}
      FileUtils.rm file
    end
  end
end

#update_tracked_task(dirs, excluded) ⇒ Object

Updates tracked_tasks file if task has been re-indexed with new ID



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/syctask/environment.rb', line 347

def update_tracked_task(dirs, excluded)
  @tracked = get_files(dirs, excluded, /tracked_tasks/) if @tracked.nil?
  return if @tracked.empty?
  task = File.read(@tracked[0])
  if File.exists? RIDX_LOG
    old_id = task.scan(/(?<=id: )\d+$/)
    old_dir = task.scan(/(?<=dir: ).*$/)
    return if old_id.empty? or old_dir.empty?
    pattern = %r{(?<=#{old_id[0]},)\d+(?=,#{old_dir[0]}\/\d+\.task)}
    new_id = File.read(RIDX_LOG).scan(pattern)
    task.gsub!("id: #{old_id}", "id: #{new_id}")
  end
  File.write(TRACKED_TASK, task)
  FileUtils.rm @tracked[0] unless TRACKED_TASK == @tracked[0]
end

#viable?Boolean

Checks if system files are available that are needed for running syc-task. Returns true if neccessary system files are available, otherwise false.

Returns:

  • (Boolean)


121
122
123
# File 'lib/syctask/environment.rb', line 121

def viable?
  File.exists? SYC_DIR and File.exists? ID 
end