doing 1.0.42 → 1.0.47

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