doing 1.0.45 → 1.0.50

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -5
  3. data/bin/doing +78 -126
  4. data/lib/doing/version.rb +1 -1
  5. data/lib/doing/wwid.rb +649 -685
  6. metadata +8 -2
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.45'
2
+ VERSION = '1.0.50'
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
647
  title = last['title'].sub(/\s*@done(\(.*?\))?/, '').chomp
673
- add_item(title, last['section'], {:note => opt[:note], :back => opt[:date], :timed => true})
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,35 +911,32 @@ 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
925
  file = File.expand_path(file)
963
- if File.exists?(file)
926
+ if File.exist?(file)
964
927
  # Create a backup copy for the undo command
965
- FileUtils.cp(file,file+"~")
928
+ FileUtils.cp(file, "#{file}~")
966
929
 
967
- File.open(file,'w+') do |f|
930
+ File.open(file, 'w+') do |f|
968
931
  f.puts output
969
932
  end
970
933
  end
971
934
 
972
935
  if @config.key?('run_after')
973
- script = File.expand_path(@config['run_after'])
974
- if File.exist?(script)
975
- # warn "Running #{script}"
976
- `#{script}`
936
+ stdout, stderr, status = Open3.capture3(@config['run_after'])
937
+ if status.exitstatus.positive?
938
+ warn "Error running #{@config['run_after']}"
939
+ warn stderr
977
940
  end
978
941
  end
979
942
  end
@@ -985,27 +948,27 @@ class WWID
985
948
  ## @param file (String) The filepath to restore
986
949
  ##
987
950
  def restore_backup(file)
988
- if File.exists?(file+"~")
989
- puts file+"~"
990
- FileUtils.cp(file+"~",file)
951
+ if File.exist?(file + '~')
952
+ puts file + '~'
953
+ FileUtils.cp(file + '~', file)
991
954
  @results.push("Restored #{file}")
992
955
  end
993
956
  end
994
957
 
995
-
996
958
  ##
997
959
  ## @brief Generate a menu of sections and allow user selection
998
960
  ##
999
961
  ## @return (String) The selected section name
1000
962
  ##
1001
963
  def choose_section
1002
- sections.each_with_index {|section, i|
1003
- puts "% 3d: %s" % [i+1, section]
1004
- }
964
+ sections.each_with_index do |section, i|
965
+ puts format('% 3d: %s', i + 1, section)
966
+ end
1005
967
  print "#{colors['green']}> #{colors['default']}"
1006
968
  num = STDIN.gets
1007
969
  return false if num =~ /^[a-z ]*$/i
1008
- return sections[num.to_i - 1]
970
+
971
+ sections[num.to_i - 1]
1009
972
  end
1010
973
 
1011
974
  ##
@@ -1023,13 +986,14 @@ class WWID
1023
986
  ## @return (String) The selected view name
1024
987
  ##
1025
988
  def choose_view
1026
- views.each_with_index {|view, i|
1027
- puts "% 3d: %s" % [i+1, view]
1028
- }
1029
- print "> "
989
+ views.each_with_index do |view, i|
990
+ puts format('% 3d: %s', i + 1, view)
991
+ end
992
+ print '> '
1030
993
  num = STDIN.gets
1031
994
  return false if num =~ /^[a-z ]*$/i
1032
- return views[num.to_i - 1]
995
+
996
+ views[num.to_i - 1]
1033
997
  end
1034
998
 
1035
999
  ##
@@ -1038,9 +1002,8 @@ class WWID
1038
1002
  ## @param title (String) The title of the view to retrieve
1039
1003
  ##
1040
1004
  def get_view(title)
1041
- if @config['views'].has_key?(title)
1042
- return @config['views'][title]
1043
- end
1005
+ return @config['views'][title] if @config['views'].has_key?(title)
1006
+
1044
1007
  false
1045
1008
  end
1046
1009
 
@@ -1050,14 +1013,14 @@ class WWID
1050
1013
  ##
1051
1014
  ## @param opt (Hash) Additional Options
1052
1015
  ##
1053
- def list_section(opt={})
1016
+ def list_section(opt = {})
1054
1017
  opt[:count] ||= 0
1055
1018
  count = opt[:count] - 1
1056
1019
  opt[:section] ||= nil
1057
1020
  opt[:format] ||= @default_date_format
1058
1021
  opt[:template] ||= @default_template
1059
- opt[:age] ||= "newest"
1060
- opt[:order] ||= "desc"
1022
+ opt[:age] ||= 'newest'
1023
+ opt[:order] ||= 'desc'
1061
1024
  opt[:today] ||= false
1062
1025
  opt[:tag_filter] ||= false
1063
1026
  opt[:tags_color] ||= false
@@ -1069,17 +1032,23 @@ class WWID
1069
1032
  opt[:date_filter] ||= []
1070
1033
 
1071
1034
  # opt[:highlight] ||= true
1072
- section = ""
1035
+ section = ''
1073
1036
  if opt[:section].nil?
1074
1037
  section = choose_section
1075
1038
  opt[:section] = @content[section]
1076
- elsif opt[:section].class == String
1039
+ elsif opt[:section].instance_of?(String)
1077
1040
  if opt[:section] =~ /^all$/i
1078
- combined = {'items' => []}
1079
- @content.each {|k,v|
1041
+ combined = { 'items' => [] }
1042
+ @content.each do |_k, v|
1080
1043
  combined['items'] += v['items']
1081
- }
1082
- 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
1083
1052
  opt[:section] = combined
1084
1053
  else
1085
1054
  section = guess_section(opt[:section])
@@ -1088,209 +1057,201 @@ class WWID
1088
1057
  end
1089
1058
 
1090
1059
  if opt[:section].class != Hash
1091
- $stderr.puts "Invalid section object"
1060
+ warn 'Invalid section object'
1092
1061
  return
1093
1062
  end
1094
1063
 
1095
- items = opt[:section]['items'].sort_by{|item| item['date'] }
1064
+ items = opt[:section]['items'].sort_by { |item| item['date'] }
1096
1065
 
1097
1066
  if opt[:date_filter].length == 2
1098
1067
  start_date = opt[:date_filter][0]
1099
1068
  end_date = opt[:date_filter][1]
1100
- items.keep_if {|item|
1069
+ items.keep_if do |item|
1101
1070
  if end_date
1102
1071
  item['date'] >= start_date && item['date'] <= end_date
1103
1072
  else
1104
1073
  item['date'].strftime('%F') == start_date.strftime('%F')
1105
1074
  end
1106
- }
1075
+ end
1107
1076
  end
1108
1077
 
1109
1078
  if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
1110
- items.delete_if {|item|
1079
+ items.delete_if do |item|
1111
1080
  if opt[:tag_filter]['bool'] =~ /(AND|ALL)/
1112
1081
  score = 0
1113
- opt[:tag_filter]['tags'].each {|tag|
1082
+ opt[:tag_filter]['tags'].each do |tag|
1114
1083
  score += 1 if item['title'] =~ /@#{tag}/
1115
- }
1084
+ end
1116
1085
  score < opt[:tag_filter]['tags'].length
1117
1086
  elsif opt[:tag_filter]['bool'] =~ /NONE/
1118
1087
  del = false
1119
- opt[:tag_filter]['tags'].each {|tag|
1088
+ opt[:tag_filter]['tags'].each do |tag|
1120
1089
  del = true if item['title'] =~ /@#{tag}/
1121
- }
1090
+ end
1122
1091
  del
1123
1092
  elsif opt[:tag_filter]['bool'] =~ /(OR|ANY)/
1124
1093
  del = true
1125
- opt[:tag_filter]['tags'].each {|tag|
1094
+ opt[:tag_filter]['tags'].each do |tag|
1126
1095
  del = false if item['title'] =~ /@#{tag}/
1127
- }
1096
+ end
1128
1097
  del
1129
1098
  end
1130
- }
1099
+ end
1131
1100
  end
1132
1101
 
1133
1102
  if opt[:search]
1134
- items.keep_if {|item|
1135
- text = item['note'] ? item['title'] + item['note'].join(" ") : item['title']
1136
- if opt[:search].strip =~ /^\/.*?\/$/
1137
- pattern = opt[:search].sub(/\/(.*?)\//,'\1')
1138
- else
1139
- pattern = opt[:search].split('').join('.{0,3}')
1140
- 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
1141
1110
  text =~ /#{pattern}/i
1142
- }
1111
+ end
1143
1112
  end
1144
1113
 
1145
1114
  if opt[:only_timed]
1146
- items.delete_if {|item|
1115
+ items.delete_if do |item|
1147
1116
  get_interval(item) == false
1148
- }
1117
+ end
1149
1118
  end
1150
1119
 
1151
1120
  if opt[:today]
1152
- items.delete_if {|item|
1121
+ items.delete_if do |item|
1153
1122
  item['date'] < Date.today.to_time
1154
- }.reverse!
1123
+ end.reverse!
1155
1124
  section = Time.now.strftime('%A, %B %d')
1156
1125
  elsif opt[:yesterday]
1157
- items.delete_if {|item| item['date'] <= Date.today.prev_day.to_time or
1158
- item['date'] >= Date.today.to_time
1159
- }.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]
1160
1132
  else
1161
- if opt[:age] =~ /oldest/i
1162
- items = items[0..count]
1163
- else
1164
- items = items.reverse[0..count]
1165
- end
1133
+ items = items.reverse[0..count]
1166
1134
  end
1167
1135
 
1168
- if opt[:order] =~ /^a/i
1169
- items.reverse!
1170
- end
1136
+ items.reverse! if opt[:order] =~ /^a/i
1171
1137
 
1172
- out = ""
1138
+ out = ''
1173
1139
 
1174
- if opt[:output]
1175
- raise "Unknown output format" unless opt[:output] =~ /(template|html|csv|json|timeline)/
1176
- end
1177
- if opt[:output] == "csv"
1178
- output = [CSV.generate_line(['date','title','note','timer','section'])]
1179
- items.each {|i|
1180
- 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 = ''
1181
1146
  if i['note']
1182
- 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*$/ }
1183
1148
  note = arr.join("\n") unless arr.nil?
1184
1149
  end
1185
- if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1186
- interval = get_interval(i, false)
1187
- end
1150
+ interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1188
1151
  interval ||= 0
1189
- output.push(CSV.generate_line([i['date'],i['title'],note,interval,i['section']]))
1190
- }
1191
- out = output.join("")
1192
- 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'
1193
1156
  items_out = []
1194
1157
  max = items[-1]['date'].strftime('%F')
1195
1158
  min = items[0]['date'].strftime('%F')
1196
- items.each_with_index {|i,index|
1159
+ items.each_with_index do |i, index|
1197
1160
  if String.method_defined? :force_encoding
1198
1161
  title = i['title'].force_encoding('utf-8')
1199
- 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']
1200
1163
  else
1201
1164
  title = i['title']
1202
1165
  note = i['note'].map { |line| line.strip } if i['note']
1203
1166
  end
1204
1167
  if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1205
- end_date = Time.parse($1)
1206
- interval = get_interval(i,false)
1168
+ end_date = Time.parse(Regexp.last_match(1))
1169
+ interval = get_interval(i, false)
1207
1170
  end
1208
- end_date ||= ""
1171
+ end_date ||= ''
1209
1172
  interval ||= 0
1210
- note ||= ""
1173
+ note ||= ''
1211
1174
 
1212
1175
  tags = []
1213
- skip_tags = ['meanwhile', 'done', 'cancelled', 'flagged']
1214
- i['title'].scan(/@([^\(\s]+)(?:\((.*?)\))?/).each {|tag|
1176
+ skip_tags = %w[meanwhile done cancelled flagged]
1177
+ i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
1215
1178
  tags.push(tag[0]) unless skip_tags.include?(tag[0])
1216
- }
1217
- if opt[:output] == "json"
1179
+ end
1180
+ if opt[:output] == 'json'
1218
1181
 
1219
1182
  items_out << {
1220
- :date => i['date'],
1221
- :end_date => end_date,
1222
- :title => title.strip, #+ " #{note}"
1223
- :note => note.class == Array ? note.map(&:strip).join("\n") : note,
1224
- :time => "%02d:%02d:%02d" % fmt_time(interval),
1225
- :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
1226
1189
  }
1227
1190
 
1228
- elsif opt[:output] == "timeline"
1191
+ elsif opt[:output] == 'timeline'
1229
1192
  new_item = {
1230
1193
  'id' => index + 1,
1231
1194
  'content' => title.strip, #+ " #{note}"
1232
- 'title' => title.strip + " (#{"%02d:%02d:%02d" % fmt_time(interval)})",
1195
+ 'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
1233
1196
  'start' => i['date'].strftime('%F'),
1234
1197
  'type' => 'point'
1235
1198
  }
1236
1199
 
1237
1200
  if interval && interval > 0
1238
1201
  new_item['end'] = end_date.strftime('%F')
1239
- if interval > 3600 * 3
1240
- new_item['type'] = 'range'
1241
- end
1202
+ new_item['type'] = 'range' if interval > 3600 * 3
1242
1203
  end
1243
1204
  items_out.push(new_item)
1244
1205
  end
1245
- }
1246
- if opt[:output] == "json"
1206
+ end
1207
+ if opt[:output] == 'json'
1247
1208
  out = {
1248
1209
  'section' => section,
1249
1210
  'items' => items_out,
1250
- 'timers' => tag_times("json", opt[:sort_tags])
1211
+ 'timers' => tag_times('json', opt[:sort_tags])
1251
1212
  }.to_json
1252
- elsif opt[:output] == "timeline"
1253
- template =<<EOTEMPLATE
1254
- <!doctype html>
1255
- <html>
1256
- <head>
1257
- <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
1258
- <script src="http://visjs.org/dist/vis.js"></script>
1259
- </head>
1260
- <body>
1261
- <div id="mytimeline"></div>
1262
-
1263
- <script type="text/javascript">
1264
- // DOM element where the Timeline will be attached
1265
- var container = document.getElementById('mytimeline');
1266
-
1267
- // Create a DataSet with data (enables two way data binding)
1268
- var data = new vis.DataSet(#{items_out.to_json});
1269
-
1270
- // Configuration for the Timeline
1271
- var options = {
1272
- width: '100%',
1273
- height: '800px',
1274
- margin: {
1275
- item: 20
1276
- },
1277
- stack: true,
1278
- min: '#{min}',
1279
- max: '#{max}'
1280
- };
1281
-
1282
- // Create a Timeline
1283
- var timeline = new vis.Timeline(container, data, options);
1284
- </script>
1285
- </body>
1286
- </html>
1287
- 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
1288
1249
  return template
1289
1250
  end
1290
- elsif opt[:output] == "html"
1251
+ elsif opt[:output] == 'html'
1291
1252
  page_title = section
1292
1253
  items_out = []
1293
- items.each {|i|
1254
+ items.each do |i|
1294
1255
  # if i.has_key?('note')
1295
1256
  # note = '<span class="note">' + i['note'].map{|n| n.strip }.join('<br>') + '</span>'
1296
1257
  # else
@@ -1298,83 +1259,81 @@ EOTEMPLATE
1298
1259
  # end
1299
1260
  if String.method_defined? :force_encoding
1300
1261
  title = i['title'].force_encoding('utf-8').link_urls
1301
- 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']
1302
1263
  else
1303
1264
  title = i['title'].link_urls
1304
1265
  note = i['note'].map { |line| line.strip.link_urls } if i['note']
1305
1266
  end
1306
1267
 
1307
- if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1308
- interval = get_interval(i)
1309
- end
1268
+ interval = get_interval(i) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1310
1269
  interval ||= false
1311
1270
 
1312
1271
  items_out << {
1313
- :date => i['date'].strftime('%a %-I:%M%p'),
1314
- :title => title.gsub(/(@[^ \(]+(\(.*?\))?)/im,'<span class="tag">\1</span>').strip, #+ " #{note}"
1315
- :note => note,
1316
- :time => interval,
1317
- :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']
1318
1277
  }
1319
- }
1320
-
1321
- if @config['html_template']['haml'] && File.exists?(File.expand_path(@config['html_template']['haml']))
1322
- template = IO.read(File.expand_path(@config['html_template']['haml']))
1323
- else
1324
- template = haml_template
1325
1278
  end
1326
1279
 
1327
- if @config['html_template']['css'] && File.exists?(File.expand_path(@config['html_template']['css']))
1328
- style = IO.read(File.expand_path(@config['html_template']['css']))
1329
- else
1330
- style = css_template
1331
- 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
1332
1291
 
1333
- totals = opt[:totals] ? tag_times("html", opt[:sort_tags]) : ""
1292
+ totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
1334
1293
  engine = Haml::Engine.new(template)
1335
- 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 })
1336
1296
  else
1337
- items.each {|item|
1338
-
1297
+ items.each do |item|
1339
1298
  if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
1340
1299
  flag = colors[@config['marker_color']]
1341
1300
  reset = colors['default']
1342
1301
  else
1343
- flag = ""
1344
- reset = ""
1302
+ flag = ''
1303
+ reset = ''
1345
1304
  end
1346
1305
 
1347
1306
  if (item.has_key?('note') && !item['note'].empty?) && @config[:include_notes]
1348
- 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(/^-/, '—') + ' ' }
1349
1310
  if opt[:wrap_width] && opt[:wrap_width] > 0
1350
1311
  width = opt[:wrap_width]
1351
- note_lines.map! {|line|
1312
+ note_lines.map! do |line|
1352
1313
  line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
1353
- }
1314
+ end
1354
1315
  end
1355
1316
  note = "\n#{note_lines.join("\n").chomp}"
1356
1317
  else
1357
- note = ""
1318
+ note = ''
1358
1319
  end
1359
1320
  output = opt[:template].dup
1360
1321
 
1361
1322
  output.gsub!(/%[a-z]+/) do |m|
1362
- if colors.has_key?(m.sub(/^%/,''))
1363
- colors[m.sub(/^%/,'')]
1323
+ if colors.has_key?(m.sub(/^%/, ''))
1324
+ colors[m.sub(/^%/, '')]
1364
1325
  else
1365
1326
  m
1366
1327
  end
1367
1328
  end
1368
1329
 
1369
- output.sub!(/%date/,item['date'].strftime(opt[:format]))
1330
+ output.sub!(/%date/, item['date'].strftime(opt[:format]))
1370
1331
 
1371
- if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1372
- interval = get_interval(item)
1373
- end
1374
- interval ||= ""
1375
- 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)
1376
1335
 
1377
- output.sub!(/%shortdate/) {
1336
+ output.sub!(/%shortdate/) do
1378
1337
  if item['date'] > Date.today.to_time
1379
1338
  item['date'].strftime('%_I:%M%P')
1380
1339
  elsif item['date'] > (Date.today - 7).to_time
@@ -1384,45 +1343,45 @@ EOTEMPLATE
1384
1343
  else
1385
1344
  item['date'].strftime('%b %d %Y, %-I:%M%P')
1386
1345
  end
1387
- }
1346
+ end
1388
1347
 
1389
- output.sub!(/%title/) {|m|
1348
+ output.sub!(/%title/) do |_m|
1390
1349
  if opt[:wrap_width] && opt[:wrap_width] > 0
1391
- 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
1392
1351
  else
1393
- flag+item['title'].chomp+reset
1352
+ flag + item['title'].chomp + reset
1394
1353
  end
1395
- }
1354
+ end
1396
1355
 
1397
- output.sub!(/%section/,item['section']) if item['section']
1356
+ output.sub!(/%section/, item['section']) if item['section']
1398
1357
 
1399
1358
  if opt[:tags_color]
1400
1359
  escapes = output.scan(/(\e\[[\d;]+m)[^\e]+@/)
1401
- if escapes.length > 0
1402
- last_color = escapes[-1][0]
1403
- else
1404
- last_color = colors['default']
1405
- end
1406
- 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}")
1407
1366
  end
1408
- output.sub!(/%note/,note)
1409
- output.sub!(/%odnote/,note.gsub(/^\t*/,""))
1410
- output.sub!(/%chompnote/,note.gsub(/\n+/,' ').gsub(/(^\s*|\s*$)/,'').gsub(/\s+/,' '))
1411
- output.gsub!(/%hr(_under)?/) do |m|
1412
- 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 = ''
1413
1372
  `tput cols`.to_i.times do
1414
- o += $1.nil? ? "-" : "_"
1373
+ o += Regexp.last_match(1).nil? ? '-' : '_'
1415
1374
  end
1416
1375
  o
1417
1376
  end
1418
- output.gsub!(/%n/,"\n")
1419
- output.gsub!(/%t/,"\t")
1377
+ output.gsub!(/%n/, "\n")
1378
+ output.gsub!(/%t/, "\t")
1420
1379
 
1421
1380
  out += output + "\n"
1422
- }
1423
- out += tag_times("text", opt[:sort_tags]) if opt[:totals]
1381
+ end
1382
+ out += tag_times('text', opt[:sort_tags]) if opt[:totals]
1424
1383
  end
1425
- return out
1384
+ out
1426
1385
  end
1427
1386
 
1428
1387
  ##
@@ -1435,15 +1394,12 @@ EOTEMPLATE
1435
1394
  ## @param tags (Array) Tags to archive
1436
1395
  ## @param bool (String) Tag boolean combinator
1437
1396
  ##
1438
- def archive(section="Currently",count=5,destination=nil,tags=nil,bool=nil,export=nil)
1439
-
1397
+ def archive(section = 'Currently', count = 5, destination = nil, tags = nil, bool = nil, _export = nil)
1440
1398
  section = choose_section if section.nil? || section =~ /choose/i
1441
1399
  archive_all = section =~ /all/i # && !(tags.nil? || tags.empty?)
1442
1400
  section = guess_section(section) unless archive_all
1443
1401
 
1444
- if destination =~ /archive/i && !sections.include?("Archive")
1445
- add_section("Archive")
1446
- end
1402
+ add_section('Archive') if destination =~ /archive/i && !sections.include?('Archive')
1447
1403
 
1448
1404
  destination = guess_section(destination)
1449
1405
 
@@ -1451,16 +1407,16 @@ EOTEMPLATE
1451
1407
  if archive_all
1452
1408
  to_archive = sections.dup
1453
1409
  to_archive.delete(destination)
1454
- to_archive.each {|source,v|
1455
- do_archive(source, destination, { :count => count, :tags => tags, :bool => bool, :label => true })
1456
- }
1410
+ to_archive.each do |source, _v|
1411
+ do_archive(source, destination, { count: count, tags: tags, bool: bool, label: true })
1412
+ end
1457
1413
  else
1458
- 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 })
1459
1415
  end
1460
1416
 
1461
1417
  write(doing_file)
1462
1418
  else
1463
- raise "Either source or destination does not exist"
1419
+ raise 'Either source or destination does not exist'
1464
1420
  end
1465
1421
  end
1466
1422
 
@@ -1471,63 +1427,66 @@ EOTEMPLATE
1471
1427
  ## @param destination (String) The destination section
1472
1428
  ## @param opt (Hash) Additional Options
1473
1429
  ##
1474
- def do_archive(section, destination, opt={})
1430
+ def do_archive(section, destination, opt = {})
1475
1431
  count = opt[:count] || 5
1476
1432
  tags = opt[:tags] || []
1477
- bool = opt[:bool] || "AND"
1433
+ bool = opt[:bool] || 'AND'
1478
1434
  label = opt[:label] || false
1479
1435
 
1480
1436
  items = @content[section]['items']
1481
1437
  moved_items = []
1482
1438
 
1483
1439
  if tags && !tags.empty?
1484
- items.delete_if {|item|
1440
+ items.delete_if do |item|
1485
1441
  if bool =~ /(AND|ALL)/
1486
1442
  score = 0
1487
- tags.each {|tag|
1443
+ tags.each do |tag|
1488
1444
  score += 1 if item['title'] =~ /@#{tag}/i
1489
- }
1445
+ end
1490
1446
  res = score < tags.length
1491
1447
  moved_items.push(item) if res
1492
1448
  res
1493
1449
  elsif bool =~ /NONE/
1494
1450
  del = false
1495
- tags.each {|tag|
1451
+ tags.each do |tag|
1496
1452
  del = true if item['title'] =~ /@#{tag}/i
1497
- }
1453
+ end
1498
1454
  moved_items.push(item) if del
1499
1455
  del
1500
1456
  elsif bool =~ /(OR|ANY)/
1501
1457
  del = true
1502
- tags.each {|tag|
1458
+ tags.each do |tag|
1503
1459
  del = false if item['title'] =~ /@#{tag}/i
1504
- }
1460
+ end
1505
1461
  moved_items.push(item) if del
1506
1462
  del
1507
1463
  end
1508
- }
1509
- moved_items.each {|item|
1510
- if label
1511
- 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})")
1512
1469
  end
1513
- }
1470
+ end
1514
1471
  @content[section]['items'] = moved_items
1515
1472
  @content[destination]['items'] += items
1516
1473
  @results.push("Archived #{items.length} items from #{section} to #{destination}")
1517
1474
  else
1518
1475
 
1519
1476
  return if items.length < count
1520
- if count == 0
1521
- @content[section]['items'] = []
1522
- else
1523
- @content[section]['items'] = items[0..count-1]
1524
- end
1525
1477
 
1526
- items.each{|item|
1527
- if label
1528
- 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})")
1529
1488
  end
1530
- }
1489
+ end
1531
1490
 
1532
1491
  @content[destination]['items'] += items[count..-1]
1533
1492
  @results.push("Archived #{items.length - count} items from #{section} to #{destination}")
@@ -1583,7 +1542,6 @@ EOTEMPLATE
1583
1542
  color
1584
1543
  end
1585
1544
 
1586
-
1587
1545
  ##
1588
1546
  ## @brief Show all entries from the current day
1589
1547
  ##
@@ -1591,12 +1549,13 @@ EOTEMPLATE
1591
1549
  ## @param output (String) output format
1592
1550
  ## @param opt (Hash) Options
1593
1551
  ##
1594
- def today(times=true,output=nil,opt={})
1552
+ def today(times = true, output = nil, opt = {})
1595
1553
  opt[:totals] ||= false
1596
1554
  opt[:sort_tags] ||= false
1597
1555
 
1598
1556
  cfg = @config['templates']['today']
1599
- 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] })
1600
1559
  end
1601
1560
 
1602
1561
  ##
@@ -1608,16 +1567,15 @@ EOTEMPLATE
1608
1567
  ## @param output (String) Output format
1609
1568
  ## @param opt (Hash) Additional Options
1610
1569
  ##
1611
- def list_date(dates,section,times=nil,output=nil,opt={})
1570
+ def list_date(dates, section, times = nil, output = nil, opt = {})
1612
1571
  opt[:totals] ||= false
1613
1572
  opt[:sort_tags] ||= false
1614
1573
  section = guess_section(section)
1615
1574
  # :date_filter expects an array with start and end date
1616
- if dates.class == String
1617
- dates = [dates, dates]
1618
- end
1575
+ dates = [dates, dates] if dates.instance_of?(String)
1619
1576
 
1620
- 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] })
1621
1579
  end
1622
1580
 
1623
1581
  ##
@@ -1628,11 +1586,12 @@ EOTEMPLATE
1628
1586
  ## @param output (String) Output format
1629
1587
  ## @param opt (Hash) Additional Options
1630
1588
  ##
1631
- def yesterday(section,times=nil,output=nil,opt={})
1589
+ def yesterday(section, times = nil, output = nil, opt = {})
1632
1590
  opt[:totals] ||= false
1633
1591
  opt[:sort_tags] ||= false
1634
1592
  section = guess_section(section)
1635
- 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] })
1636
1595
  end
1637
1596
 
1638
1597
  ##
@@ -1642,7 +1601,7 @@ EOTEMPLATE
1642
1601
  ## @param section (String) The section to show from, default Currently
1643
1602
  ## @param opt (Hash) Additional Options
1644
1603
  ##
1645
- def recent(count=10,section=nil,opt={})
1604
+ def recent(count = 10, section = nil, opt = {})
1646
1605
  times = opt[:t] || true
1647
1606
  opt[:totals] ||= false
1648
1607
  opt[:sort_tags] ||= false
@@ -1650,7 +1609,8 @@ EOTEMPLATE
1650
1609
  cfg = @config['templates']['recent']
1651
1610
  section ||= @current_section
1652
1611
  section = guess_section(section)
1653
- 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] })
1654
1614
  end
1655
1615
 
1656
1616
  ##
@@ -1659,11 +1619,12 @@ EOTEMPLATE
1659
1619
  ## @param times (Bool) Show times
1660
1620
  ## @param section (String) Section to pull from, default Currently
1661
1621
  ##
1662
- def last(times=true,section=nil)
1622
+ def last(times = true, section = nil)
1663
1623
  section ||= @current_section
1664
1624
  section = guess_section(section)
1665
1625
  cfg = @config['templates']['last']
1666
- 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 })
1667
1628
  end
1668
1629
 
1669
1630
  ##
@@ -1671,22 +1632,22 @@ EOTEMPLATE
1671
1632
  ##
1672
1633
  ## @param format (String) return format (html, json, or text)
1673
1634
  ##
1674
- def tag_times(format="text", sort_by_name = false)
1675
- return "" if @timers.empty?
1635
+ def tag_times(format = 'text', sort_by_name = false)
1636
+ return '' if @timers.empty?
1676
1637
 
1677
- 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
1678
1639
 
1679
- total = @timers.delete("All")
1640
+ total = @timers.delete('All')
1680
1641
 
1681
- tags_data = @timers.delete_if { |k,v| v == 0}
1682
- if sort_by_name
1683
- sorted_tags_data = tags_data.sort_by{|k,v| k }.reverse
1684
- else
1685
- sorted_tags_data = tags_data.sort_by{|k,v| v }
1686
- 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
1687
1648
 
1688
- if format == "html"
1689
- output =<<EOS
1649
+ if format == 'html'
1650
+ output = <<EOS
1690
1651
  <table>
1691
1652
  <caption id="tagtotals">Tag Totals</caption>
1692
1653
  <colgroup>
@@ -1701,10 +1662,12 @@ EOTEMPLATE
1701
1662
  </thead>
1702
1663
  <tbody>
1703
1664
  EOS
1704
- sorted_tags_data.reverse.each {|k,v|
1705
- 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
1706
- }
1707
- 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
1708
1671
  <tr>
1709
1672
  <td style="text-align:left;" colspan="2"></td>
1710
1673
  </tr>
@@ -1712,34 +1675,34 @@ EOS
1712
1675
  <tfoot>
1713
1676
  <tr>
1714
1677
  <td style="text-align:left;"><strong>Total</strong></td>
1715
- <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>
1716
1679
  </tr>
1717
1680
  </tfoot>
1718
1681
  </table>
1719
1682
  EOS
1720
1683
  output + tail
1721
- elsif format == "json"
1684
+ elsif format == 'json'
1722
1685
  output = []
1723
- sorted_tags_data.reverse.each {|k,v|
1686
+ sorted_tags_data.reverse.each do |k, v|
1724
1687
  output << {
1725
1688
  'tag' => k,
1726
1689
  'seconds' => v,
1727
- 'formatted' => "%02d:%02d:%02d" % fmt_time(v)
1690
+ 'formatted' => '%02d:%02d:%02d' % fmt_time(v)
1728
1691
  }
1729
- }
1692
+ end
1730
1693
  output
1731
1694
  else
1732
1695
  output = []
1733
- sorted_tags_data.reverse.each {|k,v|
1734
- spacer = ""
1696
+ sorted_tags_data.reverse.each do |k, v|
1697
+ spacer = ''
1735
1698
  (max - k.length).times do
1736
- spacer += " "
1699
+ spacer += ' '
1737
1700
  end
1738
- output.push("#{k}:#{spacer}#{"%02d:%02d:%02d" % fmt_time(v)}")
1739
- }
1701
+ output.push("#{k}:#{spacer}#{'%02d:%02d:%02d' % fmt_time(v)}")
1702
+ end
1740
1703
 
1741
- output = output.empty? ? "" : "\n--- Tag Totals ---\n" + output.join("\n")
1742
- 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"
1743
1706
  output
1744
1707
  end
1745
1708
  end
@@ -1753,53 +1716,54 @@ EOS
1753
1716
  def autotag(text)
1754
1717
  return unless text
1755
1718
  return text unless @auto_tag
1719
+
1756
1720
  current_tags = text.scan(/@\w+/)
1757
1721
  whitelisted = []
1758
- @config['autotag']['whitelist'].each {|tag|
1722
+ @config['autotag']['whitelist'].each do |tag|
1723
+ next if text =~ /@#{tag}\b/i
1724
+
1759
1725
  text.sub!(/(?<!@)(#{tag.strip})\b/i) do |m|
1760
1726
  m.downcase! if tag =~ /[a-z]/
1761
1727
  whitelisted.push("@#{m}")
1762
1728
  "@#{m}"
1763
- end unless text =~ /@#{tag}\b/i
1764
- }
1729
+ end
1730
+ end
1765
1731
  tail_tags = []
1766
- @config['autotag']['synonyms'].each {|tag, v|
1767
- v.each {|word|
1768
- if text =~ /\b#{word}\b/i
1769
- unless current_tags.include?("@#{tag}") || whitelisted.include?("@#{tag}")
1770
- tail_tags.push(tag)
1771
- end
1772
- end
1773
- }
1774
- }
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
1775
1739
  if @config['autotag'].key? 'transform'
1776
- @config['autotag']['transform'].each {|tag|
1777
- if tag =~ /\S+:\S+/
1778
- rx, r = tag.split(/:/)
1779
- r.gsub!(/\$/,'\\')
1780
- rx.sub!(/^@/,'')
1781
- regex = Regexp.new('@' + rx + '\b')
1782
-
1783
- matches = text.scan(regex)
1784
- matches.each {|m|
1785
- new_tag = r
1786
- if m.kind_of?(Array)
1787
- index = 1
1788
- m.each {|v|
1789
- new_tag = new_tag.gsub('\\' + index.to_s, v)
1790
- index = index + 1
1791
- }
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
1792
1758
  end
1793
- tail_tags.push(new_tag)
1794
- } if matches
1759
+ end
1760
+ tail_tags.push(new_tag)
1795
1761
  end
1796
- }
1797
- end
1798
- if whitelisted.length > 0
1799
- @results.push("Whitelisted tags: #{whitelisted.join(', ')}")
1762
+ end
1800
1763
  end
1764
+ @results.push("Whitelisted tags: #{whitelisted.join(', ')}") if whitelisted.length > 0
1801
1765
  if tail_tags.length > 0
1802
- tags = tail_tags.uniq.map {|t| '@'+t }.join(' ')
1766
+ tags = tail_tags.uniq.map { |t| '@' + t }.join(' ')
1803
1767
  @results.push("Synonym tags: #{tags}")
1804
1768
  text + ' ' + tags
1805
1769
  else
@@ -1815,43 +1779,43 @@ EOS
1815
1779
  ## @param item (Hash) The entry
1816
1780
  ## @param formatted (Bool) Return human readable time (default seconds)
1817
1781
  ##
1818
- def get_interval(item, formatted=true)
1782
+ def get_interval(item, formatted = true)
1819
1783
  done = nil
1820
1784
  start = nil
1821
1785
 
1822
1786
  if @interval_cache.keys.include? item['title']
1823
1787
  seconds = @interval_cache[item['title']]
1824
- return seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
1788
+ return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
1825
1789
  end
1826
1790
 
1827
1791
  if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1828
- done = Time.parse($1)
1792
+ done = Time.parse(Regexp.last_match(1))
1829
1793
  else
1830
1794
  return nil
1831
1795
  end
1832
1796
 
1833
- if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
1834
- start = Time.parse($1)
1835
- else
1836
- start = item['date']
1837
- 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
1838
1802
 
1839
1803
  seconds = (done - start).to_i
1840
1804
 
1841
- item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each {|m|
1842
- 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
1843
1807
  if @timers.has_key?(k)
1844
1808
  @timers[k] += seconds
1845
1809
  else
1846
1810
  @timers[k] = seconds
1847
1811
  end
1848
- }
1812
+ end
1849
1813
 
1850
1814
  @interval_cache[item['title']] = seconds
1851
1815
 
1852
1816
  return seconds unless formatted
1853
1817
 
1854
- seconds > 0 ? "%02d:%02d:%02d" % fmt_time(seconds) : false
1818
+ seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
1855
1819
  end
1856
1820
 
1857
1821
  ##
@@ -1860,19 +1824,19 @@ EOS
1860
1824
  ## @param seconds The seconds
1861
1825
  ##
1862
1826
  def fmt_time(seconds)
1863
- if seconds.nil?
1864
- return [0, 0, 0]
1865
- end
1827
+ return [0, 0, 0] if seconds.nil?
1828
+
1866
1829
  if seconds =~ /(\d+):(\d+):(\d+)/
1867
- h, m, s = [$1, $2, $3]
1830
+ h = Regexp.last_match(1)
1831
+ m = Regexp.last_match(2)
1832
+ s = Regexp.last_match(3)
1868
1833
  seconds = (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
1869
1834
  end
1870
- minutes = (seconds / 60).to_i
1835
+ minutes = (seconds / 60).to_i
1871
1836
  hours = (minutes / 60).to_i
1872
1837
  days = (hours / 24).to_i
1873
1838
  hours = (hours % 24).to_i
1874
1839
  minutes = (minutes % 60).to_i
1875
1840
  [days, hours, minutes]
1876
1841
  end
1877
-
1878
1842
  end