doing 1.0.45 → 1.0.50

Sign up to get free protection for your applications and to get access to all the features.
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