doing 1.0.45 → 1.0.46

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