doing 1.0.42 → 1.0.47

Sign up to get free protection for your applications and to get access to all the features.
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.42'
2
+ VERSION = '1.0.47'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
3
  require 'deep_merge'
4
+ require 'open3'
4
5
 
5
6
  ##
6
7
  ## @brief String helpers
7
8
  ##
8
9
  class String
9
10
  def cap_first
10
- self.sub(/^\w/) do |m|
11
+ sub(/^\w/) do |m|
11
12
  m.upcase
12
13
  end
13
14
  end
@@ -17,21 +18,21 @@ class String
17
18
  ##
18
19
  ## @param opt (Hash) Additional Options
19
20
  ##
20
- def link_urls(opt={})
21
+ def link_urls(opt = {})
21
22
  opt[:format] ||= :html
22
23
  if opt[:format] == :html
23
- self.gsub(/(?mi)((http|https):\/\/)?([\w\-_]+(\.[\w\-_]+)+)([\w\-\.,\@?^=%&:\/~\+#]*[\w\-\@^=%&\/~\+#])?/) {|match|
24
+ gsub(%r{(?mi)((http|https)://)?([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&:/~+#]*[\w\-@^=%&/~+#])?}) do |_match|
24
25
  m = Regexp.last_match
25
- proto = m[1].nil? ? "http://" : ""
26
- %Q{<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>}
27
- }.gsub(/\<(\w+:.*?)\>/) {|match|
26
+ proto = m[1].nil? ? 'http://' : ''
27
+ %(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
28
+ end.gsub(/<(\w+:.*?)>/) do |match|
28
29
  m = Regexp.last_match
29
- unless m[1] =~ /<a href/
30
- %Q{<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>}
31
- else
30
+ if m[1] =~ /<a href/
32
31
  match
32
+ else
33
+ %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
33
34
  end
34
- }
35
+ end
35
36
  else
36
37
  self
37
38
  end
@@ -42,7 +43,8 @@ end
42
43
  ## @brief Main "What Was I Doing" methods
43
44
  ##
44
45
  class WWID
45
- attr_accessor :content, :sections, :current_section, :doing_file, :config, :user_home, :default_config_file, :config_file, :results, :auto_tag
46
+ attr_accessor :content, :sections, :current_section, :doing_file, :config, :user_home, :default_config_file,
47
+ :config_file, :results, :auto_tag
46
48
 
47
49
  ##
48
50
  ## @brief Initializes the object.
@@ -62,16 +64,13 @@ class WWID
62
64
  ## @return (String) A file path
63
65
  ##
64
66
  def find_local_config
65
-
66
67
  config = {}
67
68
  dir = Dir.pwd
68
69
 
69
70
  local_config_files = []
70
71
 
71
- while (dir != '/' && (dir =~ /[A-Z]:\//) == nil)
72
- if File.exists? File.join(dir, @default_config_file)
73
- local_config_files.push(File.join(dir, @default_config_file))
74
- end
72
+ while dir != '/' && (dir =~ %r{[A-Z]:/}).nil?
73
+ local_config_files.push(File.join(dir, @default_config_file)) if File.exist? File.join(dir, @default_config_file)
75
74
 
76
75
  dir = File.dirname(dir)
77
76
  end
@@ -82,32 +81,30 @@ class WWID
82
81
  ##
83
82
  ## @brief Reads a configuration.
84
83
  ##
85
- def read_config(opt={})
86
- unless @config_file
87
- if Dir.respond_to?('home')
88
- @config_file = File.join(Dir.home, @default_config_file)
89
- else
90
- @config_file = File.join(File.expand_path("~"), @default_config_file)
91
- end
92
- end
84
+ def read_config(opt = {})
85
+ @config_file ||= if Dir.respond_to?('home')
86
+ File.join(Dir.home, @default_config_file)
87
+ else
88
+ File.join(File.expand_path('~'), @default_config_file)
89
+ end
93
90
 
94
- if opt[:ignore_local]
95
- additional_configs = []
96
- else
97
- additional_configs = find_local_config
98
- end
91
+ additional_configs = if opt[:ignore_local]
92
+ []
93
+ else
94
+ find_local_config
95
+ end
99
96
 
100
97
  begin
101
98
  @local_config = {}
102
99
 
103
- @config = YAML.load_file(@config_file) || {} if File.exists?(@config_file)
104
- additional_configs.each { |cfg|
100
+ @config = YAML.load_file(@config_file) || {} if File.exist?(@config_file)
101
+ additional_configs.each do |cfg|
105
102
  new_config = YAML.load_file(cfg) || {} if cfg
106
103
  @local_config = @local_config.deep_merge(new_config)
107
- }
104
+ end
108
105
 
109
106
  # @config.deep_merge(@local_config)
110
- rescue
107
+ rescue StandardError
111
108
  @config = {}
112
109
  @local_config = {}
113
110
  # raise "error reading config"
@@ -119,24 +116,20 @@ class WWID
119
116
  ##
120
117
  ## @param opt (Hash) Additional Options
121
118
  ##
122
- def configure(opt={})
119
+ def configure(opt = {})
123
120
  @timers = {}
124
121
  opt[:ignore_local] ||= false
125
122
 
126
- unless @config_file
127
- @config_file = File.join(@user_home, @default_config_file)
128
- end
129
-
130
- read_config({:ignore_local => opt[:ignore_local]})
123
+ @config_file ||= File.join(@user_home, @default_config_file)
131
124
 
132
- user_config = @config.dup
125
+ read_config({ ignore_local: opt[:ignore_local] })
133
126
 
134
127
  @config = {} if @config.nil?
135
128
 
136
129
  @config['autotag'] ||= {}
137
130
  @config['autotag']['whitelist'] ||= []
138
131
  @config['autotag']['synonyms'] ||= {}
139
- @config['doing_file'] ||= "~/what_was_i_doing.md"
132
+ @config['doing_file'] ||= '~/what_was_i_doing.md'
140
133
  @config['current_section'] ||= 'Currently'
141
134
  @config['editor_app'] ||= nil
142
135
 
@@ -177,12 +170,12 @@ class WWID
177
170
  'tags_bool' => 'OR'
178
171
  },
179
172
  'color' => {
180
- 'date_format' => '%F %_I:%M%P',
181
- 'template' => '%boldblack%date %boldgreen| %boldwhite%title%default%note',
182
- 'wrap_width' => 0,
183
- 'section' => 'Currently',
184
- 'count' => 10,
185
- 'order' => "asc"
173
+ 'date_format' => '%F %_I:%M%P',
174
+ 'template' => '%boldblack%date %boldgreen| %boldwhite%title%default%note',
175
+ 'wrap_width' => 0,
176
+ 'section' => 'Currently',
177
+ 'count' => 10,
178
+ 'order' => 'asc'
186
179
  }
187
180
  }
188
181
  @config['marker_tag'] ||= 'flagged'
@@ -202,17 +195,13 @@ class WWID
202
195
  # end
203
196
  # end
204
197
 
205
- unless File.exists?(@config_file)
206
- File.open(@config_file, 'w') { |yf| YAML::dump(@config, yf) }
207
- end
198
+ File.open(@config_file, 'w') { |yf| YAML.dump(@config, yf) } unless File.exist?(@config_file)
208
199
 
209
200
  @config = @local_config.deep_merge(@config)
210
201
 
211
202
  @current_section = @config['current_section']
212
203
  @default_template = @config['templates']['default']['template']
213
- @default_date_format = @config['templates']['default']['date_format'];
214
-
215
-
204
+ @default_date_format = @config['templates']['default']['date_format']
216
205
  end
217
206
 
218
207
  ##
@@ -220,16 +209,16 @@ class WWID
220
209
  ##
221
210
  ## @param path (String) Override path to a doing file, optional
222
211
  ##
223
- def init_doing_file(path=nil)
212
+ def init_doing_file(path = nil)
224
213
  @doing_file = File.expand_path(@config['doing_file'])
225
214
 
226
215
  input = path
227
216
 
228
217
  if input.nil?
229
- create(@doing_file) unless File.exists?(@doing_file)
218
+ create(@doing_file) unless File.exist?(@doing_file)
230
219
  input = IO.read(@doing_file)
231
220
  input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
232
- elsif File.exists?(File.expand_path(input)) && File.file?(File.expand_path(input)) && File.stat(File.expand_path(input)).size > 0
221
+ elsif File.exist?(File.expand_path(input)) && File.file?(File.expand_path(input)) && File.stat(File.expand_path(input)).size.positive?
233
222
  @doing_file = File.expand_path(input)
234
223
  input = IO.read(File.expand_path(input))
235
224
  input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
@@ -243,40 +232,36 @@ class WWID
243
232
  @other_content_top = []
244
233
  @other_content_bottom = []
245
234
 
246
- section = "Uncategorized"
235
+ section = 'Uncategorized'
247
236
  lines = input.split(/[\n\r]/)
248
237
  current = 0
249
238
 
250
- lines.each {|line|
239
+ lines.each do |line|
251
240
  next if line =~ /^\s*$/
241
+
252
242
  if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
253
- section = $1
243
+ section = Regexp.last_match(1)
254
244
  @content[section] = {}
255
245
  @content[section]['original'] = line
256
246
  @content[section]['items'] = []
257
247
  current = 0
258
248
  elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
259
- date = Time.parse($1)
260
- title = $2
261
- @content[section]['items'].push({'title' => title, 'date' => date, 'section' => section})
249
+ date = Time.parse(Regexp.last_match(1))
250
+ title = Regexp.last_match(2)
251
+ @content[section]['items'].push({ 'title' => title, 'date' => date, 'section' => section })
262
252
  current += 1
263
- else
253
+ elsif current.zero?
264
254
  # if content[section]['items'].length - 1 == current
265
- if current == 0
266
- @other_content_top.push(line)
267
- else
268
- if line =~ /^\S/
269
- @other_content_bottom.push(line)
270
- else
271
- unless @content[section]['items'][current - 1].has_key? 'note'
272
- @content[section]['items'][current - 1]['note'] = []
273
- end
274
- @content[section]['items'][current - 1]['note'].push(line.gsub(/ *$/,''))
275
- end
276
- end
255
+ @other_content_top.push(line)
256
+ elsif line =~ /^\S/
257
+ @other_content_bottom.push(line)
258
+ else
259
+ @content[section]['items'][current - 1]['note'] = [] unless @content[section]['items'][current - 1].key? 'note'
260
+
261
+ @content[section]['items'][current - 1]['note'].push(line.gsub(/ *$/, ''))
277
262
  # end
278
263
  end
279
- }
264
+ end
280
265
  end
281
266
 
282
267
  ##
@@ -300,14 +285,12 @@ class WWID
300
285
  ##
301
286
  ## @brief Create a new doing file
302
287
  ##
303
- def create(filename=nil)
304
- if filename.nil?
305
- filename = @doing_file
306
- end
307
- unless File.exists?(filename) && File.stat(filename).size > 0
308
- File.open(filename,'w+') do |f|
309
- f.puts @current_section + ":"
310
- end
288
+ def create(filename = nil)
289
+ filename = @doing_file if filename.nil?
290
+ return if File.exist?(filename) && File.stat(filename).size.positive?
291
+
292
+ File.open(filename, 'w+') do |f|
293
+ f.puts "#{@current_section}:"
311
294
  end
312
295
  end
313
296
 
@@ -316,22 +299,26 @@ class WWID
316
299
  ##
317
300
  ## @param input (String) Text input for editor
318
301
  ##
319
- def fork_editor(input="")
320
- tmpfile = Tempfile.new(['doing','.md'])
302
+ def fork_editor(input = '')
303
+ tmpfile = Tempfile.new(['doing', '.md'])
321
304
 
322
- File.open(tmpfile.path,'w+') do |f|
305
+ File.open(tmpfile.path, 'w+') do |f|
323
306
  f.puts input
324
307
  f.puts "\n# The first line is the entry title, any lines after that are added as a note"
325
308
  end
326
309
 
327
310
  pid = Process.fork { system("$EDITOR #{tmpfile.path}") }
328
311
 
329
- trap("INT") {
330
- Process.kill(9, pid) rescue Errno::ESRCH
312
+ trap('INT') do
313
+ begin
314
+ Process.kill(9, pid)
315
+ rescue StandardError
316
+ Errno::ESRCH
317
+ end
331
318
  tmpfile.unlink
332
319
  tmpfile.close!
333
320
  exit 0
334
- }
321
+ end
335
322
 
336
323
  Process.wait(pid)
337
324
 
@@ -339,7 +326,7 @@ class WWID
339
326
  if $?.exitstatus == 0
340
327
  input = IO.read(tmpfile.path)
341
328
  else
342
- raise "Cancelled"
329
+ raise 'Cancelled'
343
330
  end
344
331
  ensure
345
332
  tmpfile.close
@@ -357,15 +344,13 @@ class WWID
357
344
  # @param input (String) The string to parse
358
345
  #
359
346
  def format_input(input)
360
- raise "No content in entry" if input.nil? || input.strip.length == 0
347
+ raise 'No content in entry' if input.nil? || input.strip.empty?
348
+
361
349
  input_lines = input.split(/[\n\r]+/)
362
350
  title = input_lines[0].strip
363
351
  note = input_lines.length > 1 ? input_lines[1..-1] : []
364
- note.map! { |line|
365
- line.strip
366
- }.delete_if { |line|
367
- line =~ /^\s*$/ || line =~ /^#/
368
- }
352
+ note.map!(&:strip)
353
+ note.delete_if { |line| line =~ /^\s*$/ || line =~ /^#/ }
369
354
 
370
355
  [title, note]
371
356
  end
@@ -383,21 +368,22 @@ class WWID
383
368
  #
384
369
  def chronify(input)
385
370
  now = Time.now
386
- raise "Invalid time expression #{input.inspect}" if input.to_s.strip == ""
387
- secs_ago = if input.match /^(\d+)$/
388
- # plain number, assume minutes
389
- $1.to_i * 60
390
- elsif (m = input.match /^((?<day>\d+)d)?((?<hour>\d+)h)?((?<min>\d+)m)?$/i)
391
- # day/hour/minute format e.g. 1d2h30m
392
- [[m['day'], 24*3600],
393
- [m['hour'], 3600],
394
- [m['min'], 60]].map {|qty, secs| qty ? (qty.to_i * secs) : 0 }.reduce(0, :+)
395
- end
371
+ raise "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
372
+
373
+ secs_ago = if input.match(/^(\d+)$/)
374
+ # plain number, assume minutes
375
+ Regexp.last_match(1).to_i * 60
376
+ elsif (m = input.match(/^(?:(?<day>\d+)d)?(?:(?<hour>\d+)h)?(?:(?<min>\d+)m)?$/i))
377
+ # day/hour/minute format e.g. 1d2h30m
378
+ [[m['day'], 24 * 3600],
379
+ [m['hour'], 3600],
380
+ [m['min'], 60]].map { |qty, secs| qty ? (qty.to_i * secs) : 0 }.reduce(0, :+)
381
+ end
396
382
 
397
383
  if secs_ago
398
384
  now - secs_ago
399
385
  else
400
- Chronic.parse(input, {:context => :past, :ambiguous_time_range => 8})
386
+ Chronic.parse(input, { context: :past, ambiguous_time_range: 8 })
401
387
  end
402
388
  end
403
389
 
@@ -412,23 +398,24 @@ class WWID
412
398
  #
413
399
  def chronify_qty(qty)
414
400
  minutes = 0
415
- if qty.strip =~ /^(\d+):(\d\d)$/
416
- minutes += $1.to_i * 60
417
- minutes += $2.to_i
418
- elsif qty.strip =~ /^(\d+(?:\.\d+)?)([hmd])?$/
419
- amt = $1
420
- type = $2.nil? ? "m" : $2
401
+ case qty.strip
402
+ when /^(\d+):(\d\d)$/
403
+ minutes += Regexp.last_match(1).to_i * 60
404
+ minutes += Regexp.last_match(2).to_i
405
+ when /^(\d+(?:\.\d+)?)([hmd])?$/
406
+ amt = Regexp.last_match(1)
407
+ type = Regexp.last_match(2).nil? ? 'm' : Regexp.last_match(2)
421
408
 
422
409
  minutes = case type.downcase
423
- when 'm'
424
- amt.to_i
425
- when 'h'
426
- (amt.to_f * 60).round
427
- when 'd'
428
- (amt.to_f * 60 * 24).round
429
- else
430
- minutes
431
- end
410
+ when 'm'
411
+ amt.to_i
412
+ when 'h'
413
+ (amt.to_f * 60).round
414
+ when 'd'
415
+ (amt.to_f * 60 * 24).round
416
+ else
417
+ minutes
418
+ end
432
419
  end
433
420
  minutes * 60
434
421
  end
@@ -448,8 +435,8 @@ class WWID
448
435
  ## @param title (String) The new section title
449
436
  ##
450
437
  def add_section(title)
451
- @content[title.cap_first] = {'original' => "#{title}:", 'items' => []}
452
- @results.push(%Q{Added section "#{title.cap_first}"})
438
+ @content[title.cap_first] = { 'original' => "#{title}:", 'items' => [] }
439
+ @results.push(%(Added section "#{title.cap_first}"))
453
440
  end
454
441
 
455
442
  ##
@@ -458,32 +445,32 @@ class WWID
458
445
  ## @param frag (String) The user-provided string
459
446
  ## @param guessed (Boolean) already guessed and failed
460
447
  ##
461
- def guess_section(frag,guessed=false)
462
- return "All" if frag =~ /all/i
463
- sections.each {|section| return section.cap_first if frag.downcase == section.downcase }
448
+ def guess_section(frag, guessed: false)
449
+ return 'All' if frag =~ /all/i
450
+
451
+ sections.each { |section| return section.cap_first if frag.downcase == section.downcase }
464
452
  section = false
465
- re = frag.split('').join(".*?")
466
- sections.each {|sect|
467
- if sect =~ /#{re}/i
468
- $stderr.puts "Assuming you meant #{sect}"
469
- section = sect
470
- break
471
- end
472
- }
453
+ re = frag.split('').join('.*?')
454
+ sections.each do |sect|
455
+ next unless sect =~ /#{re}/i
456
+
457
+ warn "Assuming you meant #{sect}"
458
+ section = sect
459
+ break
460
+ end
473
461
  unless section || guessed
474
- alt = guess_view(frag,true)
475
- if alt
476
- raise "Did you mean `doing view #{alt}`?"
477
- else
478
- res = yn("Section #{frag} not found, create it",false)
462
+ alt = guess_view(frag, true)
463
+ raise "Did you mean `doing view #{alt}`?" if alt
479
464
 
480
- if res
481
- add_section(frag.cap_first)
482
- write(@doing_file)
483
- return frag.cap_first
484
- end
485
- raise "Unknown section: #{frag}"
465
+ res = yn("Section #{frag} not found, create it", default_response: false)
466
+
467
+ if res
468
+ add_section(frag.cap_first)
469
+ write(@doing_file)
470
+ return frag.cap_first
486
471
  end
472
+
473
+ raise "Unknown section: #{frag}"
487
474
  end
488
475
  section ? section.cap_first : guessed
489
476
  end
@@ -496,37 +483,31 @@ class WWID
496
483
  ##
497
484
  ## @return (Bool) yes or no
498
485
  ##
499
- def yn(question, default_response=false)
500
- if default_response
501
- default = 'y'
502
- else
503
- default = 'n'
504
- end
486
+ def yn(question, default_response: false)
487
+ default = default_response ? 'y' : 'n'
488
+
505
489
  # if this isn't an interactive shell, answer default
506
- unless $stdout.isatty
507
- if default.downcase == 'y'
508
- return true
509
- else
510
- return false
511
- end
512
- end
490
+ return default.downcase == 'y' unless $stdout.isatty
491
+
513
492
  # clear the buffer
514
- if ARGV.length
493
+ if ARGV&.length
515
494
  ARGV.length.times do
516
495
  ARGV.shift
517
496
  end
518
497
  end
519
498
  system 'stty cbreak'
520
- if default
521
- if default =~ /y/i
522
- options = "#{colors['white']}[#{colors['boldgreen']}Y#{colors['white']}/#{colors['boldwhite']}n#{colors['white']}]#{colors['default']}"
523
- else
524
- options = "#{colors['white']}[#{colors['boldwhite']}y#{colors['white']}/#{colors['boldgreen']}N#{colors['white']}]#{colors['default']}"
525
- end
526
- else
527
- options = "#{colors['white']}[#{colors['boldwhite']}y#{colors['white']}/#{colors['boldwhite']}n#{colors['white']}]#{colors['default']}"
528
- end
529
- $stdout.syswrite "#{colors['boldwhite']}#{question.sub(/\?$/,'')} #{options}#{colors['boldwhite']}?#{colors['default']} "
499
+
500
+ cw = colors['white']
501
+ cbw = colors['boldwhite']
502
+ cbg = colors['boldgreen']
503
+ cd = colors['default']
504
+
505
+ options = if default
506
+ default =~ /y/i ? "#{cw}[#{cbg}Y#{cw}/#{cbw}n#{cw}]#{cd}" : "#{cw}[#{cbw}y#{cw}/#{cbg}N#{cw}]#{cd}"
507
+ else
508
+ "#{cw}[#{cbw}y#{cw}/#{cbw}n#{cw}]#{cd}"
509
+ end
510
+ $stdout.syswrite "#{cbw}#{question.sub(/\?$/, '')} #{options}#{cbw}?#{cd} "
530
511
  res = $stdin.sysread 1
531
512
  puts
532
513
  system 'stty cooked'
@@ -534,9 +515,9 @@ class WWID
534
515
  res.chomp!
535
516
  res.downcase!
536
517
 
537
- res = default.downcase if res == ""
518
+ res = default.downcase if res == ''
538
519
 
539
- return res =~ /y/i
520
+ res =~ /y/i
540
521
  end
541
522
 
542
523
  ##
@@ -545,19 +526,19 @@ class WWID
545
526
  ## @param frag (String) The user-provided string
546
527
  ## @param guessed (Boolean) already guessed
547
528
  ##
548
- def guess_view(frag,guessed=false)
549
- views.each {|view| return view if frag.downcase == view.downcase}
529
+ def guess_view(frag, guessed = false)
530
+ views.each { |view| return view if frag.downcase == view.downcase }
550
531
  view = false
551
- re = frag.split('').join(".*?")
552
- views.each {|v|
553
- if v =~ /#{re}/i
554
- $stderr.puts "Assuming you meant #{v}"
555
- view = v
556
- break
557
- end
558
- }
532
+ re = frag.split('').join('.*?')
533
+ views.each do |v|
534
+ next unless v =~ /#{re}/i
535
+
536
+ warn "Assuming you meant #{v}"
537
+ view = v
538
+ break
539
+ end
559
540
  unless view || guessed
560
- alt = guess_section(frag,true)
541
+ alt = guess_section(frag, guessed: true)
561
542
  if alt
562
543
  raise "Did you mean `doing show #{alt}`?"
563
544
  else
@@ -574,7 +555,7 @@ class WWID
574
555
  ## @param section (String) The section to add to
575
556
  ## @param opt (Hash) Additional Options {:date, :note, :back, :timed}
576
557
  ##
577
- def add_item(title,section=nil,opt={})
558
+ def add_item(title, section = nil, opt = {})
578
559
  section ||= @current_section
579
560
  add_section(section) unless @content.has_key?(section)
580
561
  opt[:date] ||= Time.now
@@ -582,7 +563,7 @@ class WWID
582
563
  opt[:back] ||= Time.now
583
564
  opt[:timed] ||= false
584
565
 
585
- opt[:note] = [opt[:note]] if opt[:note].class == String
566
+ opt[:note] = [opt[:note]] if opt[:note].instance_of?(String)
586
567
 
587
568
  title = [title.strip.cap_first]
588
569
  title = title.join(' ')
@@ -590,39 +571,37 @@ class WWID
590
571
  if @auto_tag
591
572
  title = autotag(title)
592
573
  unless @config['default_tags'].empty?
593
- title += @config['default_tags'].map{|t|
594
- unless t.nil?
595
- dt = t.sub(/^ *@/,'').chomp
596
- if title =~ /@#{dt}/
597
- ""
598
- else
599
- ' @' + dt
600
- end
574
+ default_tags = @config['default_tags'].map do |t|
575
+ next if t.nil?
576
+
577
+ dt = t.sub(/^ *@/, '').chomp
578
+ if title =~ /@#{dt}/
579
+ ''
580
+ else
581
+ " @#{dt}"
601
582
  end
602
- }.delete_if {|t| t == "" }.join(" ")
583
+ end
584
+ default_tags.delete_if { |t| t == '' }
585
+ title += default_tags.join(' ')
603
586
  end
604
587
  end
605
- title.gsub!(/ +/,' ')
606
- entry = {'title' => title.strip, 'date' => opt[:back]}
607
- unless opt[:note].join('').strip == ''
608
- entry['note'] = opt[:note].map {|n| n.chomp}
609
- end
588
+ title.gsub!(/ +/, ' ')
589
+ entry = { 'title' => title.strip, 'date' => opt[:back] }
590
+ entry['note'] = opt[:note].map(&:chomp) unless opt[:note].join('').strip == ''
610
591
  items = @content[section]['items']
611
592
  if opt[:timed]
612
593
  items.reverse!
613
- items.each_with_index {|i,x|
614
- if i['title'] =~ / @done/
615
- next
616
- else
617
- items[x]['title'] = "#{i['title']} @done(#{opt[:back].strftime('%F %R')})"
618
- break
619
- end
620
- }
594
+ items.each_with_index do |i, x|
595
+ next if i['title'] =~ / @done/
596
+
597
+ items[x]['title'] = "#{i['title']} @done(#{opt[:back].strftime('%F %R')})"
598
+ break
599
+ end
621
600
  items.reverse!
622
601
  end
623
602
  items.push(entry)
624
603
  @content[section]['items'] = items
625
- @results.push(%Q{Added "#{entry['title']}" to #{section}})
604
+ @results.push(%(Added "#{entry['title']}" to #{section}))
626
605
  end
627
606
 
628
607
  ##
@@ -631,27 +610,79 @@ class WWID
631
610
  ## @param section (String) The section to retrieve from, default
632
611
  ## All
633
612
  ##
634
- def last_note(section="All")
613
+ def last_note(section = 'All')
635
614
  section = guess_section(section)
636
615
  if section =~ /^all$/i
637
- combined = {'items' => []}
638
- @content.each {|k,v|
616
+ combined = { 'items' => [] }
617
+ @content.each do |_k, v|
639
618
  combined['items'] += v['items']
640
- }
641
- section = combined['items'].dup.sort_by{|item| item['date'] }.reverse[0]['section']
619
+ end
620
+ section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
642
621
  end
643
622
 
644
- if @content.has_key?(section)
645
- last_item = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse[0]
646
- $stderr.puts "Editing note for #{last_item['title']}"
647
- note = ''
648
- unless last_item['note'].nil?
649
- note = last_item['note'].map{|line| line.strip }.join("\n")
623
+ raise "Section #{section} not found" unless @content.key?(section)
624
+
625
+ last_item = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse[0]
626
+ warn "Editing note for #{last_item['title']}"
627
+ note = ''
628
+ note = last_item['note'].map(&:strip).join("\n") unless last_item['note'].nil?
629
+ "#{last_item['title']}\n# EDIT BELOW THIS LINE ------------\n#{note}"
630
+ end
631
+
632
+ ##
633
+ ## @brief Restart the last entry
634
+ ##
635
+ ## @param opt (Hash) Additional Options
636
+ ##
637
+ def restart_last(opt = {})
638
+ opt[:section] ||= 'all'
639
+ opt[:note] ||= []
640
+
641
+ last = last_entry(opt)
642
+ if last.nil?
643
+ @results.push(%(No previous entry found))
644
+ return
645
+ end
646
+ # Remove @done tag
647
+ title = last['title'].sub(/\s*@done(\(.*?\))?/, '').chomp
648
+ @auto_tag = false
649
+ add_item(title, last['section'], { note: opt[:note], back: opt[:date], timed: true })
650
+ write(@doing_file)
651
+ end
652
+
653
+ ##
654
+ ## @brief Get the last entry
655
+ ##
656
+ ## @param opt (Hash) Additional Options
657
+ ##
658
+ def last_entry(opt = {})
659
+ opt[:section] ||= @current_section
660
+
661
+ sec_arr = []
662
+
663
+ if opt[:section].nil?
664
+ sec_arr = [@current_section]
665
+ elsif opt[:section].instance_of?(String)
666
+ if opt[:section] =~ /^all$/i
667
+ combined = { 'items' => [] }
668
+ @content.each do |_k, v|
669
+ combined['items'] += v['items']
670
+ end
671
+ items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
672
+ sec_arr.push(items[0]['section'])
673
+
674
+ sec_arr = sections
675
+ else
676
+ sec_arr = [guess_section(opt[:section])]
650
677
  end
651
- return "#{last_item['title']}\n# EDIT BELOW THIS LINE ------------\n#{note}"
652
- else
653
- raise "Section #{section} not found"
654
678
  end
679
+
680
+ all_items = []
681
+ sec_arr.each do |section|
682
+ all_items.concat(@content[section]['items'].dup) if @content.key?(section)
683
+ end
684
+
685
+ all_items.max_by { |item| item['date'] }
655
686
  end
656
687
 
657
688
  ##
@@ -659,33 +690,32 @@ class WWID
659
690
  ##
660
691
  ## @param opt (Hash) Additional Options
661
692
  ##
662
- def tag_last(opt={})
693
+ def tag_last(opt = {})
663
694
  opt[:section] ||= @current_section
664
695
  opt[:count] ||= 1
665
696
  opt[:archive] ||= false
666
- opt[:tags] ||= ["done"]
697
+ opt[:tags] ||= ['done']
667
698
  opt[:sequential] ||= false
668
699
  opt[:date] ||= false
669
700
  opt[:remove] ||= false
670
701
  opt[:autotag] ||= false
671
702
  opt[:back] ||= false
672
703
 
673
-
674
704
  sec_arr = []
675
705
 
676
706
  if opt[:section].nil?
677
707
  sec_arr = [@current_section]
678
- elsif opt[:section].class == String
708
+ elsif opt[:section].instance_of?(String)
679
709
  if opt[:section] =~ /^all$/i
680
710
  if opt[:count] == 1
681
- combined = {'items' => []}
682
- @content.each {|k,v|
711
+ combined = { 'items' => [] }
712
+ @content.each do |_k, v|
683
713
  combined['items'] += v['items']
684
- }
685
- items = combined['items'].dup.sort_by{|item| item['date'] }.reverse
714
+ end
715
+ items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
686
716
  sec_arr.push(items[0]['section'])
687
717
  elsif opt[:count] > 1
688
- raise "A count greater than one requires a section to be specified"
718
+ raise 'A count greater than one requires a section to be specified'
689
719
  else
690
720
  sec_arr = sections
691
721
  end
@@ -694,21 +724,27 @@ class WWID
694
724
  end
695
725
  end
696
726
 
727
+ sec_arr.each do |section|
728
+ if @content.key?(section)
697
729
 
698
-
699
- sec_arr.each {|section|
700
- if @content.has_key?(section)
701
-
702
- items = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse
730
+ items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
703
731
 
704
732
  index = 0
705
733
  done_date = Time.now
706
734
  next_start = Time.now
707
- count = opt[:count] == 0 ? items.length : opt[:count]
708
- items.map! {|item|
735
+ count = (opt[:count]).zero? ? items.length : opt[:count]
736
+ items.map! do |item|
709
737
  break if index == count
710
738
 
711
- unless opt[:autotag]
739
+ if opt[:autotag]
740
+ new_title = autotag(item['title']) if @auto_tag
741
+ if new_title == item['title']
742
+ @results.push(%(Autotag: No changes))
743
+ else
744
+ @results.push("Tags updated: #{new_title}")
745
+ item['title'] = new_title
746
+ end
747
+ else
712
748
  if opt[:sequential]
713
749
  done_date = next_start - 1
714
750
  next_start = item['date']
@@ -719,62 +755,50 @@ class WWID
719
755
  end
720
756
 
721
757
  title = item['title']
722
- opt[:tags].each {|tag|
758
+ opt[:tags].each do |tag|
723
759
  tag.strip!
724
- if opt[:remove]
725
- if title =~ /@#{tag}\b/
726
- title.gsub!(/(^| )@#{tag}(\([^\)]*\))?/,'')
727
- @results.push(%Q{Removed @#{tag}: "#{title}" in #{section}})
728
- end
729
- else
730
- unless title =~ /@#{tag}/
731
- title.chomp!
732
- if opt[:date]
733
- title += " @#{tag}(#{done_date.strftime('%F %R')})"
734
- else
735
- title += " @#{tag}"
736
- end
737
- @results.push(%Q{Added @#{tag}: "#{title}" in #{section}})
738
- end
760
+ if opt[:remove] && title =~ /@#{tag}\b/
761
+ title.gsub!(/(^| )@#{tag}(\([^)]*\))?/, '')
762
+ @results.push(%(Removed @#{tag}: "#{title}" in #{section}))
763
+ elsif title !~ /@#{tag}/
764
+ title.chomp!
765
+ title += if opt[:date]
766
+ " @#{tag}(#{done_date.strftime('%F %R')})"
767
+ else
768
+ " @#{tag}"
769
+ end
770
+ @results.push(%(Added @#{tag}: "#{title}" in #{section}))
739
771
  end
740
- }
741
- item['title'] = title
742
- else
743
- new_title = autotag(item['title']) if @auto_tag
744
- unless new_title == item['title']
745
- @results.push("Tags updated: #{new_title}")
746
- item['title'] = new_title
747
- else
748
- @results.push(%Q{Autotag: No changes})
749
772
  end
773
+ item['title'] = title
750
774
  end
751
775
 
752
776
  index += 1
753
777
 
754
778
  item
755
- }
779
+ end
756
780
 
757
781
  @content[section]['items'] = items
758
782
 
759
- if opt[:archive] && section != "Archive" && opt[:count] > 0
783
+ if opt[:archive] && section != 'Archive' && (opt[:count]).positive?
760
784
  # concat [count] items from [section] and archive section
761
- archived = @content[section]['items'][0..opt[:count]-1].map {|i|
762
- i['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{i['section']})")
763
- }.concat(@content['Archive']['items'])
785
+ archived = @content[section]['items'][0..opt[:count] - 1].map do |i|
786
+ i['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{i['section']})")
787
+ end.concat(@content['Archive']['items'])
764
788
  # chop [count] items off of [section] items
765
789
  @content[opt[:section]]['items'] = @content[opt[:section]]['items'][opt[:count]..-1]
766
790
  # overwrite archive section with concatenated array
767
791
  @content['Archive']['items'] = archived
768
792
  # log it
769
- result = opt[:count] == 1 ? "1 entry" : "#{opt[:count]} entries"
793
+ result = opt[:count] == 1 ? '1 entry' : "#{opt[:count]} entries"
770
794
  @results.push("Archived #{result} from #{section}")
771
- elsif opt[:archive] && opt[:count] == 0
772
- @results.push("Archiving is skipped when operating on all entries") if opt[:count] == 0
795
+ elsif opt[:archive] && (opt[:count]).zero?
796
+ @results.push('Archiving is skipped when operating on all entries') if (opt[:count]).zero?
773
797
  end
774
798
  else
775
799
  raise "Section not found: #{section}"
776
800
  end
777
- }
801
+ end
778
802
 
779
803
  write(@doing_file)
780
804
  end
@@ -786,49 +810,47 @@ class WWID
786
810
  ## @param note (String) The note to add
787
811
  ## @param replace (Bool) Should replace existing note
788
812
  ##
789
- def note_last(section, note, replace=false)
813
+ def note_last(section, note, replace: false)
790
814
  section = guess_section(section)
791
815
 
792
816
  if section =~ /^all$/i
793
- combined = {'items' => []}
794
- @content.each {|k,v|
817
+ combined = { 'items' => [] }
818
+ @content.each do |_k, v|
795
819
  combined['items'] += v['items']
796
- }
797
- section = combined['items'].dup.sort_by{|item| item['date'] }.reverse[0]['section']
820
+ end
821
+ section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
798
822
  end
799
823
 
800
- if @content.has_key?(section)
801
- # sort_section(opt[:section])
802
- items = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse
803
-
804
- current_note = items[0]['note']
805
- current_note = [] if current_note.nil?
806
- title = items[0]['title']
807
- if replace
808
- items[0]['note'] = note
809
- if note.empty? && !current_note.empty?
810
- @results.push(%Q{Removed note from "#{title}"})
811
- elsif current_note.length > 0 && note.length > 0
812
- @results.push(%Q{Replaced note from "#{title}"})
813
- elsif note.length > 0
814
- @results.push(%Q{Added note to "#{title}"})
815
- else
816
- @results.push(%Q{Entry "#{title}" has no note})
817
- end
818
- elsif current_note.class == Array
819
- items[0]['note'] = current_note.concat(note)
820
- @results.push(%Q{Added note to "#{title}"}) if note.length > 0
824
+ raise "Section #{section} not found" unless @content.key?(section)
825
+
826
+ # sort_section(opt[:section])
827
+ items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
828
+
829
+ current_note = items[0]['note']
830
+ current_note = [] if current_note.nil?
831
+ title = items[0]['title']
832
+ if replace
833
+ items[0]['note'] = note
834
+ if note.empty? && !current_note.empty?
835
+ @results.push(%(Removed note from "#{title}"))
836
+ elsif !current_note.empty? && !note.empty?
837
+ @results.push(%(Replaced note from "#{title}"))
838
+ elsif !note.empty?
839
+ @results.push(%(Added note to "#{title}"))
821
840
  else
822
- items[0]['note'] = note
823
- @results.push(%Q{Added note to "#{title}"}) if note.length > 0
841
+ @results.push(%(Entry "#{title}" has no note))
824
842
  end
825
-
826
- @content[section]['items'] = items
843
+ elsif current_note.instance_of?(Array)
844
+ items[0]['note'] = current_note.concat(note)
845
+ @results.push(%(Added note to "#{title}")) unless note.empty?
827
846
  else
828
- raise "Section #{section} not found"
847
+ items[0]['note'] = note
848
+ @results.push(%(Added note to "#{title}")) unless note.empty?
829
849
  end
830
- end
831
850
 
851
+ @content[section]['items'] = items
852
+
853
+ end
832
854
 
833
855
  #
834
856
  # @brief Accepts one tag and the raw text of a new item if the passed tag
@@ -840,7 +862,7 @@ class WWID
840
862
  # @param tag (String) Tag to replace
841
863
  # @param opt (Hash) Additional Options
842
864
  #
843
- def stop_start(tag,opt={})
865
+ def stop_start(tag, opt = {})
844
866
  opt[:section] ||= @current_section
845
867
  opt[:archive] ||= false
846
868
  opt[:back] ||= Time.now
@@ -849,36 +871,36 @@ class WWID
849
871
 
850
872
  opt[:section] = guess_section(opt[:section])
851
873
 
852
- tag.sub!(/^@/,'')
874
+ tag.sub!(/^@/, '')
853
875
 
854
876
  found_items = 0
855
- @content[opt[:section]]['items'].each_with_index {|item, i|
856
- if item['title'] =~ /@#{tag}/
857
- title = item['title'].gsub(/(^| )@(#{tag}|done)(\([^\)]*\))?/,'')
858
- title += " @done(#{opt[:back].strftime('%F %R')})"
859
-
860
- @content[opt[:section]]['items'][i]['title'] = title
861
- found_items += 1
862
-
863
- if opt[:archive] && opt[:section] != "Archive"
864
- @results.push(%Q{Completed and archived "#{@content[opt[:section]]['items'][i]['title']}"})
865
- archive_item = @content[opt[:section]]['items'][i]
866
- archive_item['title'] = i['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{i['section']})")
867
- @content['Archive']['items'].push(archive_item)
868
- @content[opt[:section]]['items'].delete_at(i)
869
- else
870
- @results.push(%Q{Completed "#{@content[opt[:section]]['items'][i]['title']}"})
871
- end
877
+ @content[opt[:section]]['items'].each_with_index do |item, i|
878
+ next unless item['title'] =~ /@#{tag}/
879
+
880
+ title = item['title'].gsub(/(^| )@(#{tag}|done)(\([^)]*\))?/, '')
881
+ title += " @done(#{opt[:back].strftime('%F %R')})"
882
+
883
+ @content[opt[:section]]['items'][i]['title'] = title
884
+ found_items += 1
885
+
886
+ if opt[:archive] && opt[:section] != 'Archive'
887
+ @results.push(%(Completed and archived "#{@content[opt[:section]]['items'][i]['title']}"))
888
+ archive_item = @content[opt[:section]]['items'][i]
889
+ archive_item['title'] = i['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{i['section']})")
890
+ @content['Archive']['items'].push(archive_item)
891
+ @content[opt[:section]]['items'].delete_at(i)
892
+ else
893
+ @results.push(%(Completed "#{@content[opt[:section]]['items'][i]['title']}"))
872
894
  end
873
- }
895
+ end
874
896
 
875
897
  @results.push("No active @#{tag} tasks found.") if found_items == 0
876
898
 
877
899
  if opt[:new_item]
878
900
  title, note = format_input(opt[:new_item])
879
- note.push(opt[:note].gsub(/ *$/,'')) if opt[:note]
901
+ note.push(opt[:note].gsub(/ *$/, '')) if opt[:note]
880
902
  title += " @#{tag}"
881
- add_item(title.cap_first, opt[:section], {:note => note.join(' ').rstrip, :back => opt[:back]})
903
+ add_item(title.cap_first, opt[:section], { note: note.join(' ').rstrip, back: opt[:back] })
882
904
  end
883
905
 
884
906
  write(@doing_file)
@@ -889,28 +911,34 @@ class WWID
889
911
  ##
890
912
  ## @param file (String) The filepath to write to
891
913
  ##
892
- def write(file=nil)
893
- unless @other_content_top
894
- output = ""
895
- else
896
- output = @other_content_top.join("\n") + "\n"
914
+ def write(file = nil)
915
+ output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
916
+
917
+ @content.each do |title, section|
918
+ output += "#{section['original']}\n"
919
+ output += list_section({ section: title, template: "\t- %date | %title%note", highlight: false })
897
920
  end
898
- @content.each {|title, section|
899
- output += section['original'] + "\n"
900
- output += list_section({:section => title, :template => "\t- %date | %title%note", :highlight => false})
901
- }
902
921
  output += @other_content_bottom.join("\n") unless @other_content_bottom.nil?
903
922
  if file.nil?
904
923
  $stdout.puts output
905
924
  else
906
- if File.exists?(File.expand_path(file))
925
+ file = File.expand_path(file)
926
+ if File.exist?(file)
907
927
  # Create a backup copy for the undo command
908
- FileUtils.cp(file,file+"~")
928
+ FileUtils.cp(file, "#{file}~")
909
929
 
910
- File.open(File.expand_path(file),'w+') do |f|
930
+ File.open(file, 'w+') do |f|
911
931
  f.puts output
912
932
  end
913
933
  end
934
+
935
+ if @config.key?('run_after')
936
+ stdout, stderr, status = Open3.capture3(@config['run_after'])
937
+ if status.exitstatus.positive?
938
+ warn "Error running #{@config['run_after']}"
939
+ warn stderr
940
+ end
941
+ end
914
942
  end
915
943
  end
916
944
 
@@ -920,27 +948,27 @@ class WWID
920
948
  ## @param file (String) The filepath to restore
921
949
  ##
922
950
  def restore_backup(file)
923
- if File.exists?(file+"~")
924
- puts file+"~"
925
- FileUtils.cp(file+"~",file)
951
+ if File.exist?(file + '~')
952
+ puts file + '~'
953
+ FileUtils.cp(file + '~', file)
926
954
  @results.push("Restored #{file}")
927
955
  end
928
956
  end
929
957
 
930
-
931
958
  ##
932
959
  ## @brief Generate a menu of sections and allow user selection
933
960
  ##
934
961
  ## @return (String) The selected section name
935
962
  ##
936
963
  def choose_section
937
- sections.each_with_index {|section, i|
938
- puts "% 3d: %s" % [i+1, section]
939
- }
964
+ sections.each_with_index do |section, i|
965
+ puts format('% 3d: %s', i + 1, section)
966
+ end
940
967
  print "#{colors['green']}> #{colors['default']}"
941
968
  num = STDIN.gets
942
969
  return false if num =~ /^[a-z ]*$/i
943
- return sections[num.to_i - 1]
970
+
971
+ sections[num.to_i - 1]
944
972
  end
945
973
 
946
974
  ##
@@ -958,13 +986,14 @@ class WWID
958
986
  ## @return (String) The selected view name
959
987
  ##
960
988
  def choose_view
961
- views.each_with_index {|view, i|
962
- puts "% 3d: %s" % [i+1, view]
963
- }
964
- print "> "
989
+ views.each_with_index do |view, i|
990
+ puts format('% 3d: %s', i + 1, view)
991
+ end
992
+ print '> '
965
993
  num = STDIN.gets
966
994
  return false if num =~ /^[a-z ]*$/i
967
- return views[num.to_i - 1]
995
+
996
+ views[num.to_i - 1]
968
997
  end
969
998
 
970
999
  ##
@@ -973,9 +1002,8 @@ class WWID
973
1002
  ## @param title (String) The title of the view to retrieve
974
1003
  ##
975
1004
  def get_view(title)
976
- if @config['views'].has_key?(title)
977
- return @config['views'][title]
978
- end
1005
+ return @config['views'][title] if @config['views'].has_key?(title)
1006
+
979
1007
  false
980
1008
  end
981
1009
 
@@ -985,14 +1013,14 @@ class WWID
985
1013
  ##
986
1014
  ## @param opt (Hash) Additional Options
987
1015
  ##
988
- def list_section(opt={})
1016
+ def list_section(opt = {})
989
1017
  opt[:count] ||= 0
990
1018
  count = opt[:count] - 1
991
1019
  opt[:section] ||= nil
992
1020
  opt[:format] ||= @default_date_format
993
1021
  opt[:template] ||= @default_template
994
- opt[:age] ||= "newest"
995
- opt[:order] ||= "desc"
1022
+ opt[:age] ||= 'newest'
1023
+ opt[:order] ||= 'desc'
996
1024
  opt[:today] ||= false
997
1025
  opt[:tag_filter] ||= false
998
1026
  opt[:tags_color] ||= false
@@ -1004,17 +1032,23 @@ class WWID
1004
1032
  opt[:date_filter] ||= []
1005
1033
 
1006
1034
  # opt[:highlight] ||= true
1007
- section = ""
1035
+ section = ''
1008
1036
  if opt[:section].nil?
1009
1037
  section = choose_section
1010
1038
  opt[:section] = @content[section]
1011
- elsif opt[:section].class == String
1039
+ elsif opt[:section].instance_of?(String)
1012
1040
  if opt[:section] =~ /^all$/i
1013
- combined = {'items' => []}
1014
- @content.each {|k,v|
1041
+ combined = { 'items' => [] }
1042
+ @content.each do |_k, v|
1015
1043
  combined['items'] += v['items']
1016
- }
1017
- section = opt[:tag_filter] && opt[:tag_filter]['bool'] != 'NONE' ? opt[:tag_filter]['tags'].map {|tag| "@#{tag}"}.join(" + ") : "doing"
1044
+ end
1045
+ section = if opt[:tag_filter] && opt[:tag_filter]['bool'] != 'NONE'
1046
+ opt[:tag_filter]['tags'].map do |tag|
1047
+ "@#{tag}"
1048
+ end.join(' + ')
1049
+ else
1050
+ 'doing'
1051
+ end
1018
1052
  opt[:section] = combined
1019
1053
  else
1020
1054
  section = guess_section(opt[:section])
@@ -1023,209 +1057,201 @@ class WWID
1023
1057
  end
1024
1058
 
1025
1059
  if opt[:section].class != Hash
1026
- $stderr.puts "Invalid section object"
1060
+ warn 'Invalid section object'
1027
1061
  return
1028
1062
  end
1029
1063
 
1030
- items = opt[:section]['items'].sort_by{|item| item['date'] }
1064
+ items = opt[:section]['items'].sort_by { |item| item['date'] }
1031
1065
 
1032
1066
  if opt[:date_filter].length == 2
1033
1067
  start_date = opt[:date_filter][0]
1034
1068
  end_date = opt[:date_filter][1]
1035
- items.keep_if {|item|
1069
+ items.keep_if do |item|
1036
1070
  if end_date
1037
1071
  item['date'] >= start_date && item['date'] <= end_date
1038
1072
  else
1039
1073
  item['date'].strftime('%F') == start_date.strftime('%F')
1040
1074
  end
1041
- }
1075
+ end
1042
1076
  end
1043
1077
 
1044
1078
  if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
1045
- items.delete_if {|item|
1079
+ items.delete_if do |item|
1046
1080
  if opt[:tag_filter]['bool'] =~ /(AND|ALL)/
1047
1081
  score = 0
1048
- opt[:tag_filter]['tags'].each {|tag|
1082
+ opt[:tag_filter]['tags'].each do |tag|
1049
1083
  score += 1 if item['title'] =~ /@#{tag}/
1050
- }
1084
+ end
1051
1085
  score < opt[:tag_filter]['tags'].length
1052
1086
  elsif opt[:tag_filter]['bool'] =~ /NONE/
1053
1087
  del = false
1054
- opt[:tag_filter]['tags'].each {|tag|
1088
+ opt[:tag_filter]['tags'].each do |tag|
1055
1089
  del = true if item['title'] =~ /@#{tag}/
1056
- }
1090
+ end
1057
1091
  del
1058
1092
  elsif opt[:tag_filter]['bool'] =~ /(OR|ANY)/
1059
1093
  del = true
1060
- opt[:tag_filter]['tags'].each {|tag|
1094
+ opt[:tag_filter]['tags'].each do |tag|
1061
1095
  del = false if item['title'] =~ /@#{tag}/
1062
- }
1096
+ end
1063
1097
  del
1064
1098
  end
1065
- }
1099
+ end
1066
1100
  end
1067
1101
 
1068
1102
  if opt[:search]
1069
- items.keep_if {|item|
1070
- text = item['note'] ? item['title'] + item['note'].join(" ") : item['title']
1071
- if opt[:search].strip =~ /^\/.*?\/$/
1072
- pattern = opt[:search].sub(/\/(.*?)\//,'\1')
1073
- else
1074
- pattern = opt[:search].split('').join('.{0,3}')
1075
- end
1103
+ items.keep_if do |item|
1104
+ text = item['note'] ? item['title'] + item['note'].join(' ') : item['title']
1105
+ pattern = if opt[:search].strip =~ %r{^/.*?/$}
1106
+ opt[:search].sub(%r{/(.*?)/}, '\1')
1107
+ else
1108
+ opt[:search].split('').join('.{0,3}')
1109
+ end
1076
1110
  text =~ /#{pattern}/i
1077
- }
1111
+ end
1078
1112
  end
1079
1113
 
1080
1114
  if opt[:only_timed]
1081
- items.delete_if {|item|
1115
+ items.delete_if do |item|
1082
1116
  get_interval(item) == false
1083
- }
1117
+ end
1084
1118
  end
1085
1119
 
1086
1120
  if opt[:today]
1087
- items.delete_if {|item|
1121
+ items.delete_if do |item|
1088
1122
  item['date'] < Date.today.to_time
1089
- }.reverse!
1123
+ end.reverse!
1090
1124
  section = Time.now.strftime('%A, %B %d')
1091
1125
  elsif opt[:yesterday]
1092
- items.delete_if {|item| item['date'] <= Date.today.prev_day.to_time or
1093
- item['date'] >= Date.today.to_time
1094
- }.reverse!
1126
+ items.delete_if do |item|
1127
+ item['date'] <= Date.today.prev_day.to_time or
1128
+ item['date'] >= Date.today.to_time
1129
+ end.reverse!
1130
+ elsif opt[:age] =~ /oldest/i
1131
+ items = items[0..count]
1095
1132
  else
1096
- if opt[:age] =~ /oldest/i
1097
- items = items[0..count]
1098
- else
1099
- items = items.reverse[0..count]
1100
- end
1133
+ items = items.reverse[0..count]
1101
1134
  end
1102
1135
 
1103
- if opt[:order] =~ /^a/i
1104
- items.reverse!
1105
- end
1136
+ items.reverse! if opt[:order] =~ /^a/i
1106
1137
 
1107
- out = ""
1138
+ out = ''
1108
1139
 
1109
- if opt[:output]
1110
- raise "Unknown output format" unless opt[:output] =~ /(template|html|csv|json|timeline)/
1111
- end
1112
- if opt[:output] == "csv"
1113
- output = [CSV.generate_line(['date','title','note','timer','section'])]
1114
- items.each {|i|
1115
- note = ""
1140
+ raise 'Unknown output format' if opt[:output] && !(opt[:output] =~ /(template|html|csv|json|timeline)/)
1141
+
1142
+ if opt[:output] == 'csv'
1143
+ output = [CSV.generate_line(%w[date title note timer section])]
1144
+ items.each do |i|
1145
+ note = ''
1116
1146
  if i['note']
1117
- arr = i['note'].map{|line| line.strip}.delete_if{|e| e =~ /^\s*$/}
1147
+ arr = i['note'].map { |line| line.strip }.delete_if { |e| e =~ /^\s*$/ }
1118
1148
  note = arr.join("\n") unless arr.nil?
1119
1149
  end
1120
- if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1121
- interval = get_interval(i, false)
1122
- end
1150
+ interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1123
1151
  interval ||= 0
1124
- output.push(CSV.generate_line([i['date'],i['title'],note,interval,i['section']]))
1125
- }
1126
- out = output.join("")
1127
- elsif opt[:output] == "json" || opt[:output] == "timeline"
1152
+ output.push(CSV.generate_line([i['date'], i['title'], note, interval, i['section']]))
1153
+ end
1154
+ out = output.join('')
1155
+ elsif opt[:output] == 'json' || opt[:output] == 'timeline'
1128
1156
  items_out = []
1129
1157
  max = items[-1]['date'].strftime('%F')
1130
1158
  min = items[0]['date'].strftime('%F')
1131
- items.each_with_index {|i,index|
1159
+ items.each_with_index do |i, index|
1132
1160
  if String.method_defined? :force_encoding
1133
1161
  title = i['title'].force_encoding('utf-8')
1134
- note = i['note'].map {|line| line.force_encoding('utf-8').strip } if i['note']
1162
+ note = i['note'].map { |line| line.force_encoding('utf-8').strip } if i['note']
1135
1163
  else
1136
1164
  title = i['title']
1137
1165
  note = i['note'].map { |line| line.strip } if i['note']
1138
1166
  end
1139
1167
  if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1140
- end_date = Time.parse($1)
1141
- interval = get_interval(i,false)
1168
+ end_date = Time.parse(Regexp.last_match(1))
1169
+ interval = get_interval(i, false)
1142
1170
  end
1143
- end_date ||= ""
1171
+ end_date ||= ''
1144
1172
  interval ||= 0
1145
- note ||= ""
1173
+ note ||= ''
1146
1174
 
1147
1175
  tags = []
1148
- skip_tags = ['meanwhile', 'done', 'cancelled', 'flagged']
1149
- i['title'].scan(/@([^\(\s]+)(?:\((.*?)\))?/).each {|tag|
1176
+ skip_tags = %w[meanwhile done cancelled flagged]
1177
+ i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
1150
1178
  tags.push(tag[0]) unless skip_tags.include?(tag[0])
1151
- }
1152
- if opt[:output] == "json"
1179
+ end
1180
+ if opt[:output] == 'json'
1153
1181
 
1154
1182
  items_out << {
1155
- :date => i['date'],
1156
- :end_date => end_date,
1157
- :title => title.strip, #+ " #{note}"
1158
- :note => note.class == Array ? note.map(&:strip).join("\n") : note,
1159
- :time => "%02d:%02d:%02d" % fmt_time(interval),
1160
- :tags => tags
1183
+ date: i['date'],
1184
+ end_date: end_date,
1185
+ title: title.strip, #+ " #{note}"
1186
+ note: note.instance_of?(Array) ? note.map(&:strip).join("\n") : note,
1187
+ time: '%02d:%02d:%02d' % fmt_time(interval),
1188
+ tags: tags
1161
1189
  }
1162
1190
 
1163
- elsif opt[:output] == "timeline"
1191
+ elsif opt[:output] == 'timeline'
1164
1192
  new_item = {
1165
1193
  'id' => index + 1,
1166
1194
  'content' => title.strip, #+ " #{note}"
1167
- 'title' => title.strip + " (#{"%02d:%02d:%02d" % fmt_time(interval)})",
1195
+ 'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
1168
1196
  'start' => i['date'].strftime('%F'),
1169
1197
  'type' => 'point'
1170
1198
  }
1171
1199
 
1172
1200
  if interval && interval > 0
1173
1201
  new_item['end'] = end_date.strftime('%F')
1174
- if interval > 3600 * 3
1175
- new_item['type'] = 'range'
1176
- end
1202
+ new_item['type'] = 'range' if interval > 3600 * 3
1177
1203
  end
1178
1204
  items_out.push(new_item)
1179
1205
  end
1180
- }
1181
- if opt[:output] == "json"
1206
+ end
1207
+ if opt[:output] == 'json'
1182
1208
  out = {
1183
1209
  'section' => section,
1184
1210
  'items' => items_out,
1185
- 'timers' => tag_times("json", opt[:sort_tags])
1211
+ 'timers' => tag_times('json', opt[:sort_tags])
1186
1212
  }.to_json
1187
- elsif opt[:output] == "timeline"
1188
- template =<<EOTEMPLATE
1189
- <!doctype html>
1190
- <html>
1191
- <head>
1192
- <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
1193
- <script src="http://visjs.org/dist/vis.js"></script>
1194
- </head>
1195
- <body>
1196
- <div id="mytimeline"></div>
1197
-
1198
- <script type="text/javascript">
1199
- // DOM element where the Timeline will be attached
1200
- var container = document.getElementById('mytimeline');
1201
-
1202
- // Create a DataSet with data (enables two way data binding)
1203
- var data = new vis.DataSet(#{items_out.to_json});
1204
-
1205
- // Configuration for the Timeline
1206
- var options = {
1207
- width: '100%',
1208
- height: '800px',
1209
- margin: {
1210
- item: 20
1211
- },
1212
- stack: true,
1213
- min: '#{min}',
1214
- max: '#{max}'
1215
- };
1216
-
1217
- // Create a Timeline
1218
- var timeline = new vis.Timeline(container, data, options);
1219
- </script>
1220
- </body>
1221
- </html>
1222
- EOTEMPLATE
1213
+ elsif opt[:output] == 'timeline'
1214
+ template = <<~EOTEMPLATE
1215
+ <!doctype html>
1216
+ <html>
1217
+ <head>
1218
+ <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
1219
+ <script src="http://visjs.org/dist/vis.js"></script>
1220
+ </head>
1221
+ <body>
1222
+ <div id="mytimeline"></div>
1223
+ #{' '}
1224
+ <script type="text/javascript">
1225
+ // DOM element where the Timeline will be attached
1226
+ var container = document.getElementById('mytimeline');
1227
+ #{' '}
1228
+ // Create a DataSet with data (enables two way data binding)
1229
+ var data = new vis.DataSet(#{items_out.to_json});
1230
+ #{' '}
1231
+ // Configuration for the Timeline
1232
+ var options = {
1233
+ width: '100%',
1234
+ height: '800px',
1235
+ margin: {
1236
+ item: 20
1237
+ },
1238
+ stack: true,
1239
+ min: '#{min}',
1240
+ max: '#{max}'
1241
+ };
1242
+ #{' '}
1243
+ // Create a Timeline
1244
+ var timeline = new vis.Timeline(container, data, options);
1245
+ </script>
1246
+ </body>
1247
+ </html>
1248
+ EOTEMPLATE
1223
1249
  return template
1224
1250
  end
1225
- elsif opt[:output] == "html"
1251
+ elsif opt[:output] == 'html'
1226
1252
  page_title = section
1227
1253
  items_out = []
1228
- items.each {|i|
1254
+ items.each do |i|
1229
1255
  # if i.has_key?('note')
1230
1256
  # note = '<span class="note">' + i['note'].map{|n| n.strip }.join('<br>') + '</span>'
1231
1257
  # else
@@ -1233,83 +1259,81 @@ EOTEMPLATE
1233
1259
  # end
1234
1260
  if String.method_defined? :force_encoding
1235
1261
  title = i['title'].force_encoding('utf-8').link_urls
1236
- note = i['note'].map {|line| line.force_encoding('utf-8').strip.link_urls } if i['note']
1262
+ note = i['note'].map { |line| line.force_encoding('utf-8').strip.link_urls } if i['note']
1237
1263
  else
1238
1264
  title = i['title'].link_urls
1239
1265
  note = i['note'].map { |line| line.strip.link_urls } if i['note']
1240
1266
  end
1241
1267
 
1242
- if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1243
- interval = get_interval(i)
1244
- end
1268
+ interval = get_interval(i) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1245
1269
  interval ||= false
1246
1270
 
1247
1271
  items_out << {
1248
- :date => i['date'].strftime('%a %-I:%M%p'),
1249
- :title => title.gsub(/(@[^ \(]+(\(.*?\))?)/im,'<span class="tag">\1</span>').strip, #+ " #{note}"
1250
- :note => note,
1251
- :time => interval,
1252
- :section => i['section']
1272
+ date: i['date'].strftime('%a %-I:%M%p'),
1273
+ title: title.gsub(/(@[^ (]+(\(.*?\))?)/im, '<span class="tag">\1</span>').strip, #+ " #{note}"
1274
+ note: note,
1275
+ time: interval,
1276
+ section: i['section']
1253
1277
  }
1254
- }
1255
-
1256
- if @config['html_template']['haml'] && File.exists?(File.expand_path(@config['html_template']['haml']))
1257
- template = IO.read(File.expand_path(@config['html_template']['haml']))
1258
- else
1259
- template = haml_template
1260
1278
  end
1261
1279
 
1262
- if @config['html_template']['css'] && File.exists?(File.expand_path(@config['html_template']['css']))
1263
- style = IO.read(File.expand_path(@config['html_template']['css']))
1264
- else
1265
- style = css_template
1266
- end
1280
+ template = if @config['html_template']['haml'] && File.exist?(File.expand_path(@config['html_template']['haml']))
1281
+ IO.read(File.expand_path(@config['html_template']['haml']))
1282
+ else
1283
+ haml_template
1284
+ end
1285
+
1286
+ style = if @config['html_template']['css'] && File.exist?(File.expand_path(@config['html_template']['css']))
1287
+ IO.read(File.expand_path(@config['html_template']['css']))
1288
+ else
1289
+ css_template
1290
+ end
1267
1291
 
1268
- totals = opt[:totals] ? tag_times("html", opt[:sort_tags]) : ""
1292
+ totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
1269
1293
  engine = Haml::Engine.new(template)
1270
- puts engine.render(Object.new, { :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
1294
+ puts engine.render(Object.new,
1295
+ { :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
1271
1296
  else
1272
- items.each {|item|
1273
-
1297
+ items.each do |item|
1274
1298
  if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
1275
1299
  flag = colors[@config['marker_color']]
1276
1300
  reset = colors['default']
1277
1301
  else
1278
- flag = ""
1279
- reset = ""
1302
+ flag = ''
1303
+ reset = ''
1280
1304
  end
1281
1305
 
1282
1306
  if (item.has_key?('note') && !item['note'].empty?) && @config[:include_notes]
1283
- note_lines = item['note'].delete_if{|line| line =~ /^\s*$/ }.map{|line| "\t\t" + line.sub(/^\t*/,'').sub(/^-/, '—') + " " }
1307
+ note_lines = item['note'].delete_if do |line|
1308
+ line =~ /^\s*$/
1309
+ end.map { |line| "\t\t" + line.sub(/^\t*/, '').sub(/^-/, '—') + ' ' }
1284
1310
  if opt[:wrap_width] && opt[:wrap_width] > 0
1285
1311
  width = opt[:wrap_width]
1286
- note_lines.map! {|line|
1312
+ note_lines.map! do |line|
1287
1313
  line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
1288
- }
1314
+ end
1289
1315
  end
1290
1316
  note = "\n#{note_lines.join("\n").chomp}"
1291
1317
  else
1292
- note = ""
1318
+ note = ''
1293
1319
  end
1294
1320
  output = opt[:template].dup
1295
1321
 
1296
1322
  output.gsub!(/%[a-z]+/) do |m|
1297
- if colors.has_key?(m.sub(/^%/,''))
1298
- colors[m.sub(/^%/,'')]
1323
+ if colors.has_key?(m.sub(/^%/, ''))
1324
+ colors[m.sub(/^%/, '')]
1299
1325
  else
1300
1326
  m
1301
1327
  end
1302
1328
  end
1303
1329
 
1304
- output.sub!(/%date/,item['date'].strftime(opt[:format]))
1330
+ output.sub!(/%date/, item['date'].strftime(opt[:format]))
1305
1331
 
1306
- if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1307
- interval = get_interval(item)
1308
- end
1309
- interval ||= ""
1310
- output.sub!(/%interval/,interval)
1332
+ interval = get_interval(item) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1333
+ interval ||= ''
1334
+ output.sub!(/%interval/, interval)
1311
1335
 
1312
- output.sub!(/%shortdate/) {
1336
+ output.sub!(/%shortdate/) do
1313
1337
  if item['date'] > Date.today.to_time
1314
1338
  item['date'].strftime('%_I:%M%P')
1315
1339
  elsif item['date'] > (Date.today - 7).to_time
@@ -1319,45 +1343,45 @@ EOTEMPLATE
1319
1343
  else
1320
1344
  item['date'].strftime('%b %d %Y, %-I:%M%P')
1321
1345
  end
1322
- }
1346
+ end
1323
1347
 
1324
- output.sub!(/%title/) {|m|
1348
+ output.sub!(/%title/) do |_m|
1325
1349
  if opt[:wrap_width] && opt[:wrap_width] > 0
1326
- flag+item['title'].gsub(/(.{1,#{opt[:wrap_width]}})(\s+|\Z)/, "\\1\n\t ").chomp+reset
1350
+ flag + item['title'].gsub(/(.{1,#{opt[:wrap_width]}})(\s+|\Z)/, "\\1\n\t ").chomp + reset
1327
1351
  else
1328
- flag+item['title'].chomp+reset
1352
+ flag + item['title'].chomp + reset
1329
1353
  end
1330
- }
1354
+ end
1331
1355
 
1332
- output.sub!(/%section/,item['section']) if item['section']
1356
+ output.sub!(/%section/, item['section']) if item['section']
1333
1357
 
1334
1358
  if opt[:tags_color]
1335
1359
  escapes = output.scan(/(\e\[[\d;]+m)[^\e]+@/)
1336
- if escapes.length > 0
1337
- last_color = escapes[-1][0]
1338
- else
1339
- last_color = colors['default']
1340
- end
1341
- output.gsub!(/\s(@[^ \(]+)/," #{colors[opt[:tags_color]]}\\1#{last_color}")
1360
+ last_color = if escapes.length > 0
1361
+ escapes[-1][0]
1362
+ else
1363
+ colors['default']
1364
+ end
1365
+ output.gsub!(/\s(@[^ (]+)/, " #{colors[opt[:tags_color]]}\\1#{last_color}")
1342
1366
  end
1343
- output.sub!(/%note/,note)
1344
- output.sub!(/%odnote/,note.gsub(/^\t*/,""))
1345
- output.sub!(/%chompnote/,note.gsub(/\n+/,' ').gsub(/(^\s*|\s*$)/,'').gsub(/\s+/,' '))
1346
- output.gsub!(/%hr(_under)?/) do |m|
1347
- o = ""
1367
+ output.sub!(/%note/, note)
1368
+ output.sub!(/%odnote/, note.gsub(/^\t*/, ''))
1369
+ output.sub!(/%chompnote/, note.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' '))
1370
+ output.gsub!(/%hr(_under)?/) do |_m|
1371
+ o = ''
1348
1372
  `tput cols`.to_i.times do
1349
- o += $1.nil? ? "-" : "_"
1373
+ o += Regexp.last_match(1).nil? ? '-' : '_'
1350
1374
  end
1351
1375
  o
1352
1376
  end
1353
- output.gsub!(/%n/,"\n")
1354
- output.gsub!(/%t/,"\t")
1377
+ output.gsub!(/%n/, "\n")
1378
+ output.gsub!(/%t/, "\t")
1355
1379
 
1356
1380
  out += output + "\n"
1357
- }
1358
- out += tag_times("text", opt[:sort_tags]) if opt[:totals]
1381
+ end
1382
+ out += tag_times('text', opt[:sort_tags]) if opt[:totals]
1359
1383
  end
1360
- return out
1384
+ out
1361
1385
  end
1362
1386
 
1363
1387
  ##
@@ -1370,15 +1394,12 @@ EOTEMPLATE
1370
1394
  ## @param tags (Array) Tags to archive
1371
1395
  ## @param bool (String) Tag boolean combinator
1372
1396
  ##
1373
- def archive(section="Currently",count=5,destination=nil,tags=nil,bool=nil,export=nil)
1374
-
1397
+ def archive(section = 'Currently', count = 5, destination = nil, tags = nil, bool = nil, _export = nil)
1375
1398
  section = choose_section if section.nil? || section =~ /choose/i
1376
1399
  archive_all = section =~ /all/i # && !(tags.nil? || tags.empty?)
1377
1400
  section = guess_section(section) unless archive_all
1378
1401
 
1379
- if destination =~ /archive/i && !sections.include?("Archive")
1380
- add_section("Archive")
1381
- end
1402
+ add_section('Archive') if destination =~ /archive/i && !sections.include?('Archive')
1382
1403
 
1383
1404
  destination = guess_section(destination)
1384
1405
 
@@ -1386,16 +1407,16 @@ EOTEMPLATE
1386
1407
  if archive_all
1387
1408
  to_archive = sections.dup
1388
1409
  to_archive.delete(destination)
1389
- to_archive.each {|source,v|
1390
- do_archive(source, destination, { :count => count, :tags => tags, :bool => bool, :label => true })
1391
- }
1410
+ to_archive.each do |source, _v|
1411
+ do_archive(source, destination, { count: count, tags: tags, bool: bool, label: true })
1412
+ end
1392
1413
  else
1393
- do_archive(section, destination, { :count => count, :tags => tags, :bool => bool, :label => true })
1414
+ do_archive(section, destination, { count: count, tags: tags, bool: bool, label: true })
1394
1415
  end
1395
1416
 
1396
1417
  write(doing_file)
1397
1418
  else
1398
- raise "Either source or destination does not exist"
1419
+ raise 'Either source or destination does not exist'
1399
1420
  end
1400
1421
  end
1401
1422
 
@@ -1406,63 +1427,66 @@ EOTEMPLATE
1406
1427
  ## @param destination (String) The destination section
1407
1428
  ## @param opt (Hash) Additional Options
1408
1429
  ##
1409
- def do_archive(section, destination, opt={})
1430
+ def do_archive(section, destination, opt = {})
1410
1431
  count = opt[:count] || 5
1411
1432
  tags = opt[:tags] || []
1412
- bool = opt[:bool] || "AND"
1433
+ bool = opt[:bool] || 'AND'
1413
1434
  label = opt[:label] || false
1414
1435
 
1415
1436
  items = @content[section]['items']
1416
1437
  moved_items = []
1417
1438
 
1418
1439
  if tags && !tags.empty?
1419
- items.delete_if {|item|
1440
+ items.delete_if do |item|
1420
1441
  if bool =~ /(AND|ALL)/
1421
1442
  score = 0
1422
- tags.each {|tag|
1443
+ tags.each do |tag|
1423
1444
  score += 1 if item['title'] =~ /@#{tag}/i
1424
- }
1445
+ end
1425
1446
  res = score < tags.length
1426
1447
  moved_items.push(item) if res
1427
1448
  res
1428
1449
  elsif bool =~ /NONE/
1429
1450
  del = false
1430
- tags.each {|tag|
1451
+ tags.each do |tag|
1431
1452
  del = true if item['title'] =~ /@#{tag}/i
1432
- }
1453
+ end
1433
1454
  moved_items.push(item) if del
1434
1455
  del
1435
1456
  elsif bool =~ /(OR|ANY)/
1436
1457
  del = true
1437
- tags.each {|tag|
1458
+ tags.each do |tag|
1438
1459
  del = false if item['title'] =~ /@#{tag}/i
1439
- }
1460
+ end
1440
1461
  moved_items.push(item) if del
1441
1462
  del
1442
1463
  end
1443
- }
1444
- moved_items.each {|item|
1445
- if label
1446
- item['title'] = item['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{section})") unless section == "Currently"
1464
+ end
1465
+ moved_items.each do |item|
1466
+ if label && !(section == 'Currently')
1467
+ item['title'] =
1468
+ item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
1447
1469
  end
1448
- }
1470
+ end
1449
1471
  @content[section]['items'] = moved_items
1450
1472
  @content[destination]['items'] += items
1451
1473
  @results.push("Archived #{items.length} items from #{section} to #{destination}")
1452
1474
  else
1453
1475
 
1454
1476
  return if items.length < count
1455
- if count == 0
1456
- @content[section]['items'] = []
1457
- else
1458
- @content[section]['items'] = items[0..count-1]
1459
- end
1460
1477
 
1461
- items.each{|item|
1462
- if label
1463
- item['title'] = item['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{section})") unless section == "Currently"
1478
+ @content[section]['items'] = if count == 0
1479
+ []
1480
+ else
1481
+ items[0..count - 1]
1482
+ end
1483
+
1484
+ items.each do |item|
1485
+ if label && !(section == 'Currently')
1486
+ item['title'] =
1487
+ item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
1464
1488
  end
1465
- }
1489
+ end
1466
1490
 
1467
1491
  @content[destination]['items'] += items[count..-1]
1468
1492
  @results.push("Archived #{items.length - count} items from #{section} to #{destination}")
@@ -1518,7 +1542,6 @@ EOTEMPLATE
1518
1542
  color
1519
1543
  end
1520
1544
 
1521
-
1522
1545
  ##
1523
1546
  ## @brief Show all entries from the current day
1524
1547
  ##
@@ -1526,12 +1549,13 @@ EOTEMPLATE
1526
1549
  ## @param output (String) output format
1527
1550
  ## @param opt (Hash) Options
1528
1551
  ##
1529
- def today(times=true,output=nil,opt={})
1552
+ def today(times = true, output = nil, opt = {})
1530
1553
  opt[:totals] ||= false
1531
1554
  opt[:sort_tags] ||= false
1532
1555
 
1533
1556
  cfg = @config['templates']['today']
1534
- list_section({:section => opt[:section], :wrap_width => cfg['wrap_width'], :count => 0, :format => cfg['date_format'], :template => cfg['template'], :order => "asc", :today => true, :times => times, :output => output, :totals => opt[:totals], :sort_tags => opt[:sort_tags]})
1557
+ list_section({ section: opt[:section], wrap_width: cfg['wrap_width'], count: 0,
1558
+ format: cfg['date_format'], template: cfg['template'], order: 'asc', today: true, times: times, output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
1535
1559
  end
1536
1560
 
1537
1561
  ##
@@ -1543,16 +1567,15 @@ EOTEMPLATE
1543
1567
  ## @param output (String) Output format
1544
1568
  ## @param opt (Hash) Additional Options
1545
1569
  ##
1546
- def list_date(dates,section,times=nil,output=nil,opt={})
1570
+ def list_date(dates, section, times = nil, output = nil, opt = {})
1547
1571
  opt[:totals] ||= false
1548
1572
  opt[:sort_tags] ||= false
1549
1573
  section = guess_section(section)
1550
1574
  # :date_filter expects an array with start and end date
1551
- if dates.class == String
1552
- dates = [dates, dates]
1553
- end
1575
+ dates = [dates, dates] if dates.instance_of?(String)
1554
1576
 
1555
- list_section({:section => section, :count => 0, :order => "asc", :date_filter => dates, :times => times, :output => output, :totals => opt[:totals], :sort_tags => opt[:sort_tags] })
1577
+ list_section({ section: section, count: 0, order: 'asc', date_filter: dates, times: times,
1578
+ output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
1556
1579
  end
1557
1580
 
1558
1581
  ##
@@ -1563,11 +1586,12 @@ EOTEMPLATE
1563
1586
  ## @param output (String) Output format
1564
1587
  ## @param opt (Hash) Additional Options
1565
1588
  ##
1566
- def yesterday(section,times=nil,output=nil,opt={})
1589
+ def yesterday(section, times = nil, output = nil, opt = {})
1567
1590
  opt[:totals] ||= false
1568
1591
  opt[:sort_tags] ||= false
1569
1592
  section = guess_section(section)
1570
- list_section({:section => section, :count => 0, :order => "asc", :yesterday => true, :times => times, :output => output, :totals => opt[:totals], :sort_tags => opt[:sort_tags] })
1593
+ list_section({ section: section, count: 0, order: 'asc', yesterday: true, times: times,
1594
+ output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
1571
1595
  end
1572
1596
 
1573
1597
  ##
@@ -1577,7 +1601,7 @@ EOTEMPLATE
1577
1601
  ## @param section (String) The section to show from, default Currently
1578
1602
  ## @param opt (Hash) Additional Options
1579
1603
  ##
1580
- def recent(count=10,section=nil,opt={})
1604
+ def recent(count = 10, section = nil, opt = {})
1581
1605
  times = opt[:t] || true
1582
1606
  opt[:totals] ||= false
1583
1607
  opt[:sort_tags] ||= false
@@ -1585,7 +1609,8 @@ EOTEMPLATE
1585
1609
  cfg = @config['templates']['recent']
1586
1610
  section ||= @current_section
1587
1611
  section = guess_section(section)
1588
- list_section({:section => section, :wrap_width => cfg['wrap_width'], :count => count, :format => cfg['date_format'], :template => cfg['template'], :order => "asc", :times => times, :totals => opt[:totals], :sort_tags => opt[:sort_tags] })
1612
+ list_section({ section: section, wrap_width: cfg['wrap_width'], count: count,
1613
+ format: cfg['date_format'], template: cfg['template'], order: 'asc', times: times, totals: opt[:totals], sort_tags: opt[:sort_tags] })
1589
1614
  end
1590
1615
 
1591
1616
  ##
@@ -1594,11 +1619,12 @@ EOTEMPLATE
1594
1619
  ## @param times (Bool) Show times
1595
1620
  ## @param section (String) Section to pull from, default Currently
1596
1621
  ##
1597
- def last(times=true,section=nil)
1622
+ def last(times = true, section = nil)
1598
1623
  section ||= @current_section
1599
1624
  section = guess_section(section)
1600
1625
  cfg = @config['templates']['last']
1601
- list_section({:section => section, :wrap_width => cfg['wrap_width'], :count => 1, :format => cfg['date_format'], :template => cfg['template'], :times => times})
1626
+ list_section({ section: section, wrap_width: cfg['wrap_width'], count: 1, format: cfg['date_format'],
1627
+ template: cfg['template'], times: times })
1602
1628
  end
1603
1629
 
1604
1630
  ##
@@ -1606,22 +1632,22 @@ EOTEMPLATE
1606
1632
  ##
1607
1633
  ## @param format (String) return format (html, json, or text)
1608
1634
  ##
1609
- def tag_times(format="text", sort_by_name = false)
1610
- return "" if @timers.empty?
1635
+ def tag_times(format = 'text', sort_by_name = false)
1636
+ return '' if @timers.empty?
1611
1637
 
1612
- max = @timers.keys.sort_by {|k| k.length }.reverse[0].length + 1
1638
+ max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
1613
1639
 
1614
- total = @timers.delete("All")
1640
+ total = @timers.delete('All')
1615
1641
 
1616
- tags_data = @timers.delete_if { |k,v| v == 0}
1617
- if sort_by_name
1618
- sorted_tags_data = tags_data.sort_by{|k,v| k }.reverse
1619
- else
1620
- sorted_tags_data = tags_data.sort_by{|k,v| v }
1621
- end
1642
+ tags_data = @timers.delete_if { |_k, v| v == 0 }
1643
+ sorted_tags_data = if sort_by_name
1644
+ tags_data.sort_by { |k, _v| k }.reverse
1645
+ else
1646
+ tags_data.sort_by { |_k, v| v }
1647
+ end
1622
1648
 
1623
- if format == "html"
1624
- output =<<EOS
1649
+ if format == 'html'
1650
+ output = <<EOS
1625
1651
  <table>
1626
1652
  <caption id="tagtotals">Tag Totals</caption>
1627
1653
  <colgroup>
@@ -1636,10 +1662,12 @@ EOTEMPLATE
1636
1662
  </thead>
1637
1663
  <tbody>
1638
1664
  EOS
1639
- sorted_tags_data.reverse.each {|k,v|
1640
- output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{"%02d:%02d:%02d" % fmt_time(v)}</td></tr>\n" if v > 0
1641
- }
1642
- tail =<<EOS
1665
+ sorted_tags_data.reverse.each do |k, v|
1666
+ if v > 0
1667
+ output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{'%02d:%02d:%02d' % fmt_time(v)}</td></tr>\n"
1668
+ end
1669
+ end
1670
+ tail = <<EOS
1643
1671
  <tr>
1644
1672
  <td style="text-align:left;" colspan="2"></td>
1645
1673
  </tr>
@@ -1647,34 +1675,34 @@ EOS
1647
1675
  <tfoot>
1648
1676
  <tr>
1649
1677
  <td style="text-align:left;"><strong>Total</strong></td>
1650
- <td style="text-align:left;">#{"%02d:%02d:%02d" % fmt_time(total)}</td>
1678
+ <td style="text-align:left;">#{'%02d:%02d:%02d' % fmt_time(total)}</td>
1651
1679
  </tr>
1652
1680
  </tfoot>
1653
1681
  </table>
1654
1682
  EOS
1655
1683
  output + tail
1656
- elsif format == "json"
1684
+ elsif format == 'json'
1657
1685
  output = []
1658
- sorted_tags_data.reverse.each {|k,v|
1686
+ sorted_tags_data.reverse.each do |k, v|
1659
1687
  output << {
1660
1688
  'tag' => k,
1661
1689
  'seconds' => v,
1662
- 'formatted' => "%02d:%02d:%02d" % fmt_time(v)
1690
+ 'formatted' => '%02d:%02d:%02d' % fmt_time(v)
1663
1691
  }
1664
- }
1692
+ end
1665
1693
  output
1666
1694
  else
1667
1695
  output = []
1668
- sorted_tags_data.reverse.each {|k,v|
1669
- spacer = ""
1696
+ sorted_tags_data.reverse.each do |k, v|
1697
+ spacer = ''
1670
1698
  (max - k.length).times do
1671
- spacer += " "
1699
+ spacer += ' '
1672
1700
  end
1673
- output.push("#{k}:#{spacer}#{"%02d:%02d:%02d" % fmt_time(v)}")
1674
- }
1701
+ output.push("#{k}:#{spacer}#{'%02d:%02d:%02d' % fmt_time(v)}")
1702
+ end
1675
1703
 
1676
- output = output.empty? ? "" : "\n--- Tag Totals ---\n" + output.join("\n")
1677
- output += "\n\nTotal tracked: #{"%02d:%02d:%02d" % fmt_time(total)}\n"
1704
+ output = output.empty? ? '' : "\n--- Tag Totals ---\n" + output.join("\n")
1705
+ output += "\n\nTotal tracked: #{'%02d:%02d:%02d' % fmt_time(total)}\n"
1678
1706
  output
1679
1707
  end
1680
1708
  end
@@ -1688,53 +1716,54 @@ EOS
1688
1716
  def autotag(text)
1689
1717
  return unless text
1690
1718
  return text unless @auto_tag
1719
+
1691
1720
  current_tags = text.scan(/@\w+/)
1692
1721
  whitelisted = []
1693
- @config['autotag']['whitelist'].each {|tag|
1722
+ @config['autotag']['whitelist'].each do |tag|
1723
+ next if text =~ /@#{tag}\b/i
1724
+
1694
1725
  text.sub!(/(?<!@)(#{tag.strip})\b/i) do |m|
1695
1726
  m.downcase! if tag =~ /[a-z]/
1696
1727
  whitelisted.push("@#{m}")
1697
1728
  "@#{m}"
1698
- end unless text =~ /@#{tag}\b/i
1699
- }
1729
+ end
1730
+ end
1700
1731
  tail_tags = []
1701
- @config['autotag']['synonyms'].each {|tag, v|
1702
- v.each {|word|
1703
- if text =~ /\b#{word}\b/i
1704
- unless current_tags.include?("@#{tag}") || whitelisted.include?("@#{tag}")
1705
- tail_tags.push(tag)
1706
- end
1707
- end
1708
- }
1709
- }
1732
+ @config['autotag']['synonyms'].each do |tag, v|
1733
+ v.each do |word|
1734
+ next unless text =~ /\b#{word}\b/i
1735
+
1736
+ tail_tags.push(tag) unless current_tags.include?("@#{tag}") || whitelisted.include?("@#{tag}")
1737
+ end
1738
+ end
1710
1739
  if @config['autotag'].key? 'transform'
1711
- @config['autotag']['transform'].each {|tag|
1712
- if tag =~ /\S+:\S+/
1713
- rx, r = tag.split(/:/)
1714
- r.gsub!(/\$/,'\\')
1715
- rx.sub!(/^@/,'')
1716
- regex = Regexp.new('@' + rx + '\b')
1717
-
1718
- matches = text.scan(regex)
1719
- matches.each {|m|
1720
- new_tag = r
1721
- if m.kind_of?(Array)
1722
- index = 1
1723
- m.each {|v|
1724
- new_tag = new_tag.gsub('\\' + index.to_s, v)
1725
- index = index + 1
1726
- }
1740
+ @config['autotag']['transform'].each do |tag|
1741
+ next unless tag =~ /\S+:\S+/
1742
+
1743
+ rx, r = tag.split(/:/)
1744
+ r.gsub!(/\$/, '\\')
1745
+ rx.sub!(/^@/, '')
1746
+ regex = Regexp.new('@' + rx + '\b')
1747
+
1748
+ matches = text.scan(regex)
1749
+ next unless matches
1750
+
1751
+ matches.each do |m|
1752
+ new_tag = r
1753
+ if m.is_a?(Array)
1754
+ index = 1
1755
+ m.each do |v|
1756
+ new_tag = new_tag.gsub('\\' + index.to_s, v)
1757
+ index += 1
1727
1758
  end
1728
- tail_tags.push(new_tag)
1729
- } if matches
1759
+ end
1760
+ tail_tags.push(new_tag)
1730
1761
  end
1731
- }
1732
- end
1733
- if whitelisted.length > 0
1734
- @results.push("Whitelisted tags: #{whitelisted.join(', ')}")
1762
+ end
1735
1763
  end
1764
+ @results.push("Whitelisted tags: #{whitelisted.join(', ')}") if whitelisted.length > 0
1736
1765
  if tail_tags.length > 0
1737
- tags = tail_tags.uniq.map {|t| '@'+t }.join(' ')
1766
+ tags = tail_tags.uniq.map { |t| '@' + t }.join(' ')
1738
1767
  @results.push("Synonym tags: #{tags}")
1739
1768
  text + ' ' + tags
1740
1769
  else
@@ -1750,43 +1779,43 @@ EOS
1750
1779
  ## @param item (Hash) The entry
1751
1780
  ## @param formatted (Bool) Return human readable time (default seconds)
1752
1781
  ##
1753
- def get_interval(item, formatted=true)
1782
+ def get_interval(item, formatted = true)
1754
1783
  done = nil
1755
1784
  start = nil
1756
1785
 
1757
1786
  if @interval_cache.keys.include? item['title']
1758
1787
  seconds = @interval_cache[item['title']]
1759
- return seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
1788
+ return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
1760
1789
  end
1761
1790
 
1762
1791
  if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1763
- done = Time.parse($1)
1792
+ done = Time.parse(Regexp.last_match(1))
1764
1793
  else
1765
1794
  return nil
1766
1795
  end
1767
1796
 
1768
- if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1769
- start = Time.parse($1)
1770
- else
1771
- start = item['date']
1772
- end
1797
+ start = if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1798
+ Time.parse(Regexp.last_match(1))
1799
+ else
1800
+ item['date']
1801
+ end
1773
1802
 
1774
1803
  seconds = (done - start).to_i
1775
1804
 
1776
- item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each {|m|
1777
- k = m[0] == "done" ? "All" : m[0].downcase
1805
+ item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
1806
+ k = m[0] == 'done' ? 'All' : m[0].downcase
1778
1807
  if @timers.has_key?(k)
1779
1808
  @timers[k] += seconds
1780
1809
  else
1781
1810
  @timers[k] = seconds
1782
1811
  end
1783
- }
1812
+ end
1784
1813
 
1785
1814
  @interval_cache[item['title']] = seconds
1786
1815
 
1787
1816
  return seconds unless formatted
1788
1817
 
1789
- seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
1818
+ seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
1790
1819
  end
1791
1820
 
1792
1821
  ##
@@ -1795,19 +1824,19 @@ EOS
1795
1824
  ## @param seconds The seconds
1796
1825
  ##
1797
1826
  def fmt_time(seconds)
1798
- if seconds.nil?
1799
- return [0, 0, 0]
1800
- end
1827
+ return [0, 0, 0] if seconds.nil?
1828
+
1801
1829
  if seconds =~ /(\d+):(\d+):(\d+)/
1802
- h, m, s = [$1, $2, $3]
1830
+ h = Regexp.last_match(1)
1831
+ m = Regexp.last_match(2)
1832
+ s = Regexp.last_match(3)
1803
1833
  seconds = (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
1804
1834
  end
1805
- minutes = (seconds / 60).to_i
1835
+ minutes = (seconds / 60).to_i
1806
1836
  hours = (minutes / 60).to_i
1807
1837
  days = (hours / 24).to_i
1808
1838
  hours = (hours % 24).to_i
1809
1839
  minutes = (minutes % 60).to_i
1810
1840
  [days, hours, minutes]
1811
1841
  end
1812
-
1813
1842
  end