syc-task 0.3.2 → 1.0.0

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.
@@ -1,13 +1,12 @@
1
- require_relative 'times.rb'
2
- require_relative 'meeting.rb'
3
- require_relative '../sycstring/string_util.rb'
4
- require_relative '../syctime/time_util.rb'
1
+ require_relative 'times'
2
+ require_relative 'meeting'
3
+ require_relative '../sycstring/string_util'
4
+ require_relative '../syctime/time_util'
5
5
 
6
6
  include Sycstring
7
7
  include Syctime
8
8
 
9
9
  module Syctask
10
-
11
10
  # Schedule represents a working day with a start and end time, meeting times
12
11
  # and titles and tasks. Tasks can also be associated to meetings as in an
13
12
  # agenda.
@@ -18,7 +17,7 @@ module Syctask
18
17
  # tasks = [task1,task2,task3,task4,task5,task6]
19
18
  # schedule = Syctask::Schedule.new(work,busy,titles,tasks)
20
19
  # schedule.graph.each {|output| puts output}
21
- #
20
+ #
22
21
  # This will create following output
23
22
  # Meetings
24
23
  # --------
@@ -28,7 +27,7 @@ module Syctask
28
27
  # A B
29
28
  # xxoo/////xxx|-////oooooxoooo|---|---|---|---|
30
29
  # 8 9 10 11 12 13 14 15 16 17 18 19
31
- # 1 2 3 4 5
30
+ # 1 2 3 4 5
32
31
  # 6
33
32
  #
34
33
  # Tasks
@@ -39,7 +38,7 @@ module Syctask
39
38
  # 3 - 4: task4
40
39
  # 4 - 5: task5
41
40
  # 5 - 6: task6
42
- #
41
+ #
43
42
  # Subsequent tasks are are displayed in the graph alternating with x and o.
44
43
  # Meetings are indicated with / and the start is marked with A, B and so on.
45
44
  # Task IDs are shown below the graph. The graph will be printed colored.
@@ -55,14 +54,14 @@ module Syctask
55
54
  # If tasks cannot be assigned to the working time this color is used
56
55
  UNSCHEDULED_COLOR = :yellow
57
56
  # Regex scans tasks and free times in the graph
58
- GRAPH_PATTERN = /[\|-]+|\/+|[xo]+/
57
+ GRAPH_PATTERN = %r{[|-]+|/+|[xo]+}
59
58
  # Regex scans meetings in the graph
60
- BUSY_PATTERN = /\/+/
59
+ BUSY_PATTERN = %r{/+}
61
60
  # Regex scans free times in the graph
62
- FREE_PATTERN = /[\|-]+/
61
+ FREE_PATTERN = /[|-]+/
63
62
  # Regex scans tasks in the graph
64
63
  WORK_PATTERN = /[xo]+/
65
-
64
+
66
65
  # Start time of working day
67
66
  attr_reader :starts
68
67
  # End time of working day
@@ -79,19 +78,21 @@ module Syctask
79
78
  # * busy time: [[start_hour, start_minute, end_hour, end_minute],[...]]
80
79
  # * titles: [title,...]
81
80
  # * tasks: [task,...]
82
- def initialize(work_time, busy_time=[], titles=[], tasks=[])
81
+ def initialize(work_time, busy_time = [], titles = [], tasks = [])
83
82
  @starts = Syctask::Times.new([work_time[0], work_time[1]])
84
83
  @ends = Syctask::Times.new([work_time[2], work_time[3]])
85
84
  @meetings = []
86
85
  titles ||= []
87
- busy_time.each.with_index do |busy,index|
88
- title = titles[index] ? titles[index] : "Meeting #{index}"
89
- @meetings << Syctask::Meeting.new(busy, title)
86
+ busy_time.each.with_index do |busy, index|
87
+ title = titles[index] || "Meeting #{index}"
88
+ @meetings << Syctask::Meeting.new(busy, title)
89
+ end
90
+ unless within?(@meetings,
91
+ @starts,
92
+ @ends)
93
+ raise Exception,
94
+ 'Busy times have to be within work time'
90
95
  end
91
- raise Exception,
92
- "Busy times have to be within work time" unless within?(@meetings,
93
- @starts,
94
- @ends)
95
96
  @tasks = tasks
96
97
  end
97
98
 
@@ -99,12 +100,13 @@ module Syctask
99
100
  # Returns true if succeeds
100
101
  def assign(assignments)
101
102
  assignments.each do |assignment|
102
- number = assignment[0].upcase.ord - "A".ord
103
- return false if number < 0 or number > @meetings.size
103
+ number = assignment[0].upcase.ord - 'A'.ord
104
+ return false if number.negative? || number > @meetings.size
105
+
104
106
  @meetings[number].tasks.clear
105
107
  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]
108
+ index = @tasks.find_index { |task| task.id == id.to_i }
109
+ @meetings[number].tasks << @tasks[index] if index && @tasks[index]
108
110
  end
109
111
  @meetings[number].tasks.uniq!
110
112
  end
@@ -113,46 +115,46 @@ module Syctask
113
115
 
114
116
  # Creates a meeting list for printing. Returns the meeting list
115
117
  def meeting_list
116
- list = sprintf("%s", "Meetings\n").color(:red)
117
- list << sprintf("%s", "--------\n").color(:red)
118
- meeting_number = "A"
118
+ list = format('%s', "Meetings\n").color(:red)
119
+ list << format('%s', "--------\n").color(:red)
120
+ meeting_number = 'A'
119
121
  @meetings.each do |meeting|
120
- hint = "-"
121
- hint = "*" if time_between?(Time.now,
122
- meeting.starts.time,
122
+ hint = '-'
123
+ hint = '*' if time_between?(Time.now,
124
+ meeting.starts.time,
123
125
  meeting.ends.time)
124
- list << sprintf("%s %s %s\n", meeting_number,
125
- hint,
126
- meeting.title).color(:red)
126
+ list << format("%s %s %s\n", meeting_number,
127
+ hint,
128
+ meeting.title).color(:red)
127
129
  meeting_number.next!
128
130
  meeting.tasks.each do |task|
129
131
  task_color = task.done? ? :green : :blue
130
- list << sprintf("%5s - %s\n", task.id, task.title).color(task_color)
132
+ list << format("%5s - %s\n", task.id, task.title).color(task_color)
131
133
  end
132
- end
134
+ end
133
135
  list
134
136
  end
135
137
 
136
138
  # Creates a meeting caption and returns it for printing
137
139
  def meeting_caption
138
- work_time, meeting_times = get_times
139
- caption = ""
140
- meeting_number = "A"
140
+ (_, meeting_times) = get_times
141
+ caption = ''
142
+ meeting_number = 'A'
141
143
  meeting_times.each do |times|
142
- caption << ' ' * (times[0] - caption.size) + meeting_number
144
+ caption << ' ' * (times[0] - caption.size) + meeting_number
143
145
  meeting_number.next!
144
146
  end
145
- sprintf("%s", caption).color(:red)
147
+ format('%s', caption).color(:red)
146
148
  end
147
149
 
148
150
  # Creates the time caption for the time line
149
151
  def time_caption
150
152
  work_time = get_times[0]
151
- caption = ""
153
+ caption = ''
152
154
  work_time[0].upto(work_time[1]) do |time|
153
155
  caption << time.to_s + (time < 9 ? ' ' * 3 : ' ' * 2)
154
156
  end
155
- sprintf("%s", caption)
157
+ format('%s', caption)
156
158
  end
157
159
 
158
160
  # graph first creates creates the time line. Then the busy times are added.
@@ -169,35 +171,35 @@ module Syctask
169
171
  # * return time line, task caption, task list, meeting caption and meeting
170
172
  # list
171
173
  def graph
172
- work_time, meeting_times = get_times
174
+ (work_time, meeting_times) = get_times
173
175
 
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)
176
+ heading = format('+++ %s - %s-%s +++', Time.now.strftime('%Y-%m-%d'),
177
+ @starts.time.strftime('%H:%M'),
178
+ @ends.time.strftime('%H:%M')).color(:blue)
177
179
 
178
- time_line = "|---" * (work_time[1]-work_time[0]) + "|"
180
+ time_line = '|---' * (work_time[1] - work_time[0]) + '|'
179
181
  meeting_times.each do |time|
180
- time_line[time[0]..time[1]-1] = '/' * (time[1] - time[0])
182
+ time_line[time[0]..time[1] - 1] = '/' * (time[1] - time[0])
181
183
  end
182
184
 
183
185
  task_list, task_caption = assign_tasks_to_graph(time_line)
184
186
 
185
187
  [heading.center(80), meeting_list, meeting_caption,
186
- colorize(time_line), time_caption,
188
+ colorize(time_line), time_caption,
187
189
  task_caption, task_list]
188
190
  end
189
191
 
190
- private
192
+ private
191
193
 
192
194
  # Checks if meetings are within work times. Returns true if fullfilled
193
195
  # otherwise false
194
196
  def within?(meetings, starts, ends)
195
197
  meetings.each do |meeting|
196
198
  return false if meeting.starts.h < starts.h
197
- return false if meeting.starts.h == starts.h and
198
- meeting.starts.m < starts.m
199
+ return false if meeting.starts.h == starts.h &&
200
+ meeting.starts.m < starts.m
199
201
  return false if meeting.ends.h > ends.h
200
- return false if meeting.ends.h == ends.h and
202
+ return false if meeting.ends.h == ends.h &&
201
203
  meeting.ends.m > ends.m
202
204
  end
203
205
  true
@@ -207,11 +209,13 @@ module Syctask
207
209
  # past time is colored black
208
210
  def colorize(time_line)
209
211
  time_line, future = split_time_line(time_line)
210
- future.scan(GRAPH_PATTERN) do |part|
211
- time_line << sprintf("%s", part).color(BUSY_COLOR) unless part.scan(BUSY_PATTERN).empty?
212
- time_line << sprintf("%s", part).color(FREE_COLOR) unless part.scan(FREE_PATTERN).empty?
213
- time_line << sprintf("%s", part).color(WORK_COLOR) unless part.scan(WORK_PATTERN).empty?
214
- end if future
212
+ if future
213
+ future.scan(GRAPH_PATTERN) do |part|
214
+ time_line << format('%s', part).color(BUSY_COLOR) unless part.scan(BUSY_PATTERN).empty?
215
+ time_line << format('%s', part).color(FREE_COLOR) unless part.scan(FREE_PATTERN).empty?
216
+ time_line << format('%s', part).color(WORK_COLOR) unless part.scan(WORK_PATTERN).empty?
217
+ end
218
+ end
215
219
  time_line
216
220
  end
217
221
 
@@ -220,11 +224,11 @@ module Syctask
220
224
  def split_time_line(time_line)
221
225
  time = Time.now
222
226
  if time.hour < @starts.h
223
- past = ""
227
+ past = ''
224
228
  future = time_line
225
229
  else
226
- offset = (time.hour - @starts.h) * 4 + time.min.div(15)
227
- past = time_line.slice(0,offset)
230
+ offset = (time.hour - @starts.h) * 4 + time.min.div(15)
231
+ past = time_line.slice(0, offset)
228
232
  future = time_line.slice(offset, time_line.size - offset)
229
233
  end
230
234
  [past, future]
@@ -233,18 +237,18 @@ module Syctask
233
237
  # Assigns the tasks to the timeline in alternation x and o subsequent tasks.
234
238
  # Returns the task list and the task caption
235
239
  def assign_tasks_to_graph(time_line)
236
- done_tasks = []
240
+ done_tasks = []
237
241
  unscheduled_tasks = []
238
- signs = ['x','o']
242
+ signs = %w[x o]
239
243
  positions = {}
240
244
  current_time = Time.now
241
245
  unassigned_tasks.each.with_index do |task, index|
242
- if task.done? or not task.today?
246
+ if task.done? || !task.today?
243
247
  done_tasks << task
244
248
  next
245
249
  else
246
- round = task.remaining.to_i % 900 == 0 ? 0 : 0.5
247
- duration = [(task.remaining.to_i/900+round).round, 1].max
250
+ round = (task.remaining.to_i % 900).zero? ? 0 : 0.5
251
+ duration = [(task.remaining.to_i / 900 + round).round, 1].max
248
252
  position = [0, position_for_time(current_time)].max
249
253
  end
250
254
  free_time = scan_free(time_line, 1, position)
@@ -252,9 +256,10 @@ module Syctask
252
256
  unscheduled_tasks << task
253
257
  next
254
258
  end
255
- 0.upto(duration-1) do |i|
259
+ 0.upto(duration - 1) do |i|
256
260
  break unless free_time[i]
257
- time_line[free_time[i]] = signs[index%2]
261
+
262
+ time_line[free_time[i]] = signs[index % 2]
258
263
  end
259
264
  positions[free_time[0]] = task.id
260
265
  end
@@ -262,15 +267,16 @@ module Syctask
262
267
  unless done_tasks.empty?
263
268
  end_position = position_for_time(current_time)
264
269
  total_duration = 0
265
- done_tasks.each_with_index do |task,index|
270
+ done_tasks.each_with_index do |task, index|
266
271
  free_time = scan_free(time_line, 1, 0, end_position)
267
272
  lead_time = task.duration.to_i - task.remaining.to_i + 0.0
268
273
  max_duration = [free_time.size - (done_tasks.size - index - 1), 1].max
269
- duration = [(lead_time/900).round, 1].max
274
+ duration = [(lead_time / 900).round, 1].max
270
275
  total_duration += duration = [duration, max_duration].min
271
- 0.upto(duration-1) do |i|
276
+ 0.upto(duration - 1) do |i|
272
277
  break unless free_time[i]
273
- time_line[free_time[i]] = signs[index%2]
278
+
279
+ time_line[free_time[i]] = signs[index % 2]
274
280
  end
275
281
  positions[free_time[0]] = task.id if free_time[0]
276
282
  end
@@ -278,50 +284,50 @@ module Syctask
278
284
 
279
285
  # Create task list
280
286
  max_id_size = 1
281
- @tasks.each {|task| max_id_size = [task.id.to_s.size, max_id_size].max}
287
+ @tasks.each { |task| max_id_size = [task.id.to_s.size, max_id_size].max }
282
288
  max_ord_size = (@tasks.size - 1).to_s.size
283
289
 
284
- task_list = sprintf("%s", "Tasks\n").color(:blue)
285
- task_list << sprintf("%s", "-----\n").color(:blue)
290
+ task_list = format('%s', "Tasks\n").color(:blue)
291
+ task_list << format('%s', "-----\n").color(:blue)
286
292
  @tasks.each.with_index do |task, i|
287
- if task.done? or not task.today?
288
- color = :green
289
- elsif unscheduled_tasks.find_index(task)
290
- color = UNSCHEDULED_COLOR
291
- else
292
- color = WORK_COLOR
293
- end
294
-
295
- hint = "-"
296
- hint = "~" unless task.today?
297
- hint = "*" if task.tracked?
293
+ color = if task.done? or !task.today?
294
+ :green
295
+ elsif unscheduled_tasks.find_index(task)
296
+ UNSCHEDULED_COLOR
297
+ else
298
+ WORK_COLOR
299
+ end
300
+
301
+ hint = '-'
302
+ hint = '~' unless task.today?
303
+ hint = '*' if task.tracked?
298
304
 
299
305
  offset = max_ord_size + max_id_size + 5
300
- title = split_lines(task.title, 80-offset)
301
- title = title.chomp.gsub(/\n/, "\n#{' '*offset}")
302
- title << ">" if !task.options[:note].nil?
303
- task_list << sprintf("%#{max_ord_size}d: %#{max_id_size}s %s %s\n",
304
- i, task.id, hint, title).color(color)
306
+ title = split_lines(task.title, 80 - offset)
307
+ title = title.chomp.gsub(/\n/, "\n#{' ' * offset}")
308
+ title << '>' unless task.options[:note].nil?
309
+ task_list << format("%#{max_ord_size}d: %#{max_id_size}s %s %s\n",
310
+ i, task.id, hint, title).color(color)
305
311
  end
306
312
 
307
313
  # Create task caption
308
- task_caption = ""
309
- create_caption(positions).each do |caption|
310
- task_caption << sprintf("%s\n", caption).color(WORK_COLOR)
314
+ task_caption = ''
315
+ create_caption(positions).each do |caption|
316
+ task_caption << format("%s\n", caption).color(WORK_COLOR)
311
317
  end
312
318
 
313
319
  [task_list, task_caption]
314
-
315
320
  end
316
321
 
317
322
  # creates the caption of the graph with hours in 1 hour steps and task IDs
318
323
  # that indicate where in the schedule a task is scheduled.
319
324
  def create_caption(positions)
320
325
  counter = 0
321
- lines = [""]
322
- positions.each do |position,id|
326
+ lines = ['']
327
+ positions.each do |position, id|
323
328
  next unless position
324
- line_id = next_line(position,lines,counter)
329
+
330
+ line_id = next_line(position, lines, counter)
325
331
  legend = ' ' * [0, position - lines[line_id].size].max + id.to_s
326
332
  lines[line_id] += legend
327
333
  counter += 1
@@ -333,20 +339,21 @@ module Syctask
333
339
  # task ID of a previous task. The effect is shown below
334
340
  # |xx-|//o|x--|
335
341
  # 8 9 10 10
336
- # 10 101
342
+ # 10 101
337
343
  # 11 2
338
344
  # position is the position (time) within the schedule
339
345
  # lines is the available ID lines (above we have 2 ID lines)
340
346
  # counter is the currently displayed line. IDs are displayed alternating in
341
347
  # each line, when we have 2 lines IDs will be printed in line 1,2,1,2...
342
348
  def next_line(position, lines, counter)
343
- line = lines[counter%lines.size]
344
- return counter%lines.size if line.size == 0 or line.size < position - 1
349
+ line = lines[counter % lines.size]
350
+ return counter % lines.size if line.size == 0 || line.size < position - 1
351
+
345
352
  lines.each.with_index do |line, index|
346
353
  return index if line.size < position - 1
347
354
  end
348
- lines << ""
349
- return lines.size - 1
355
+ lines << ''
356
+ lines.size - 1
350
357
  end
351
358
 
352
359
  # Determines the position within the time line for the given time. Each
@@ -362,12 +369,12 @@ module Syctask
362
369
  # Scans the schedule for free time where a task can be added to. Count
363
370
  # specifies the length of the free time and the position where to start
364
371
  # scanning within the graph
365
- def scan_free(graph, count, starts, ends=graph.size)
366
- pattern = /(?!\/)[\|-]{#{count}}(?<=-|\||\/)/
372
+ def scan_free(graph, count, starts, ends = graph.size)
373
+ pattern = %r{(?!/)[|-]{#{count}}(?<=-|\||/)}
367
374
 
368
375
  positions = []
369
376
  index = starts
370
- while index and index < ends
377
+ while index && index < ends
371
378
  index = graph.index(pattern, index)
372
379
  if index
373
380
  positions << index if index < ends
@@ -387,7 +394,7 @@ module Syctask
387
394
 
388
395
  unassigned = []
389
396
  unassigned << @tasks
390
- unassigned.flatten.delete_if {|task| assigned.find_index(task)}
397
+ unassigned.flatten.delete_if { |task| assigned.find_index(task) }
391
398
  end
392
399
 
393
400
  public
@@ -396,16 +403,18 @@ module Syctask
396
403
  def get_times
397
404
  work_time = [@starts.h, @ends.round_up]
398
405
  meeting_times = []
399
- @meetings.each do |meeting|
400
- meeting_time = Array.new(2)
401
- meeting_time[0] = hour_offset(@starts.h, meeting.starts.h) +
402
- minute_offset(meeting.starts.m)
403
- meeting_time[1] = hour_offset(@starts.h, meeting.ends.h) +
404
- minute_offset(meeting.ends.m)
405
- meeting_times << meeting_time
406
- end if @meetings
407
-
408
- times = [work_time, meeting_times]
406
+ if @meetings
407
+ @meetings.each do |meeting|
408
+ meeting_time = Array.new(2)
409
+ meeting_time[0] = hour_offset(@starts.h, meeting.starts.h) +
410
+ minute_offset(meeting.starts.m)
411
+ meeting_time[1] = hour_offset(@starts.h, meeting.ends.h) +
412
+ minute_offset(meeting.ends.m)
413
+ meeting_times << meeting_time
414
+ end
415
+ end
416
+
417
+ [work_time, meeting_times]
409
418
  end
410
419
 
411
420
  private
@@ -419,6 +428,5 @@ module Syctask
419
428
  def minute_offset(minutes)
420
429
  minutes.to_i.div(15)
421
430
  end
422
-
423
431
  end
424
432
  end
@@ -13,8 +13,9 @@ module Syctask
13
13
  # system directory
14
14
  def tasks(tasks)
15
15
  service = Syctask::TaskService.new
16
- if File.exists? Syctask::DEFAULT_TASKS
17
- general = YAML.load_file(Syctask::DEFAULT_TASKS)
16
+ if File.exist? Syctask::DEFAULT_TASKS
17
+ general = YAML.safe_load_file(Syctask::DEFAULT_TASKS,
18
+ permitted_classes: [Syctask::Task, Symbol])
18
19
  else
19
20
  general = {}
20
21
  end
@@ -30,9 +31,10 @@ module Syctask
30
31
  # Retrieves the general purpose files from the default_tasks file in the
31
32
  # syctask system directory
32
33
  def read_tasks
33
- if File.exists? Syctask::DEFAULT_TASKS and not \
34
+ if File.exist? Syctask::DEFAULT_TASKS and not \
34
35
  File.read(Syctask::DEFAULT_TASKS).empty?
35
- YAML.load_file(Syctask::DEFAULT_TASKS)
36
+ YAML.safe_load_file(Syctask::DEFAULT_TASKS,
37
+ permitted_classes: [Syctask::Task, Symbol])
36
38
  else
37
39
  {}
38
40
  end
@@ -19,6 +19,12 @@ module Syctask
19
19
 
20
20
  # Creates a statistics report
21
21
  def report(file, from="", to=from)
22
+ unless File.exist? file
23
+ return sprintf("Warning: Statistics log file %s",
24
+ file.bright).color(:red) +
25
+ sprintf(" is missing!\n%sNo statistics available!\n",
26
+ " "*9).color(:red)
27
+ end
22
28
 
23
29
  from, to, time_log, count_log = logs(file, from, to)
24
30
  working_days = time_log["work"].count.to_s if time_log["work"]