doing 2.1.19 → 2.1.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +9 -8
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +49 -0
  6. data/Gemfile.lock +11 -11
  7. data/README.md +1 -1
  8. data/Rakefile +10 -4
  9. data/bin/doing +123 -167
  10. data/docs/doc/Array.html +3 -3
  11. data/docs/doc/BooleanTermParser/Clause.html +3 -3
  12. data/docs/doc/BooleanTermParser/Operator.html +3 -3
  13. data/docs/doc/BooleanTermParser/Query.html +3 -3
  14. data/docs/doc/BooleanTermParser/QueryParser.html +3 -3
  15. data/docs/doc/BooleanTermParser/QueryTransformer.html +3 -3
  16. data/docs/doc/BooleanTermParser.html +3 -3
  17. data/docs/doc/Doing/Color.html +3 -3
  18. data/docs/doc/Doing/Completion.html +3 -3
  19. data/docs/doc/Doing/Configuration.html +4 -3
  20. data/docs/doc/Doing/Errors/DoingNoTraceError.html +3 -3
  21. data/docs/doc/Doing/Errors/DoingRuntimeError.html +3 -3
  22. data/docs/doc/Doing/Errors/DoingStandardError.html +3 -3
  23. data/docs/doc/Doing/Errors/EmptyInput.html +3 -3
  24. data/docs/doc/Doing/Errors/NoResults.html +3 -3
  25. data/docs/doc/Doing/Errors/PluginException.html +3 -3
  26. data/docs/doc/Doing/Errors/UserCancelled.html +3 -3
  27. data/docs/doc/Doing/Errors/WrongCommand.html +3 -3
  28. data/docs/doc/Doing/Errors.html +3 -3
  29. data/docs/doc/Doing/Hooks.html +3 -3
  30. data/docs/doc/Doing/Item.html +3 -3
  31. data/docs/doc/Doing/Items.html +3 -3
  32. data/docs/doc/Doing/LogAdapter.html +3 -3
  33. data/docs/doc/Doing/Note.html +3 -3
  34. data/docs/doc/Doing/Pager.html +3 -3
  35. data/docs/doc/Doing/Plugins.html +3 -3
  36. data/docs/doc/Doing/Prompt.html +3 -3
  37. data/docs/doc/Doing/Section.html +3 -3
  38. data/docs/doc/Doing/TemplateString.html +4 -4
  39. data/docs/doc/Doing/Util/Backup.html +3 -3
  40. data/docs/doc/Doing/Util.html +3 -3
  41. data/docs/doc/Doing/WWID.html +66 -8
  42. data/docs/doc/Doing.html +4 -4
  43. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +3 -3
  44. data/docs/doc/GLI/Commands.html +3 -3
  45. data/docs/doc/GLI.html +3 -3
  46. data/docs/doc/Hash.html +3 -3
  47. data/docs/doc/Numeric.html +3 -3
  48. data/docs/doc/PhraseParser/Operator.html +3 -3
  49. data/docs/doc/PhraseParser/PhraseClause.html +3 -3
  50. data/docs/doc/PhraseParser/Query.html +3 -3
  51. data/docs/doc/PhraseParser/QueryParser.html +3 -3
  52. data/docs/doc/PhraseParser/QueryTransformer.html +3 -3
  53. data/docs/doc/PhraseParser/TermClause.html +3 -3
  54. data/docs/doc/PhraseParser.html +3 -3
  55. data/docs/doc/Status.html +3 -3
  56. data/docs/doc/String.html +94 -17
  57. data/docs/doc/Symbol.html +3 -3
  58. data/docs/doc/Time.html +3 -3
  59. data/docs/doc/_index.html +4 -4
  60. data/docs/doc/file.README.html +4 -4
  61. data/docs/doc/frames.html +1 -1
  62. data/docs/doc/index.html +4 -4
  63. data/docs/doc/method_list.html +300 -276
  64. data/docs/doc/top-level-namespace.html +94 -3
  65. data/doing.gemspec +1 -1
  66. data/doing.rdoc +15 -6
  67. data/lib/completion/_doing.zsh +5 -5
  68. data/lib/completion/doing.bash +8 -8
  69. data/lib/completion/doing.fish +93 -15
  70. data/lib/doing/completion/fish_completion.rb +80 -11
  71. data/lib/doing/configuration.rb +1 -0
  72. data/lib/doing/hash.rb +1 -1
  73. data/lib/doing/items.rb +3 -1
  74. data/lib/doing/pager.rb +1 -1
  75. data/lib/doing/plugins/export/dayone_export.rb +1 -1
  76. data/lib/doing/plugins/export/markdown_export.rb +1 -1
  77. data/lib/doing/string.rb +11 -0
  78. data/lib/doing/string_chronify.rb +55 -17
  79. data/lib/doing/types.rb +19 -0
  80. data/lib/doing/version.rb +1 -1
  81. data/lib/doing/wwid.rb +69 -42
  82. data/lib/examples/commands/later.rb +32 -0
  83. data/lib/helpers/threaded_tests.rb +250 -0
  84. metadata +9 -6
data/bin/doing CHANGED
@@ -4,6 +4,7 @@
4
4
  $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
5
5
  require 'gli'
6
6
  require 'doing'
7
+ require 'doing/types'
7
8
  require 'tempfile'
8
9
  require 'pp'
9
10
 
@@ -25,12 +26,13 @@ version Doing::VERSION
25
26
  hide_commands_without_desc true
26
27
  autocomplete_commands true
27
28
 
28
- REGEX_BOOL = /^(?:and|all|any|or|not|none|p(?:at(?:tern)?)?)$/i
29
- REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i
30
- REGEX_VALUE_QUERY = /^(?:!)?@?(?:\S+) +(?:!?[<>=][=*]?|[$*^]=) +(?:.*?)$/
31
-
32
29
  InvalidExportType = Class.new(RuntimeError)
33
30
  MissingConfigFile = Class.new(RuntimeError)
31
+ TagArray = Class.new(Array)
32
+ DateBeginString = Class.new(DateTime)
33
+ DateEndString = Class.new(DateTime)
34
+ DateRangeString = Class.new(Array)
35
+ DateIntervalString = Class.new(DateTime)
34
36
 
35
37
  colors = Doing::Color
36
38
  wwid = Doing::WWID.new
@@ -70,7 +72,42 @@ if settings.dig('plugins', 'command_path')
70
72
  commands_from File.expand_path(settings.dig('plugins', 'command_path'))
71
73
  end
72
74
 
73
- class TagArray < Array; end
75
+ accept DateBeginString do |value|
76
+ if value =~ REGEX_TIME
77
+ res = value
78
+ else
79
+ res = value.chronify(guess: :begin, future: false)
80
+ end
81
+ raise InvalidTimeExpression, 'Invalid start date' unless res
82
+
83
+ res
84
+ end
85
+
86
+ accept DateEndString do |value|
87
+ if value =~ REGEX_TIME
88
+ res = value
89
+ else
90
+ res = value.chronify(guess: :end, future: false)
91
+ end
92
+ raise InvalidTimeExpression, 'Invalid end date' unless res
93
+
94
+ res
95
+ end
96
+
97
+ accept DateRangeString do |value|
98
+ start, finish = value.split_date_range
99
+ raise InvalidTimeExpression, 'Invalid range' unless start
100
+
101
+ finish ||= Time.now
102
+ [start, finish]
103
+ end
104
+
105
+ accept DateIntervalString do |value|
106
+ res = value.chronify_qty
107
+ raise InvalidTimeExpression, 'Invalid time quantity' unless res
108
+
109
+ res
110
+ end
74
111
 
75
112
  accept TagArray do |value|
76
113
  value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '')}.map(&:strip)
@@ -143,6 +180,10 @@ command %i[again resume] do |c|
143
180
  c.arg_name 'SECTION_NAME'
144
181
  c.flag [:in]
145
182
 
183
+ c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
184
+ c.arg_name 'DATE_STRING'
185
+ c.flag %i[b back started], type: DateBeginString
186
+
146
187
  c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
147
188
  c.arg_name 'TAG'
148
189
  c.flag [:tag], type: TagArray
@@ -198,6 +239,13 @@ command %i[again resume] do |c|
198
239
  options[:search] = search
199
240
  end
200
241
 
242
+ if options[:back]
243
+ date = options[:back]
244
+ raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
245
+ else
246
+ date = Time.now
247
+ end
248
+
201
249
  note = Doing::Note.new(options[:note])
202
250
  note.add(Doing::Prompt.read_lines(prompt: 'Add a note')) if options[:ask]
203
251
 
@@ -208,6 +256,7 @@ command %i[again resume] do |c|
208
256
  opts[:tag] = tags
209
257
  opts[:tag_bool] = options[:bool].normalize_bool
210
258
  opts[:interactive] = options[:interactive]
259
+ opts[:date] = date
211
260
 
212
261
  wwid.repeat_last(opts)
213
262
  end
@@ -340,24 +389,24 @@ command %i[done did] do |c|
340
389
  c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm).
341
390
  Used with --took, backdates start date)
342
391
  c.arg_name 'DATE_STRING'
343
- c.flag %i[at finished]
392
+ c.flag %i[at finished], type: DateEndString
344
393
 
345
394
  c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
346
395
  c.arg_name 'DATE_STRING'
347
- c.flag %i[b back started]
396
+ c.flag %i[b back started], type: DateBeginString
348
397
 
349
398
  c.desc %(
350
399
  Start and end times as a date/time range `doing done --from "1am to 8am"`.
351
400
  Overrides other date flags.
352
401
  )
353
402
  c.arg_name 'TIME_RANGE'
354
- c.flag [:from]
403
+ c.flag [:from], must_match: REGEX_RANGE
355
404
 
356
405
  c.desc %(Set completion date to start date plus interval (XX[mhd] or HH:MM).
357
406
  If used without the --back option, the start date will be moved back to allow
358
407
  the completion date to be the current time.)
359
408
  c.arg_name 'INTERVAL'
360
- c.flag %i[t took for]
409
+ c.flag %i[t took for], type: DateIntervalString
361
410
 
362
411
  c.desc 'Section'
363
412
  c.arg_name 'NAME'
@@ -385,23 +434,27 @@ command %i[done did] do |c|
385
434
  donedate = nil
386
435
 
387
436
  if options[:from]
388
- date, finish_date = options[:from].split_date_range
437
+ options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
438
+ time =~ REGEX_TIME ? "today #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}" : time
439
+ end.join(' to ').split_date_range
440
+ date, finish_date = options[:from]
389
441
  finish_date ||= Time.now
390
442
  else
391
443
  if options[:took]
392
- took = options[:took].chronify_qty
444
+ took = options[:took]
393
445
  raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
394
446
  end
395
447
 
396
448
  if options[:back]
397
- date = options[:back].chronify(guess: :begin)
449
+ date = options[:back]
398
450
  raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
399
451
  else
400
452
  date = options[:took] ? Time.now - took : Time.now
401
453
  end
402
454
 
403
455
  if options[:at]
404
- finish_date = options[:at].chronify(guess: :begin)
456
+ finish_date = options[:at]
457
+ finish_date = finish_date.chronify(guess: :begin) if finish_date.is_a? String
405
458
  raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
406
459
 
407
460
  if options[:took]
@@ -417,6 +470,7 @@ command %i[done did] do |c|
417
470
  end
418
471
 
419
472
  if options[:date]
473
+ date = date.chronify(guess: :begin, context: :today) if date =~ REGEX_TIME
420
474
  finish_date = wwid.verify_duration(date, finish_date) unless options[:took] || options[:from]
421
475
 
422
476
  donedate = finish_date.strftime('%F %R')
@@ -528,7 +582,7 @@ command %i[done did] do |c|
528
582
  wwid.content.push(new_entry)
529
583
  Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
530
584
  wwid.write(wwid.doing_file)
531
- Doing.logger.info('Entry Added:', new_entry.title)
585
+ Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
532
586
  elsif $stdin.stat.size.positive?
533
587
  note = Doing::Note.new(options[:note])
534
588
  d, title, note = wwid.format_input($stdin.read.strip)
@@ -553,7 +607,7 @@ command %i[done did] do |c|
553
607
  Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
554
608
 
555
609
  wwid.write(wwid.doing_file)
556
- Doing.logger.info('Entry Added:', new_entry.title)
610
+ Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
557
611
  else
558
612
  raise EmptyInput, 'You must provide content when creating a new entry'
559
613
  end
@@ -574,15 +628,15 @@ command :finish do |c|
574
628
 
575
629
  c.desc 'Backdate completed date to date string [4pm|20m|2h|yesterday noon]'
576
630
  c.arg_name 'DATE_STRING'
577
- c.flag %i[b back]
631
+ c.flag %i[b back started], type: DateBeginString
578
632
 
579
633
  c.desc 'Set the completed date to the start date plus XX[hmd]'
580
634
  c.arg_name 'INTERVAL'
581
- c.flag %i[t took for]
635
+ c.flag %i[t took for], type: DateIntervalString
582
636
 
583
637
  c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
584
638
  c.arg_name 'DATE_STRING'
585
- c.flag [:at]
639
+ c.flag %i[at finished], type: DateEndString
586
640
 
587
641
  c.desc 'Finish the last X entries containing TAG.
588
642
  Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
@@ -639,7 +693,7 @@ command :finish do |c|
639
693
  options[:fuzzy] = false
640
694
  unless options[:auto]
641
695
  if options[:took]
642
- took = options[:took].chronify_qty
696
+ took = options[:took]
643
697
  raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
644
698
  end
645
699
 
@@ -648,12 +702,13 @@ command :finish do |c|
648
702
  raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
649
703
 
650
704
  if options[:at]
651
- finish_date = options[:at].chronify(guess: :begin)
705
+ finish_date = options[:at]
706
+ finish_date = finish_date.chronify(guess: :begin) if finish_date.is_a? String
652
707
  raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
653
708
 
654
709
  date = options[:took] ? finish_date - took : finish_date
655
710
  elsif options[:back]
656
- date = options[:back].chronify()
711
+ date = options[:back]
657
712
 
658
713
  raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
659
714
  else
@@ -661,8 +716,6 @@ command :finish do |c|
661
716
  end
662
717
  end
663
718
 
664
- options[:took] = options[:took].chronify_qty if options[:took]
665
-
666
719
  if options[:tag].nil?
667
720
  tags = []
668
721
  else
@@ -711,92 +764,6 @@ command :finish do |c|
711
764
  end
712
765
  end
713
766
 
714
- # @@later
715
- desc 'Add an item to the Later section'
716
- arg_name 'ENTRY'
717
- command :later do |c|
718
- c.example 'doing later "Something I\'ll think about tomorrow"', desc: 'Add an entry to the Later section'
719
- c.example 'doing later -e', desc: 'Open $EDITOR to create an entry and optional note'
720
-
721
- c.desc "Edit entry with #{Doing::Util.default_editor}"
722
- c.switch %i[e editor], negatable: false, default_value: false
723
-
724
- c.desc 'Backdate start time to date string [4pm|20m|2h|yesterday noon]'
725
- c.arg_name 'DATE_STRING'
726
- c.flag %i[b back]
727
-
728
- c.desc 'Note'
729
- c.arg_name 'TEXT'
730
- c.flag %i[n note]
731
-
732
- c.desc 'Prompt for note via multi-line input'
733
- c.switch %i[ask], negatable: false, default_value: false
734
-
735
- c.action do |_global_options, options, args|
736
- if options[:back]
737
- date = options[:back].chronify(guess: :begin)
738
- raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
739
- else
740
- date = Time.now
741
- end
742
-
743
- ask_note = options[:ask] && !options[:editor] && args.count.positive? ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
744
-
745
- if options[:editor]
746
- raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
747
-
748
- input = ''
749
- input += date.strftime('%F %R | ')
750
- input += args.empty? ? '' : args.join(' ')
751
- input += "\n#{options[:note]}" if options[:note]
752
- input += "\n#{ask_note}" unless ask_note.empty?
753
-
754
- input = wwid.fork_editor(input).strip
755
-
756
- d, title, note = wwid.format_input(input)
757
- raise EmptyInput, 'No content' if title.empty?
758
-
759
- note.add(options[:note]) if options[:note]
760
- if ask_note.empty? && options[:ask]
761
- ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
762
- note.add(ask_note) unless ask_note.empty?
763
- end
764
-
765
- date = d.nil? ? date : d
766
- wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
767
- wwid.write(wwid.doing_file)
768
- elsif !args.empty?
769
- d, title, note = wwid.format_input(args.join(' '))
770
- date = d.nil? ? date : d
771
- note.add(options[:note]) if options[:note]
772
- note.add(ask_note) unless ask_note.empty?
773
- wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
774
- wwid.write(wwid.doing_file)
775
- elsif $stdin.stat.size.positive?
776
- d, title, note = wwid.format_input($stdin.read)
777
- unless d.nil?
778
- Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
779
- date = d
780
- end
781
- note.add(options[:note]) if options[:note]
782
- note.add(ask_note) unless ask_note.empty?
783
- wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
784
- wwid.write(wwid.doing_file)
785
- else
786
- title = Doing::Prompt.read_line(prompt: 'Entry content')
787
- raise EmptyInput, 'You must provide content when creating a new entry' if title.strip.empty?
788
-
789
- note = Doing::Note.new
790
- res = Doing::Prompt.yn('Add a note', default_response: false)
791
- ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
792
- note.add(ask_note)
793
-
794
- wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
795
- wwid.write(wwid.doing_file)
796
- end
797
- end
798
- end
799
-
800
767
  # @@mark @@flag
801
768
  desc 'Mark last entry as flagged'
802
769
  command %i[mark flag] do |c|
@@ -949,7 +916,7 @@ command :meanwhile do |c|
949
916
 
950
917
  c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
951
918
  c.arg_name 'DATE_STRING'
952
- c.flag %i[b back]
919
+ c.flag %i[b back started], type: DateBeginString
953
920
 
954
921
  c.desc 'Note'
955
922
  c.arg_name 'TEXT'
@@ -960,7 +927,7 @@ command :meanwhile do |c|
960
927
 
961
928
  c.action do |_global_options, options, args|
962
929
  if options[:back]
963
- date = options[:back].chronify(guess: :begin)
930
+ date = options[:back]
964
931
 
965
932
  raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
966
933
  else
@@ -1169,7 +1136,7 @@ command %i[now next] do |c|
1169
1136
 
1170
1137
  c.desc 'Backdate start time [4pm|20m|2h|"yesterday noon"]'
1171
1138
  c.arg_name 'DATE_STRING'
1172
- c.flag %i[b back started]
1139
+ c.flag %i[b back started], type: DateBeginString
1173
1140
 
1174
1141
  c.desc 'Timed entry, marks last entry in section as @done'
1175
1142
  c.switch %i[f finish_last], negatable: false, default_value: false
@@ -1187,7 +1154,7 @@ command %i[now next] do |c|
1187
1154
 
1188
1155
  c.action do |_global_options, options, args|
1189
1156
  if options[:back]
1190
- date = options[:back].chronify(guess: :begin)
1157
+ date = options[:back]
1191
1158
 
1192
1159
  raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
1193
1160
  else
@@ -1211,7 +1178,7 @@ command %i[now next] do |c|
1211
1178
  input += "\n#{ask_note}" unless ask_note.empty?
1212
1179
  input = wwid.fork_editor(input).strip
1213
1180
 
1214
- date, title, note = wwid.format_input(input)
1181
+ d, title, note = wwid.format_input(input)
1215
1182
  raise EmptyInput, 'No content' if title.strip.empty?
1216
1183
 
1217
1184
  if ask_note.empty? && options[:ask]
@@ -1219,6 +1186,7 @@ command %i[now next] do |c|
1219
1186
  note.add(ask_note) unless ask_note.empty?
1220
1187
  end
1221
1188
 
1189
+ date = d.nil? ? date : d
1222
1190
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1223
1191
  wwid.write(wwid.doing_file)
1224
1192
  elsif args.length.positive?
@@ -1416,11 +1384,11 @@ command :select do |c|
1416
1384
 
1417
1385
  c.desc 'Select from entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
1418
1386
  c.arg_name 'DATE_STRING'
1419
- c.flag [:before]
1387
+ c.flag [:before], type: DateBeginString
1420
1388
 
1421
1389
  c.desc 'Select from entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
1422
1390
  c.arg_name 'DATE_STRING'
1423
- c.flag [:after]
1391
+ c.flag [:after], type: DateEndString
1424
1392
 
1425
1393
  c.desc %(
1426
1394
  Date range to show, or a single day to filter date on.
@@ -1431,7 +1399,7 @@ command :select do |c|
1431
1399
  by time of day.
1432
1400
  )
1433
1401
  c.arg_name 'DATE_OR_RANGE'
1434
- c.flag [:from]
1402
+ c.flag [:from], type: DateRangeString
1435
1403
 
1436
1404
  c.desc 'Force exact search string matching (case sensitive)'
1437
1405
  c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
@@ -1698,11 +1666,11 @@ command %i[grep search] do |c|
1698
1666
 
1699
1667
  c.desc 'Search entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
1700
1668
  c.arg_name 'DATE_STRING'
1701
- c.flag [:before]
1669
+ c.flag [:before], type: DateBeginString
1702
1670
 
1703
1671
  c.desc 'Search entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
1704
1672
  c.arg_name 'DATE_STRING'
1705
- c.flag [:after]
1673
+ c.flag [:after], type: DateEndString
1706
1674
 
1707
1675
  c.desc %(
1708
1676
  Date range to show, or a single day to filter date on.
@@ -1713,7 +1681,7 @@ command %i[grep search] do |c|
1713
1681
  by time of day.
1714
1682
  )
1715
1683
  c.arg_name 'DATE_OR_RANGE'
1716
- c.flag [:from]
1684
+ c.flag [:from], type: DateRangeString
1717
1685
 
1718
1686
  c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
1719
1687
  c.arg_name 'FORMAT'
@@ -2011,11 +1979,11 @@ command :show do |c|
2011
1979
 
2012
1980
  c.desc 'Show entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
2013
1981
  c.arg_name 'DATE_STRING'
2014
- c.flag [:before]
1982
+ c.flag [:before], type: DateBeginString
2015
1983
 
2016
1984
  c.desc 'Show entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
2017
1985
  c.arg_name 'DATE_STRING'
2018
- c.flag [:after]
1986
+ c.flag [:after], type: DateEndString
2019
1987
 
2020
1988
  c.desc %(
2021
1989
  Date range to show, or a single day to filter date on.
@@ -2027,7 +1995,7 @@ command :show do |c|
2027
1995
  )
2028
1996
 
2029
1997
  c.arg_name 'DATE_OR_RANGE'
2030
- c.flag [:from]
1998
+ c.flag [:from], type: DateRangeString
2031
1999
 
2032
2000
  c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
2033
2001
  c.arg_name 'QUERY'
@@ -2322,8 +2290,8 @@ command :today do |c|
2322
2290
  c.desc %(
2323
2291
  Time range to show `doing today --from "12pm to 4pm"`
2324
2292
  )
2325
- c.arg_name 'DATE_OR_RANGE'
2326
- c.flag [:from]
2293
+ c.arg_name 'TIME_RANGE'
2294
+ c.flag [:from], type: DateRangeString
2327
2295
 
2328
2296
  c.action do |_global_options, options, _args|
2329
2297
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
@@ -2336,7 +2304,7 @@ command :today do |c|
2336
2304
  end
2337
2305
  end
2338
2306
 
2339
- # @on
2307
+ # @@on
2340
2308
  desc 'List entries for a date'
2341
2309
  long_desc %(Date argument can be natural language. "thursday" would be interpreted as "last thursday,"
2342
2310
  and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates,
@@ -2375,16 +2343,11 @@ command :on do |c|
2375
2343
 
2376
2344
  raise MissingArgument, 'Missing date argument' if args.empty?
2377
2345
 
2378
- date_string = args.join(' ')
2379
-
2380
- if date_string =~ / (to|through|thru) /
2381
- dates = date_string.split(/ (to|through|thru) /)
2382
- start = dates[0].chronify(guess: :begin)
2383
- finish = dates[2].chronify(guess: :end)
2384
- else
2385
- start = date_string.chronify(guess: :begin)
2386
- finish = false
2346
+ date_string = args.join(' ').strip
2347
+ if date_string =~ /^tod(?:ay)?/i
2348
+ date_string = 'today to tomorrow 12am'
2387
2349
  end
2350
+ start, finish = date_string.split_date_range
2388
2351
 
2389
2352
  raise InvalidTimeExpression, 'Unrecognized date string' unless start
2390
2353
 
@@ -2536,11 +2499,11 @@ command :view do |c|
2536
2499
 
2537
2500
  c.desc 'View entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
2538
2501
  c.arg_name 'DATE_STRING'
2539
- c.flag [:before]
2502
+ c.flag [:before], type: DateBeginString
2540
2503
 
2541
2504
  c.desc 'View entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
2542
2505
  c.arg_name 'DATE_STRING'
2543
- c.flag [:after]
2506
+ c.flag [:after], type: DateEndString
2544
2507
 
2545
2508
  c.desc %(
2546
2509
  Date range to show, or a single day to filter date on.
@@ -2551,7 +2514,7 @@ command :view do |c|
2551
2514
  by time of day.
2552
2515
  )
2553
2516
  c.arg_name 'DATE_OR_RANGE'
2554
- c.flag [:from]
2517
+ c.flag [:from], type: DateRangeString
2555
2518
 
2556
2519
  c.desc 'Only show items with recorded time intervals (override view settings)'
2557
2520
  c.switch [:only_timed], default_value: false, negatable: false
@@ -2669,7 +2632,6 @@ command :view do |c|
2669
2632
 
2670
2633
  opts = options.dup
2671
2634
  opts[:age] = options[:age].normalize_age(:newest)
2672
- opts[:view_template] = title
2673
2635
  opts[:count] = count
2674
2636
  opts[:format] = date_format
2675
2637
  opts[:highlight] = options[:color]
@@ -2686,6 +2648,7 @@ command :view do |c|
2686
2648
  opts[:tags_color] = tags_color
2687
2649
  opts[:template] = template
2688
2650
  opts[:totals] = totals
2651
+ opts[:view_template] = title
2689
2652
 
2690
2653
  Doing::Pager.page wwid.list_section(opts)
2691
2654
  elsif title.instance_of?(FalseClass)
@@ -2735,11 +2698,9 @@ command :yesterday do |c|
2735
2698
  c.arg_name 'TIME_STRING'
2736
2699
  c.flag [:after]
2737
2700
 
2738
- c.desc %(
2739
- Time range to show, e.g. `doing yesterday --from "1am to 8am"`
2740
- )
2701
+ c.desc 'Time range to show, e.g. `doing yesterday --from "1am to 8am"`'
2741
2702
  c.arg_name 'TIME_RANGE'
2742
- c.flag [:from]
2703
+ c.flag [:from], must_match: REGEX_TIME_RANGE
2743
2704
 
2744
2705
  c.desc 'Tag sort direction (asc|desc)'
2745
2706
  c.arg_name 'DIRECTION'
@@ -2751,9 +2712,9 @@ command :yesterday do |c|
2751
2712
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
2752
2713
 
2753
2714
  if options[:from]
2754
- options[:from] = options[:from].split(/ (?:to|through|thru|(?:un)?til|-+) /).map do |time|
2715
+ options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
2755
2716
  "yesterday #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}"
2756
- end.join(' to ')
2717
+ end.join(' to ').split_date_range
2757
2718
  end
2758
2719
 
2759
2720
  opt = {
@@ -2991,8 +2952,7 @@ command :config do |c|
2991
2952
  value = options[:remove] ? nil : args.pop
2992
2953
  keypath = args.join('.')
2993
2954
  real_path = config.resolve_key_path(keypath, create: true)
2994
-
2995
- old_value = settings.dig(*real_path) || nil
2955
+ old_value = settings.dig(*real_path)
2996
2956
  old_type = old_value&.class.to_s || nil
2997
2957
 
2998
2958
  if old_value.is_a?(Hash) && !options[:remove]
@@ -3018,7 +2978,6 @@ command :config do |c|
3018
2978
  else
3019
2979
  current_value = cfg.dig(*real_path)
3020
2980
  cfg.deep_set(real_path, value.set_type(old_type))
3021
-
3022
2981
  $stderr.puts "#{' Key path:'.yellow} #{real_path.join('->').boldwhite}"
3023
2982
  $stderr.puts "#{'Inherited:'.yellow} #{(old_value ? old_value.to_s : 'empty').boldwhite}"
3024
2983
  $stderr.puts "#{' Current:'.yellow} #{ (current_value ? current_value.to_s : 'empty').boldwhite }"
@@ -3178,7 +3137,7 @@ command %i[archive move] do |c|
3178
3137
  c.desc 'Archive entries older than date
3179
3138
  (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
3180
3139
  c.arg_name 'DATE_STRING'
3181
- c.flag [:before]
3140
+ c.flag [:before], type: DateEndString
3182
3141
 
3183
3142
  c.action do |_global_options, options, args|
3184
3143
  options[:fuzzy] = false
@@ -3269,11 +3228,11 @@ command :import do |c|
3269
3228
  # TODO: Allow time range filtering
3270
3229
  c.desc 'Import entries older than date'
3271
3230
  c.arg_name 'DATE_STRING'
3272
- c.flag [:before]
3231
+ c.flag [:before], type: DateBeginString
3273
3232
 
3274
3233
  c.desc 'Import entries newer than date'
3275
3234
  c.arg_name 'DATE_STRING'
3276
- c.flag [:after]
3235
+ c.flag [:after], type: DateEndString
3277
3236
 
3278
3237
  c.desc %(
3279
3238
  Date range to import. Date range argument should be quoted. Date specifications can be natural language.
@@ -3281,7 +3240,7 @@ command :import do |c|
3281
3240
  Has no effect unless the import plugin has implemented date range filtering.
3282
3241
  )
3283
3242
  c.arg_name 'DATE_OR_RANGE'
3284
- c.flag %i[f from]
3243
+ c.flag %i[f from], type: DateRangeString
3285
3244
 
3286
3245
  c.desc 'Allow entries that overlap existing times'
3287
3246
  c.switch [:overlap], negatable: true
@@ -3299,24 +3258,19 @@ command :import do |c|
3299
3258
  end
3300
3259
 
3301
3260
  if options[:from]
3302
- date_string = options[:from]
3303
- if date_string =~ / (to|through|thru|(un)?til|-+) /
3304
- dates = date_string.split(/ (to|through|thru|(un)?til|-+) /)
3305
- start = dates[0].chronify(guess: :begin)
3306
- finish = dates[2].chronify(guess: :end)
3307
- else
3308
- start = date_string.chronify(guess: :begin)
3309
- finish = date_string.chronify(guess: :end)
3310
- end
3311
- raise InvalidTimeExpression, 'Unrecognized date string' unless start
3312
- dates = [start, finish]
3261
+ options[:date_filter] = options[:from]
3262
+
3263
+ raise InvalidTimeExpression, 'Unrecognized date string' unless options[:date_filter][0]
3264
+ elsif options[:before] || options[:after]
3265
+ options[:date_filter] = [nil, nil]
3266
+ options[:date_filter][1] = options[:before] || Time.now + (1 << 64)
3267
+ options[:date_filter][0] = options[:after] || Time.now - (1 << 64)
3313
3268
  end
3314
3269
 
3315
3270
  options[:case] = options[:case].normalize_case
3316
3271
 
3317
3272
  if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
3318
3273
  options[:no_overlap] = !options[:overlap]
3319
- options[:date_filter] = dates
3320
3274
  wwid.import(args, options)
3321
3275
  wwid.write(wwid.doing_file)
3322
3276
  else
@@ -3667,9 +3621,9 @@ command :commands_accepting do |c|
3667
3621
  end
3668
3622
 
3669
3623
  if o[:column]
3670
- puts cmds
3624
+ puts cmds.sort
3671
3625
  else
3672
- puts "Commands accepting --#{option}: #{cmds.join(', ')}"
3626
+ puts "Commands accepting --#{option}: #{cmds.sort.join(', ')}"
3673
3627
  end
3674
3628
  end
3675
3629
  end
@@ -3714,7 +3668,9 @@ pre do |global, _command, _options, _args|
3714
3668
  end
3715
3669
 
3716
3670
  on_error do |exception|
3717
- if exception.kind_of?(SystemExit)
3671
+ if exception.kind_of?(GLI::UnknownCommand)
3672
+ exit run(['now'].concat(ARGV))
3673
+ elsif exception.kind_of?(SystemExit)
3718
3674
  false
3719
3675
  else
3720
3676
  # Doing.logger.error('Fatal:', exception)
data/docs/doc/Array.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <title>
7
7
  Class: Array
8
8
 
9
- &mdash; Documentation by YARD 0.9.26
9
+ &mdash; Documentation by YARD 0.9.27
10
10
 
11
11
  </title>
12
12
 
@@ -654,9 +654,9 @@ minutes]</p>
654
654
  </div>
655
655
 
656
656
  <div id="footer">
657
- Generated on Tue Jan 18 08:53:02 2022 by
657
+ Generated on Thu Jan 20 12:10:33 2022 by
658
658
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
659
- 0.9.26 (ruby-3.0.1).
659
+ 0.9.27 (ruby-3.0.1).
660
660
  </div>
661
661
 
662
662
  </div>