doing 1.0.44 → 1.0.49

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.
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.44'
2
+ VERSION = '1.0.49'
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,23 @@ 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']
642
- end
643
-
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")
650
619
  end
651
- return "#{last_item['title']}\n# EDIT BELOW THIS LINE ------------\n#{note}"
652
- else
653
- raise "Section #{section} not found"
620
+ section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
654
621
  end
622
+
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}"
655
630
  end
656
631
 
657
632
  ##
@@ -665,12 +640,13 @@ class WWID
665
640
 
666
641
  last = last_entry(opt)
667
642
  if last.nil?
668
- @results.push(%Q{No previous entry found})
643
+ @results.push(%(No previous entry found))
669
644
  return
670
645
  end
671
646
  # Remove @done tag
672
- title = last['title'].sub!(/\s*@done(\(.*?\))?/, '').chomp
673
- add_item(title, last['section'], {:note => opt[:note], :back => opt[:date], :timed => true})
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 })
674
650
  write(@doing_file)
675
651
  end
676
652
 
@@ -679,20 +655,20 @@ class WWID
679
655
  ##
680
656
  ## @param opt (Hash) Additional Options
681
657
  ##
682
- def last_entry(opt={})
658
+ def last_entry(opt = {})
683
659
  opt[:section] ||= @current_section
684
660
 
685
661
  sec_arr = []
686
662
 
687
663
  if opt[:section].nil?
688
664
  sec_arr = [@current_section]
689
- elsif opt[:section].class == String
665
+ elsif opt[:section].instance_of?(String)
690
666
  if opt[:section] =~ /^all$/i
691
- combined = {'items' => []}
692
- @content.each {|k,v|
667
+ combined = { 'items' => [] }
668
+ @content.each do |_k, v|
693
669
  combined['items'] += v['items']
694
- }
695
- items = combined['items'].dup.sort_by{|item| item['date'] }.reverse
670
+ end
671
+ items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
696
672
  sec_arr.push(items[0]['section'])
697
673
 
698
674
  sec_arr = sections
@@ -701,13 +677,12 @@ class WWID
701
677
  end
702
678
  end
703
679
 
704
-
705
680
  all_items = []
706
681
  sec_arr.each do |section|
707
- all_items.concat(@content[section]['items'].dup) if @content.has_key?(section)
682
+ all_items.concat(@content[section]['items'].dup) if @content.key?(section)
708
683
  end
709
684
 
710
- all_items.sort_by { |item| item['date'] }.last
685
+ all_items.max_by { |item| item['date'] }
711
686
  end
712
687
 
713
688
  ##
@@ -715,33 +690,32 @@ class WWID
715
690
  ##
716
691
  ## @param opt (Hash) Additional Options
717
692
  ##
718
- def tag_last(opt={})
693
+ def tag_last(opt = {})
719
694
  opt[:section] ||= @current_section
720
695
  opt[:count] ||= 1
721
696
  opt[:archive] ||= false
722
- opt[:tags] ||= ["done"]
697
+ opt[:tags] ||= ['done']
723
698
  opt[:sequential] ||= false
724
699
  opt[:date] ||= false
725
700
  opt[:remove] ||= false
726
701
  opt[:autotag] ||= false
727
702
  opt[:back] ||= false
728
703
 
729
-
730
704
  sec_arr = []
731
705
 
732
706
  if opt[:section].nil?
733
707
  sec_arr = [@current_section]
734
- elsif opt[:section].class == String
708
+ elsif opt[:section].instance_of?(String)
735
709
  if opt[:section] =~ /^all$/i
736
710
  if opt[:count] == 1
737
- combined = {'items' => []}
738
- @content.each {|k,v|
711
+ combined = { 'items' => [] }
712
+ @content.each do |_k, v|
739
713
  combined['items'] += v['items']
740
- }
741
- items = combined['items'].dup.sort_by{|item| item['date'] }.reverse
714
+ end
715
+ items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
742
716
  sec_arr.push(items[0]['section'])
743
717
  elsif opt[:count] > 1
744
- 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'
745
719
  else
746
720
  sec_arr = sections
747
721
  end
@@ -750,21 +724,27 @@ class WWID
750
724
  end
751
725
  end
752
726
 
727
+ sec_arr.each do |section|
728
+ if @content.key?(section)
753
729
 
754
-
755
- sec_arr.each {|section|
756
- if @content.has_key?(section)
757
-
758
- items = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse
730
+ items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
759
731
 
760
732
  index = 0
761
733
  done_date = Time.now
762
734
  next_start = Time.now
763
- count = opt[:count] == 0 ? items.length : opt[:count]
764
- items.map! {|item|
735
+ count = (opt[:count]).zero? ? items.length : opt[:count]
736
+ items.map! do |item|
765
737
  break if index == count
766
738
 
767
- 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
768
748
  if opt[:sequential]
769
749
  done_date = next_start - 1
770
750
  next_start = item['date']
@@ -775,62 +755,50 @@ class WWID
775
755
  end
776
756
 
777
757
  title = item['title']
778
- opt[:tags].each {|tag|
758
+ opt[:tags].each do |tag|
779
759
  tag.strip!
780
- if opt[:remove]
781
- if title =~ /@#{tag}\b/
782
- title.gsub!(/(^| )@#{tag}(\([^\)]*\))?/,'')
783
- @results.push(%Q{Removed @#{tag}: "#{title}" in #{section}})
784
- end
785
- else
786
- unless title =~ /@#{tag}/
787
- title.chomp!
788
- if opt[:date]
789
- title += " @#{tag}(#{done_date.strftime('%F %R')})"
790
- else
791
- title += " @#{tag}"
792
- end
793
- @results.push(%Q{Added @#{tag}: "#{title}" in #{section}})
794
- 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}))
795
771
  end
796
- }
797
- item['title'] = title
798
- else
799
- new_title = autotag(item['title']) if @auto_tag
800
- unless new_title == item['title']
801
- @results.push("Tags updated: #{new_title}")
802
- item['title'] = new_title
803
- else
804
- @results.push(%Q{Autotag: No changes})
805
772
  end
773
+ item['title'] = title
806
774
  end
807
775
 
808
776
  index += 1
809
777
 
810
778
  item
811
- }
779
+ end
812
780
 
813
781
  @content[section]['items'] = items
814
782
 
815
- if opt[:archive] && section != "Archive" && opt[:count] > 0
783
+ if opt[:archive] && section != 'Archive' && (opt[:count]).positive?
816
784
  # concat [count] items from [section] and archive section
817
- archived = @content[section]['items'][0..opt[:count]-1].map {|i|
818
- i['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{i['section']})")
819
- }.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'])
820
788
  # chop [count] items off of [section] items
821
789
  @content[opt[:section]]['items'] = @content[opt[:section]]['items'][opt[:count]..-1]
822
790
  # overwrite archive section with concatenated array
823
791
  @content['Archive']['items'] = archived
824
792
  # log it
825
- result = opt[:count] == 1 ? "1 entry" : "#{opt[:count]} entries"
793
+ result = opt[:count] == 1 ? '1 entry' : "#{opt[:count]} entries"
826
794
  @results.push("Archived #{result} from #{section}")
827
- elsif opt[:archive] && opt[:count] == 0
828
- @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?
829
797
  end
830
798
  else
831
799
  raise "Section not found: #{section}"
832
800
  end
833
- }
801
+ end
834
802
 
835
803
  write(@doing_file)
836
804
  end
@@ -842,49 +810,47 @@ class WWID
842
810
  ## @param note (String) The note to add
843
811
  ## @param replace (Bool) Should replace existing note
844
812
  ##
845
- def note_last(section, note, replace=false)
813
+ def note_last(section, note, replace: false)
846
814
  section = guess_section(section)
847
815
 
848
816
  if section =~ /^all$/i
849
- combined = {'items' => []}
850
- @content.each {|k,v|
817
+ combined = { 'items' => [] }
818
+ @content.each do |_k, v|
851
819
  combined['items'] += v['items']
852
- }
853
- 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']
854
822
  end
855
823
 
856
- if @content.has_key?(section)
857
- # sort_section(opt[:section])
858
- items = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse
859
-
860
- current_note = items[0]['note']
861
- current_note = [] if current_note.nil?
862
- title = items[0]['title']
863
- if replace
864
- items[0]['note'] = note
865
- if note.empty? && !current_note.empty?
866
- @results.push(%Q{Removed note from "#{title}"})
867
- elsif current_note.length > 0 && note.length > 0
868
- @results.push(%Q{Replaced note from "#{title}"})
869
- elsif note.length > 0
870
- @results.push(%Q{Added note to "#{title}"})
871
- else
872
- @results.push(%Q{Entry "#{title}" has no note})
873
- end
874
- elsif current_note.class == Array
875
- items[0]['note'] = current_note.concat(note)
876
- @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}"))
877
840
  else
878
- items[0]['note'] = note
879
- @results.push(%Q{Added note to "#{title}"}) if note.length > 0
841
+ @results.push(%(Entry "#{title}" has no note))
880
842
  end
881
-
882
- @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?
883
846
  else
884
- raise "Section #{section} not found"
847
+ items[0]['note'] = note
848
+ @results.push(%(Added note to "#{title}")) unless note.empty?
885
849
  end
886
- end
887
850
 
851
+ @content[section]['items'] = items
852
+
853
+ end
888
854
 
889
855
  #
890
856
  # @brief Accepts one tag and the raw text of a new item if the passed tag
@@ -896,7 +862,7 @@ class WWID
896
862
  # @param tag (String) Tag to replace
897
863
  # @param opt (Hash) Additional Options
898
864
  #
899
- def stop_start(tag,opt={})
865
+ def stop_start(tag, opt = {})
900
866
  opt[:section] ||= @current_section
901
867
  opt[:archive] ||= false
902
868
  opt[:back] ||= Time.now
@@ -905,36 +871,36 @@ class WWID
905
871
 
906
872
  opt[:section] = guess_section(opt[:section])
907
873
 
908
- tag.sub!(/^@/,'')
874
+ tag.sub!(/^@/, '')
909
875
 
910
876
  found_items = 0
911
- @content[opt[:section]]['items'].each_with_index {|item, i|
912
- if item['title'] =~ /@#{tag}/
913
- title = item['title'].gsub(/(^| )@(#{tag}|done)(\([^\)]*\))?/,'')
914
- title += " @done(#{opt[:back].strftime('%F %R')})"
915
-
916
- @content[opt[:section]]['items'][i]['title'] = title
917
- found_items += 1
918
-
919
- if opt[:archive] && opt[:section] != "Archive"
920
- @results.push(%Q{Completed and archived "#{@content[opt[:section]]['items'][i]['title']}"})
921
- archive_item = @content[opt[:section]]['items'][i]
922
- archive_item['title'] = i['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{i['section']})")
923
- @content['Archive']['items'].push(archive_item)
924
- @content[opt[:section]]['items'].delete_at(i)
925
- else
926
- @results.push(%Q{Completed "#{@content[opt[:section]]['items'][i]['title']}"})
927
- 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']}"))
928
894
  end
929
- }
895
+ end
930
896
 
931
897
  @results.push("No active @#{tag} tasks found.") if found_items == 0
932
898
 
933
899
  if opt[:new_item]
934
900
  title, note = format_input(opt[:new_item])
935
- note.push(opt[:note].gsub(/ *$/,'')) if opt[:note]
901
+ note.push(opt[:note].gsub(/ *$/, '')) if opt[:note]
936
902
  title += " @#{tag}"
937
- 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] })
938
904
  end
939
905
 
940
906
  write(@doing_file)
@@ -945,28 +911,34 @@ class WWID
945
911
  ##
946
912
  ## @param file (String) The filepath to write to
947
913
  ##
948
- def write(file=nil)
949
- unless @other_content_top
950
- output = ""
951
- else
952
- 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 })
953
920
  end
954
- @content.each {|title, section|
955
- output += section['original'] + "\n"
956
- output += list_section({:section => title, :template => "\t- %date | %title%note", :highlight => false})
957
- }
958
921
  output += @other_content_bottom.join("\n") unless @other_content_bottom.nil?
959
922
  if file.nil?
960
923
  $stdout.puts output
961
924
  else
962
- if File.exists?(File.expand_path(file))
925
+ file = File.expand_path(file)
926
+ if File.exist?(file)
963
927
  # Create a backup copy for the undo command
964
- FileUtils.cp(file,file+"~")
928
+ FileUtils.cp(file, "#{file}~")
965
929
 
966
- File.open(File.expand_path(file),'w+') do |f|
930
+ File.open(file, 'w+') do |f|
967
931
  f.puts output
968
932
  end
969
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
970
942
  end
971
943
  end
972
944
 
@@ -976,27 +948,27 @@ class WWID
976
948
  ## @param file (String) The filepath to restore
977
949
  ##
978
950
  def restore_backup(file)
979
- if File.exists?(file+"~")
980
- puts file+"~"
981
- FileUtils.cp(file+"~",file)
951
+ if File.exist?(file + '~')
952
+ puts file + '~'
953
+ FileUtils.cp(file + '~', file)
982
954
  @results.push("Restored #{file}")
983
955
  end
984
956
  end
985
957
 
986
-
987
958
  ##
988
959
  ## @brief Generate a menu of sections and allow user selection
989
960
  ##
990
961
  ## @return (String) The selected section name
991
962
  ##
992
963
  def choose_section
993
- sections.each_with_index {|section, i|
994
- puts "% 3d: %s" % [i+1, section]
995
- }
964
+ sections.each_with_index do |section, i|
965
+ puts format('% 3d: %s', i + 1, section)
966
+ end
996
967
  print "#{colors['green']}> #{colors['default']}"
997
968
  num = STDIN.gets
998
969
  return false if num =~ /^[a-z ]*$/i
999
- return sections[num.to_i - 1]
970
+
971
+ sections[num.to_i - 1]
1000
972
  end
1001
973
 
1002
974
  ##
@@ -1014,13 +986,14 @@ class WWID
1014
986
  ## @return (String) The selected view name
1015
987
  ##
1016
988
  def choose_view
1017
- views.each_with_index {|view, i|
1018
- puts "% 3d: %s" % [i+1, view]
1019
- }
1020
- print "> "
989
+ views.each_with_index do |view, i|
990
+ puts format('% 3d: %s', i + 1, view)
991
+ end
992
+ print '> '
1021
993
  num = STDIN.gets
1022
994
  return false if num =~ /^[a-z ]*$/i
1023
- return views[num.to_i - 1]
995
+
996
+ views[num.to_i - 1]
1024
997
  end
1025
998
 
1026
999
  ##
@@ -1029,9 +1002,8 @@ class WWID
1029
1002
  ## @param title (String) The title of the view to retrieve
1030
1003
  ##
1031
1004
  def get_view(title)
1032
- if @config['views'].has_key?(title)
1033
- return @config['views'][title]
1034
- end
1005
+ return @config['views'][title] if @config['views'].has_key?(title)
1006
+
1035
1007
  false
1036
1008
  end
1037
1009
 
@@ -1041,14 +1013,14 @@ class WWID
1041
1013
  ##
1042
1014
  ## @param opt (Hash) Additional Options
1043
1015
  ##
1044
- def list_section(opt={})
1016
+ def list_section(opt = {})
1045
1017
  opt[:count] ||= 0
1046
1018
  count = opt[:count] - 1
1047
1019
  opt[:section] ||= nil
1048
1020
  opt[:format] ||= @default_date_format
1049
1021
  opt[:template] ||= @default_template
1050
- opt[:age] ||= "newest"
1051
- opt[:order] ||= "desc"
1022
+ opt[:age] ||= 'newest'
1023
+ opt[:order] ||= 'desc'
1052
1024
  opt[:today] ||= false
1053
1025
  opt[:tag_filter] ||= false
1054
1026
  opt[:tags_color] ||= false
@@ -1060,17 +1032,23 @@ class WWID
1060
1032
  opt[:date_filter] ||= []
1061
1033
 
1062
1034
  # opt[:highlight] ||= true
1063
- section = ""
1035
+ section = ''
1064
1036
  if opt[:section].nil?
1065
1037
  section = choose_section
1066
1038
  opt[:section] = @content[section]
1067
- elsif opt[:section].class == String
1039
+ elsif opt[:section].instance_of?(String)
1068
1040
  if opt[:section] =~ /^all$/i
1069
- combined = {'items' => []}
1070
- @content.each {|k,v|
1041
+ combined = { 'items' => [] }
1042
+ @content.each do |_k, v|
1071
1043
  combined['items'] += v['items']
1072
- }
1073
- 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
1074
1052
  opt[:section] = combined
1075
1053
  else
1076
1054
  section = guess_section(opt[:section])
@@ -1079,209 +1057,201 @@ class WWID
1079
1057
  end
1080
1058
 
1081
1059
  if opt[:section].class != Hash
1082
- $stderr.puts "Invalid section object"
1060
+ warn 'Invalid section object'
1083
1061
  return
1084
1062
  end
1085
1063
 
1086
- items = opt[:section]['items'].sort_by{|item| item['date'] }
1064
+ items = opt[:section]['items'].sort_by { |item| item['date'] }
1087
1065
 
1088
1066
  if opt[:date_filter].length == 2
1089
1067
  start_date = opt[:date_filter][0]
1090
1068
  end_date = opt[:date_filter][1]
1091
- items.keep_if {|item|
1069
+ items.keep_if do |item|
1092
1070
  if end_date
1093
1071
  item['date'] >= start_date && item['date'] <= end_date
1094
1072
  else
1095
1073
  item['date'].strftime('%F') == start_date.strftime('%F')
1096
1074
  end
1097
- }
1075
+ end
1098
1076
  end
1099
1077
 
1100
1078
  if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
1101
- items.delete_if {|item|
1079
+ items.delete_if do |item|
1102
1080
  if opt[:tag_filter]['bool'] =~ /(AND|ALL)/
1103
1081
  score = 0
1104
- opt[:tag_filter]['tags'].each {|tag|
1082
+ opt[:tag_filter]['tags'].each do |tag|
1105
1083
  score += 1 if item['title'] =~ /@#{tag}/
1106
- }
1084
+ end
1107
1085
  score < opt[:tag_filter]['tags'].length
1108
1086
  elsif opt[:tag_filter]['bool'] =~ /NONE/
1109
1087
  del = false
1110
- opt[:tag_filter]['tags'].each {|tag|
1088
+ opt[:tag_filter]['tags'].each do |tag|
1111
1089
  del = true if item['title'] =~ /@#{tag}/
1112
- }
1090
+ end
1113
1091
  del
1114
1092
  elsif opt[:tag_filter]['bool'] =~ /(OR|ANY)/
1115
1093
  del = true
1116
- opt[:tag_filter]['tags'].each {|tag|
1094
+ opt[:tag_filter]['tags'].each do |tag|
1117
1095
  del = false if item['title'] =~ /@#{tag}/
1118
- }
1096
+ end
1119
1097
  del
1120
1098
  end
1121
- }
1099
+ end
1122
1100
  end
1123
1101
 
1124
1102
  if opt[:search]
1125
- items.keep_if {|item|
1126
- text = item['note'] ? item['title'] + item['note'].join(" ") : item['title']
1127
- if opt[:search].strip =~ /^\/.*?\/$/
1128
- pattern = opt[:search].sub(/\/(.*?)\//,'\1')
1129
- else
1130
- pattern = opt[:search].split('').join('.{0,3}')
1131
- 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
1132
1110
  text =~ /#{pattern}/i
1133
- }
1111
+ end
1134
1112
  end
1135
1113
 
1136
1114
  if opt[:only_timed]
1137
- items.delete_if {|item|
1115
+ items.delete_if do |item|
1138
1116
  get_interval(item) == false
1139
- }
1117
+ end
1140
1118
  end
1141
1119
 
1142
1120
  if opt[:today]
1143
- items.delete_if {|item|
1121
+ items.delete_if do |item|
1144
1122
  item['date'] < Date.today.to_time
1145
- }.reverse!
1123
+ end.reverse!
1146
1124
  section = Time.now.strftime('%A, %B %d')
1147
1125
  elsif opt[:yesterday]
1148
- items.delete_if {|item| item['date'] <= Date.today.prev_day.to_time or
1149
- item['date'] >= Date.today.to_time
1150
- }.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]
1151
1132
  else
1152
- if opt[:age] =~ /oldest/i
1153
- items = items[0..count]
1154
- else
1155
- items = items.reverse[0..count]
1156
- end
1133
+ items = items.reverse[0..count]
1157
1134
  end
1158
1135
 
1159
- if opt[:order] =~ /^a/i
1160
- items.reverse!
1161
- end
1136
+ items.reverse! if opt[:order] =~ /^a/i
1162
1137
 
1163
- out = ""
1138
+ out = ''
1164
1139
 
1165
- if opt[:output]
1166
- raise "Unknown output format" unless opt[:output] =~ /(template|html|csv|json|timeline)/
1167
- end
1168
- if opt[:output] == "csv"
1169
- output = [CSV.generate_line(['date','title','note','timer','section'])]
1170
- items.each {|i|
1171
- 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 = ''
1172
1146
  if i['note']
1173
- 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*$/ }
1174
1148
  note = arr.join("\n") unless arr.nil?
1175
1149
  end
1176
- if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1177
- interval = get_interval(i, false)
1178
- end
1150
+ interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1179
1151
  interval ||= 0
1180
- output.push(CSV.generate_line([i['date'],i['title'],note,interval,i['section']]))
1181
- }
1182
- out = output.join("")
1183
- 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'
1184
1156
  items_out = []
1185
1157
  max = items[-1]['date'].strftime('%F')
1186
1158
  min = items[0]['date'].strftime('%F')
1187
- items.each_with_index {|i,index|
1159
+ items.each_with_index do |i, index|
1188
1160
  if String.method_defined? :force_encoding
1189
1161
  title = i['title'].force_encoding('utf-8')
1190
- 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']
1191
1163
  else
1192
1164
  title = i['title']
1193
1165
  note = i['note'].map { |line| line.strip } if i['note']
1194
1166
  end
1195
1167
  if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1196
- end_date = Time.parse($1)
1197
- interval = get_interval(i,false)
1168
+ end_date = Time.parse(Regexp.last_match(1))
1169
+ interval = get_interval(i, false)
1198
1170
  end
1199
- end_date ||= ""
1171
+ end_date ||= ''
1200
1172
  interval ||= 0
1201
- note ||= ""
1173
+ note ||= ''
1202
1174
 
1203
1175
  tags = []
1204
- skip_tags = ['meanwhile', 'done', 'cancelled', 'flagged']
1205
- i['title'].scan(/@([^\(\s]+)(?:\((.*?)\))?/).each {|tag|
1176
+ skip_tags = %w[meanwhile done cancelled flagged]
1177
+ i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
1206
1178
  tags.push(tag[0]) unless skip_tags.include?(tag[0])
1207
- }
1208
- if opt[:output] == "json"
1179
+ end
1180
+ if opt[:output] == 'json'
1209
1181
 
1210
1182
  items_out << {
1211
- :date => i['date'],
1212
- :end_date => end_date,
1213
- :title => title.strip, #+ " #{note}"
1214
- :note => note.class == Array ? note.map(&:strip).join("\n") : note,
1215
- :time => "%02d:%02d:%02d" % fmt_time(interval),
1216
- :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
1217
1189
  }
1218
1190
 
1219
- elsif opt[:output] == "timeline"
1191
+ elsif opt[:output] == 'timeline'
1220
1192
  new_item = {
1221
1193
  'id' => index + 1,
1222
1194
  'content' => title.strip, #+ " #{note}"
1223
- 'title' => title.strip + " (#{"%02d:%02d:%02d" % fmt_time(interval)})",
1195
+ 'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
1224
1196
  'start' => i['date'].strftime('%F'),
1225
1197
  'type' => 'point'
1226
1198
  }
1227
1199
 
1228
1200
  if interval && interval > 0
1229
1201
  new_item['end'] = end_date.strftime('%F')
1230
- if interval > 3600 * 3
1231
- new_item['type'] = 'range'
1232
- end
1202
+ new_item['type'] = 'range' if interval > 3600 * 3
1233
1203
  end
1234
1204
  items_out.push(new_item)
1235
1205
  end
1236
- }
1237
- if opt[:output] == "json"
1206
+ end
1207
+ if opt[:output] == 'json'
1238
1208
  out = {
1239
1209
  'section' => section,
1240
1210
  'items' => items_out,
1241
- 'timers' => tag_times("json", opt[:sort_tags])
1211
+ 'timers' => tag_times('json', opt[:sort_tags])
1242
1212
  }.to_json
1243
- elsif opt[:output] == "timeline"
1244
- template =<<EOTEMPLATE
1245
- <!doctype html>
1246
- <html>
1247
- <head>
1248
- <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
1249
- <script src="http://visjs.org/dist/vis.js"></script>
1250
- </head>
1251
- <body>
1252
- <div id="mytimeline"></div>
1253
-
1254
- <script type="text/javascript">
1255
- // DOM element where the Timeline will be attached
1256
- var container = document.getElementById('mytimeline');
1257
-
1258
- // Create a DataSet with data (enables two way data binding)
1259
- var data = new vis.DataSet(#{items_out.to_json});
1260
-
1261
- // Configuration for the Timeline
1262
- var options = {
1263
- width: '100%',
1264
- height: '800px',
1265
- margin: {
1266
- item: 20
1267
- },
1268
- stack: true,
1269
- min: '#{min}',
1270
- max: '#{max}'
1271
- };
1272
-
1273
- // Create a Timeline
1274
- var timeline = new vis.Timeline(container, data, options);
1275
- </script>
1276
- </body>
1277
- </html>
1278
- 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
1279
1249
  return template
1280
1250
  end
1281
- elsif opt[:output] == "html"
1251
+ elsif opt[:output] == 'html'
1282
1252
  page_title = section
1283
1253
  items_out = []
1284
- items.each {|i|
1254
+ items.each do |i|
1285
1255
  # if i.has_key?('note')
1286
1256
  # note = '<span class="note">' + i['note'].map{|n| n.strip }.join('<br>') + '</span>'
1287
1257
  # else
@@ -1289,83 +1259,81 @@ EOTEMPLATE
1289
1259
  # end
1290
1260
  if String.method_defined? :force_encoding
1291
1261
  title = i['title'].force_encoding('utf-8').link_urls
1292
- 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']
1293
1263
  else
1294
1264
  title = i['title'].link_urls
1295
1265
  note = i['note'].map { |line| line.strip.link_urls } if i['note']
1296
1266
  end
1297
1267
 
1298
- if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1299
- interval = get_interval(i)
1300
- end
1268
+ interval = get_interval(i) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1301
1269
  interval ||= false
1302
1270
 
1303
1271
  items_out << {
1304
- :date => i['date'].strftime('%a %-I:%M%p'),
1305
- :title => title.gsub(/(@[^ \(]+(\(.*?\))?)/im,'<span class="tag">\1</span>').strip, #+ " #{note}"
1306
- :note => note,
1307
- :time => interval,
1308
- :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']
1309
1277
  }
1310
- }
1311
-
1312
- if @config['html_template']['haml'] && File.exists?(File.expand_path(@config['html_template']['haml']))
1313
- template = IO.read(File.expand_path(@config['html_template']['haml']))
1314
- else
1315
- template = haml_template
1316
1278
  end
1317
1279
 
1318
- if @config['html_template']['css'] && File.exists?(File.expand_path(@config['html_template']['css']))
1319
- style = IO.read(File.expand_path(@config['html_template']['css']))
1320
- else
1321
- style = css_template
1322
- 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
1323
1291
 
1324
- totals = opt[:totals] ? tag_times("html", opt[:sort_tags]) : ""
1292
+ totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
1325
1293
  engine = Haml::Engine.new(template)
1326
- 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 })
1327
1296
  else
1328
- items.each {|item|
1329
-
1297
+ items.each do |item|
1330
1298
  if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
1331
1299
  flag = colors[@config['marker_color']]
1332
1300
  reset = colors['default']
1333
1301
  else
1334
- flag = ""
1335
- reset = ""
1302
+ flag = ''
1303
+ reset = ''
1336
1304
  end
1337
1305
 
1338
1306
  if (item.has_key?('note') && !item['note'].empty?) && @config[:include_notes]
1339
- 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(/^-/, '—') + ' ' }
1340
1310
  if opt[:wrap_width] && opt[:wrap_width] > 0
1341
1311
  width = opt[:wrap_width]
1342
- note_lines.map! {|line|
1312
+ note_lines.map! do |line|
1343
1313
  line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
1344
- }
1314
+ end
1345
1315
  end
1346
1316
  note = "\n#{note_lines.join("\n").chomp}"
1347
1317
  else
1348
- note = ""
1318
+ note = ''
1349
1319
  end
1350
1320
  output = opt[:template].dup
1351
1321
 
1352
1322
  output.gsub!(/%[a-z]+/) do |m|
1353
- if colors.has_key?(m.sub(/^%/,''))
1354
- colors[m.sub(/^%/,'')]
1323
+ if colors.has_key?(m.sub(/^%/, ''))
1324
+ colors[m.sub(/^%/, '')]
1355
1325
  else
1356
1326
  m
1357
1327
  end
1358
1328
  end
1359
1329
 
1360
- output.sub!(/%date/,item['date'].strftime(opt[:format]))
1330
+ output.sub!(/%date/, item['date'].strftime(opt[:format]))
1361
1331
 
1362
- if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1363
- interval = get_interval(item)
1364
- end
1365
- interval ||= ""
1366
- 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)
1367
1335
 
1368
- output.sub!(/%shortdate/) {
1336
+ output.sub!(/%shortdate/) do
1369
1337
  if item['date'] > Date.today.to_time
1370
1338
  item['date'].strftime('%_I:%M%P')
1371
1339
  elsif item['date'] > (Date.today - 7).to_time
@@ -1375,45 +1343,45 @@ EOTEMPLATE
1375
1343
  else
1376
1344
  item['date'].strftime('%b %d %Y, %-I:%M%P')
1377
1345
  end
1378
- }
1346
+ end
1379
1347
 
1380
- output.sub!(/%title/) {|m|
1348
+ output.sub!(/%title/) do |_m|
1381
1349
  if opt[:wrap_width] && opt[:wrap_width] > 0
1382
- 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
1383
1351
  else
1384
- flag+item['title'].chomp+reset
1352
+ flag + item['title'].chomp + reset
1385
1353
  end
1386
- }
1354
+ end
1387
1355
 
1388
- output.sub!(/%section/,item['section']) if item['section']
1356
+ output.sub!(/%section/, item['section']) if item['section']
1389
1357
 
1390
1358
  if opt[:tags_color]
1391
1359
  escapes = output.scan(/(\e\[[\d;]+m)[^\e]+@/)
1392
- if escapes.length > 0
1393
- last_color = escapes[-1][0]
1394
- else
1395
- last_color = colors['default']
1396
- end
1397
- 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}")
1398
1366
  end
1399
- output.sub!(/%note/,note)
1400
- output.sub!(/%odnote/,note.gsub(/^\t*/,""))
1401
- output.sub!(/%chompnote/,note.gsub(/\n+/,' ').gsub(/(^\s*|\s*$)/,'').gsub(/\s+/,' '))
1402
- output.gsub!(/%hr(_under)?/) do |m|
1403
- 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 = ''
1404
1372
  `tput cols`.to_i.times do
1405
- o += $1.nil? ? "-" : "_"
1373
+ o += Regexp.last_match(1).nil? ? '-' : '_'
1406
1374
  end
1407
1375
  o
1408
1376
  end
1409
- output.gsub!(/%n/,"\n")
1410
- output.gsub!(/%t/,"\t")
1377
+ output.gsub!(/%n/, "\n")
1378
+ output.gsub!(/%t/, "\t")
1411
1379
 
1412
1380
  out += output + "\n"
1413
- }
1414
- out += tag_times("text", opt[:sort_tags]) if opt[:totals]
1381
+ end
1382
+ out += tag_times('text', opt[:sort_tags]) if opt[:totals]
1415
1383
  end
1416
- return out
1384
+ out
1417
1385
  end
1418
1386
 
1419
1387
  ##
@@ -1426,15 +1394,12 @@ EOTEMPLATE
1426
1394
  ## @param tags (Array) Tags to archive
1427
1395
  ## @param bool (String) Tag boolean combinator
1428
1396
  ##
1429
- def archive(section="Currently",count=5,destination=nil,tags=nil,bool=nil,export=nil)
1430
-
1397
+ def archive(section = 'Currently', count = 5, destination = nil, tags = nil, bool = nil, _export = nil)
1431
1398
  section = choose_section if section.nil? || section =~ /choose/i
1432
1399
  archive_all = section =~ /all/i # && !(tags.nil? || tags.empty?)
1433
1400
  section = guess_section(section) unless archive_all
1434
1401
 
1435
- if destination =~ /archive/i && !sections.include?("Archive")
1436
- add_section("Archive")
1437
- end
1402
+ add_section('Archive') if destination =~ /archive/i && !sections.include?('Archive')
1438
1403
 
1439
1404
  destination = guess_section(destination)
1440
1405
 
@@ -1442,16 +1407,16 @@ EOTEMPLATE
1442
1407
  if archive_all
1443
1408
  to_archive = sections.dup
1444
1409
  to_archive.delete(destination)
1445
- to_archive.each {|source,v|
1446
- do_archive(source, destination, { :count => count, :tags => tags, :bool => bool, :label => true })
1447
- }
1410
+ to_archive.each do |source, _v|
1411
+ do_archive(source, destination, { count: count, tags: tags, bool: bool, label: true })
1412
+ end
1448
1413
  else
1449
- 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 })
1450
1415
  end
1451
1416
 
1452
1417
  write(doing_file)
1453
1418
  else
1454
- raise "Either source or destination does not exist"
1419
+ raise 'Either source or destination does not exist'
1455
1420
  end
1456
1421
  end
1457
1422
 
@@ -1462,63 +1427,66 @@ EOTEMPLATE
1462
1427
  ## @param destination (String) The destination section
1463
1428
  ## @param opt (Hash) Additional Options
1464
1429
  ##
1465
- def do_archive(section, destination, opt={})
1430
+ def do_archive(section, destination, opt = {})
1466
1431
  count = opt[:count] || 5
1467
1432
  tags = opt[:tags] || []
1468
- bool = opt[:bool] || "AND"
1433
+ bool = opt[:bool] || 'AND'
1469
1434
  label = opt[:label] || false
1470
1435
 
1471
1436
  items = @content[section]['items']
1472
1437
  moved_items = []
1473
1438
 
1474
1439
  if tags && !tags.empty?
1475
- items.delete_if {|item|
1440
+ items.delete_if do |item|
1476
1441
  if bool =~ /(AND|ALL)/
1477
1442
  score = 0
1478
- tags.each {|tag|
1443
+ tags.each do |tag|
1479
1444
  score += 1 if item['title'] =~ /@#{tag}/i
1480
- }
1445
+ end
1481
1446
  res = score < tags.length
1482
1447
  moved_items.push(item) if res
1483
1448
  res
1484
1449
  elsif bool =~ /NONE/
1485
1450
  del = false
1486
- tags.each {|tag|
1451
+ tags.each do |tag|
1487
1452
  del = true if item['title'] =~ /@#{tag}/i
1488
- }
1453
+ end
1489
1454
  moved_items.push(item) if del
1490
1455
  del
1491
1456
  elsif bool =~ /(OR|ANY)/
1492
1457
  del = true
1493
- tags.each {|tag|
1458
+ tags.each do |tag|
1494
1459
  del = false if item['title'] =~ /@#{tag}/i
1495
- }
1460
+ end
1496
1461
  moved_items.push(item) if del
1497
1462
  del
1498
1463
  end
1499
- }
1500
- moved_items.each {|item|
1501
- if label
1502
- 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})")
1503
1469
  end
1504
- }
1470
+ end
1505
1471
  @content[section]['items'] = moved_items
1506
1472
  @content[destination]['items'] += items
1507
1473
  @results.push("Archived #{items.length} items from #{section} to #{destination}")
1508
1474
  else
1509
1475
 
1510
1476
  return if items.length < count
1511
- if count == 0
1512
- @content[section]['items'] = []
1513
- else
1514
- @content[section]['items'] = items[0..count-1]
1515
- end
1516
1477
 
1517
- items.each{|item|
1518
- if label
1519
- 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})")
1520
1488
  end
1521
- }
1489
+ end
1522
1490
 
1523
1491
  @content[destination]['items'] += items[count..-1]
1524
1492
  @results.push("Archived #{items.length - count} items from #{section} to #{destination}")
@@ -1574,7 +1542,6 @@ EOTEMPLATE
1574
1542
  color
1575
1543
  end
1576
1544
 
1577
-
1578
1545
  ##
1579
1546
  ## @brief Show all entries from the current day
1580
1547
  ##
@@ -1582,12 +1549,13 @@ EOTEMPLATE
1582
1549
  ## @param output (String) output format
1583
1550
  ## @param opt (Hash) Options
1584
1551
  ##
1585
- def today(times=true,output=nil,opt={})
1552
+ def today(times = true, output = nil, opt = {})
1586
1553
  opt[:totals] ||= false
1587
1554
  opt[:sort_tags] ||= false
1588
1555
 
1589
1556
  cfg = @config['templates']['today']
1590
- 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] })
1591
1559
  end
1592
1560
 
1593
1561
  ##
@@ -1599,16 +1567,15 @@ EOTEMPLATE
1599
1567
  ## @param output (String) Output format
1600
1568
  ## @param opt (Hash) Additional Options
1601
1569
  ##
1602
- def list_date(dates,section,times=nil,output=nil,opt={})
1570
+ def list_date(dates, section, times = nil, output = nil, opt = {})
1603
1571
  opt[:totals] ||= false
1604
1572
  opt[:sort_tags] ||= false
1605
1573
  section = guess_section(section)
1606
1574
  # :date_filter expects an array with start and end date
1607
- if dates.class == String
1608
- dates = [dates, dates]
1609
- end
1575
+ dates = [dates, dates] if dates.instance_of?(String)
1610
1576
 
1611
- 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] })
1612
1579
  end
1613
1580
 
1614
1581
  ##
@@ -1619,11 +1586,12 @@ EOTEMPLATE
1619
1586
  ## @param output (String) Output format
1620
1587
  ## @param opt (Hash) Additional Options
1621
1588
  ##
1622
- def yesterday(section,times=nil,output=nil,opt={})
1589
+ def yesterday(section, times = nil, output = nil, opt = {})
1623
1590
  opt[:totals] ||= false
1624
1591
  opt[:sort_tags] ||= false
1625
1592
  section = guess_section(section)
1626
- 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] })
1627
1595
  end
1628
1596
 
1629
1597
  ##
@@ -1633,7 +1601,7 @@ EOTEMPLATE
1633
1601
  ## @param section (String) The section to show from, default Currently
1634
1602
  ## @param opt (Hash) Additional Options
1635
1603
  ##
1636
- def recent(count=10,section=nil,opt={})
1604
+ def recent(count = 10, section = nil, opt = {})
1637
1605
  times = opt[:t] || true
1638
1606
  opt[:totals] ||= false
1639
1607
  opt[:sort_tags] ||= false
@@ -1641,7 +1609,8 @@ EOTEMPLATE
1641
1609
  cfg = @config['templates']['recent']
1642
1610
  section ||= @current_section
1643
1611
  section = guess_section(section)
1644
- 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] })
1645
1614
  end
1646
1615
 
1647
1616
  ##
@@ -1650,11 +1619,12 @@ EOTEMPLATE
1650
1619
  ## @param times (Bool) Show times
1651
1620
  ## @param section (String) Section to pull from, default Currently
1652
1621
  ##
1653
- def last(times=true,section=nil)
1622
+ def last(times = true, section = nil)
1654
1623
  section ||= @current_section
1655
1624
  section = guess_section(section)
1656
1625
  cfg = @config['templates']['last']
1657
- 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 })
1658
1628
  end
1659
1629
 
1660
1630
  ##
@@ -1662,22 +1632,22 @@ EOTEMPLATE
1662
1632
  ##
1663
1633
  ## @param format (String) return format (html, json, or text)
1664
1634
  ##
1665
- def tag_times(format="text", sort_by_name = false)
1666
- return "" if @timers.empty?
1635
+ def tag_times(format = 'text', sort_by_name = false)
1636
+ return '' if @timers.empty?
1667
1637
 
1668
- 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
1669
1639
 
1670
- total = @timers.delete("All")
1640
+ total = @timers.delete('All')
1671
1641
 
1672
- tags_data = @timers.delete_if { |k,v| v == 0}
1673
- if sort_by_name
1674
- sorted_tags_data = tags_data.sort_by{|k,v| k }.reverse
1675
- else
1676
- sorted_tags_data = tags_data.sort_by{|k,v| v }
1677
- 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
1678
1648
 
1679
- if format == "html"
1680
- output =<<EOS
1649
+ if format == 'html'
1650
+ output = <<EOS
1681
1651
  <table>
1682
1652
  <caption id="tagtotals">Tag Totals</caption>
1683
1653
  <colgroup>
@@ -1692,10 +1662,12 @@ EOTEMPLATE
1692
1662
  </thead>
1693
1663
  <tbody>
1694
1664
  EOS
1695
- sorted_tags_data.reverse.each {|k,v|
1696
- 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
1697
- }
1698
- 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
1699
1671
  <tr>
1700
1672
  <td style="text-align:left;" colspan="2"></td>
1701
1673
  </tr>
@@ -1703,34 +1675,34 @@ EOS
1703
1675
  <tfoot>
1704
1676
  <tr>
1705
1677
  <td style="text-align:left;"><strong>Total</strong></td>
1706
- <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>
1707
1679
  </tr>
1708
1680
  </tfoot>
1709
1681
  </table>
1710
1682
  EOS
1711
1683
  output + tail
1712
- elsif format == "json"
1684
+ elsif format == 'json'
1713
1685
  output = []
1714
- sorted_tags_data.reverse.each {|k,v|
1686
+ sorted_tags_data.reverse.each do |k, v|
1715
1687
  output << {
1716
1688
  'tag' => k,
1717
1689
  'seconds' => v,
1718
- 'formatted' => "%02d:%02d:%02d" % fmt_time(v)
1690
+ 'formatted' => '%02d:%02d:%02d' % fmt_time(v)
1719
1691
  }
1720
- }
1692
+ end
1721
1693
  output
1722
1694
  else
1723
1695
  output = []
1724
- sorted_tags_data.reverse.each {|k,v|
1725
- spacer = ""
1696
+ sorted_tags_data.reverse.each do |k, v|
1697
+ spacer = ''
1726
1698
  (max - k.length).times do
1727
- spacer += " "
1699
+ spacer += ' '
1728
1700
  end
1729
- output.push("#{k}:#{spacer}#{"%02d:%02d:%02d" % fmt_time(v)}")
1730
- }
1701
+ output.push("#{k}:#{spacer}#{'%02d:%02d:%02d' % fmt_time(v)}")
1702
+ end
1731
1703
 
1732
- output = output.empty? ? "" : "\n--- Tag Totals ---\n" + output.join("\n")
1733
- 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"
1734
1706
  output
1735
1707
  end
1736
1708
  end
@@ -1744,53 +1716,54 @@ EOS
1744
1716
  def autotag(text)
1745
1717
  return unless text
1746
1718
  return text unless @auto_tag
1719
+
1747
1720
  current_tags = text.scan(/@\w+/)
1748
1721
  whitelisted = []
1749
- @config['autotag']['whitelist'].each {|tag|
1722
+ @config['autotag']['whitelist'].each do |tag|
1723
+ next if text =~ /@#{tag}\b/i
1724
+
1750
1725
  text.sub!(/(?<!@)(#{tag.strip})\b/i) do |m|
1751
1726
  m.downcase! if tag =~ /[a-z]/
1752
1727
  whitelisted.push("@#{m}")
1753
1728
  "@#{m}"
1754
- end unless text =~ /@#{tag}\b/i
1755
- }
1729
+ end
1730
+ end
1756
1731
  tail_tags = []
1757
- @config['autotag']['synonyms'].each {|tag, v|
1758
- v.each {|word|
1759
- if text =~ /\b#{word}\b/i
1760
- unless current_tags.include?("@#{tag}") || whitelisted.include?("@#{tag}")
1761
- tail_tags.push(tag)
1762
- end
1763
- end
1764
- }
1765
- }
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
1766
1739
  if @config['autotag'].key? 'transform'
1767
- @config['autotag']['transform'].each {|tag|
1768
- if tag =~ /\S+:\S+/
1769
- rx, r = tag.split(/:/)
1770
- r.gsub!(/\$/,'\\')
1771
- rx.sub!(/^@/,'')
1772
- regex = Regexp.new('@' + rx + '\b')
1773
-
1774
- matches = text.scan(regex)
1775
- matches.each {|m|
1776
- new_tag = r
1777
- if m.kind_of?(Array)
1778
- index = 1
1779
- m.each {|v|
1780
- new_tag = new_tag.gsub('\\' + index.to_s, v)
1781
- index = index + 1
1782
- }
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
1783
1758
  end
1784
- tail_tags.push(new_tag)
1785
- } if matches
1759
+ end
1760
+ tail_tags.push(new_tag)
1786
1761
  end
1787
- }
1788
- end
1789
- if whitelisted.length > 0
1790
- @results.push("Whitelisted tags: #{whitelisted.join(', ')}")
1762
+ end
1791
1763
  end
1764
+ @results.push("Whitelisted tags: #{whitelisted.join(', ')}") if whitelisted.length > 0
1792
1765
  if tail_tags.length > 0
1793
- tags = tail_tags.uniq.map {|t| '@'+t }.join(' ')
1766
+ tags = tail_tags.uniq.map { |t| '@' + t }.join(' ')
1794
1767
  @results.push("Synonym tags: #{tags}")
1795
1768
  text + ' ' + tags
1796
1769
  else
@@ -1806,43 +1779,43 @@ EOS
1806
1779
  ## @param item (Hash) The entry
1807
1780
  ## @param formatted (Bool) Return human readable time (default seconds)
1808
1781
  ##
1809
- def get_interval(item, formatted=true)
1782
+ def get_interval(item, formatted = true)
1810
1783
  done = nil
1811
1784
  start = nil
1812
1785
 
1813
1786
  if @interval_cache.keys.include? item['title']
1814
1787
  seconds = @interval_cache[item['title']]
1815
- return seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
1788
+ return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
1816
1789
  end
1817
1790
 
1818
1791
  if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1819
- done = Time.parse($1)
1792
+ done = Time.parse(Regexp.last_match(1))
1820
1793
  else
1821
1794
  return nil
1822
1795
  end
1823
1796
 
1824
- if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1825
- start = Time.parse($1)
1826
- else
1827
- start = item['date']
1828
- 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
1829
1802
 
1830
1803
  seconds = (done - start).to_i
1831
1804
 
1832
- item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each {|m|
1833
- 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
1834
1807
  if @timers.has_key?(k)
1835
1808
  @timers[k] += seconds
1836
1809
  else
1837
1810
  @timers[k] = seconds
1838
1811
  end
1839
- }
1812
+ end
1840
1813
 
1841
1814
  @interval_cache[item['title']] = seconds
1842
1815
 
1843
1816
  return seconds unless formatted
1844
1817
 
1845
- seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
1818
+ seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
1846
1819
  end
1847
1820
 
1848
1821
  ##
@@ -1851,19 +1824,19 @@ EOS
1851
1824
  ## @param seconds The seconds
1852
1825
  ##
1853
1826
  def fmt_time(seconds)
1854
- if seconds.nil?
1855
- return [0, 0, 0]
1856
- end
1827
+ return [0, 0, 0] if seconds.nil?
1828
+
1857
1829
  if seconds =~ /(\d+):(\d+):(\d+)/
1858
- h, m, s = [$1, $2, $3]
1830
+ h = Regexp.last_match(1)
1831
+ m = Regexp.last_match(2)
1832
+ s = Regexp.last_match(3)
1859
1833
  seconds = (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
1860
1834
  end
1861
- minutes = (seconds / 60).to_i
1835
+ minutes = (seconds / 60).to_i
1862
1836
  hours = (minutes / 60).to_i
1863
1837
  days = (hours / 24).to_i
1864
1838
  hours = (hours % 24).to_i
1865
1839
  minutes = (minutes % 60).to_i
1866
1840
  [days, hours, minutes]
1867
1841
  end
1868
-
1869
1842
  end