doing 2.1.11 → 2.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.irbrc +1 -0
  3. data/.yardoc/checksums +15 -13
  4. data/.yardoc/object_types +0 -0
  5. data/.yardoc/objects/root.dat +0 -0
  6. data/CHANGELOG.md +44 -1
  7. data/Gemfile.lock +9 -2
  8. data/README.md +56 -19
  9. data/bin/doing +215 -76
  10. data/docs/doc/Array.html +117 -3
  11. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  12. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  13. data/docs/doc/BooleanTermParser/Query.html +1 -1
  14. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  15. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  16. data/docs/doc/BooleanTermParser.html +1 -1
  17. data/docs/doc/Doing/Color.html +1 -1
  18. data/docs/doc/Doing/Completion.html +1 -1
  19. data/docs/doc/Doing/Configuration.html +7 -4
  20. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  21. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  22. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  23. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  24. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  25. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  26. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  27. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  28. data/docs/doc/Doing/Errors.html +1 -1
  29. data/docs/doc/Doing/Hooks.html +1 -1
  30. data/docs/doc/Doing/Item.html +337 -14
  31. data/docs/doc/Doing/Items.html +66 -2
  32. data/docs/doc/Doing/LogAdapter.html +1 -1
  33. data/docs/doc/Doing/Note.html +2 -2
  34. data/docs/doc/Doing/Pager.html +1 -1
  35. data/docs/doc/Doing/Plugins.html +1 -1
  36. data/docs/doc/Doing/Prompt.html +35 -1
  37. data/docs/doc/Doing/Section.html +1 -1
  38. data/docs/doc/Doing/TemplateString.html +2 -2
  39. data/docs/doc/Doing/Util/Backup.html +84 -1
  40. data/docs/doc/Doing/Util.html +1 -1
  41. data/docs/doc/Doing/WWID.html +180 -35
  42. data/docs/doc/Doing.html +3 -3
  43. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  44. data/docs/doc/GLI/Commands.html +1 -1
  45. data/docs/doc/GLI.html +1 -1
  46. data/docs/doc/Hash.html +1 -1
  47. data/docs/doc/Numeric.html +279 -0
  48. data/docs/doc/PhraseParser/Operator.html +1 -1
  49. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  50. data/docs/doc/PhraseParser/Query.html +1 -1
  51. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  52. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  53. data/docs/doc/PhraseParser/TermClause.html +1 -1
  54. data/docs/doc/PhraseParser.html +1 -1
  55. data/docs/doc/Status.html +1 -1
  56. data/docs/doc/String.html +767 -115
  57. data/docs/doc/Symbol.html +1 -1
  58. data/docs/doc/Time.html +1 -1
  59. data/docs/doc/_index.html +14 -9
  60. data/docs/doc/class_list.html +1 -1
  61. data/docs/doc/file.README.html +41 -15
  62. data/docs/doc/index.html +41 -15
  63. data/docs/doc/method_list.html +407 -279
  64. data/docs/doc/top-level-namespace.html +2 -2
  65. data/docs/index.md +56 -19
  66. data/doing.gemspec +2 -0
  67. data/doing.rdoc +244 -45
  68. data/example_plugin.rb +2 -4
  69. data/lib/completion/_doing.zsh +31 -27
  70. data/lib/completion/doing.bash +50 -39
  71. data/lib/completion/doing.fish +35 -6
  72. data/lib/doing/array_chronify.rb +57 -0
  73. data/lib/doing/configuration.rb +4 -1
  74. data/lib/doing/item.rb +176 -0
  75. data/lib/doing/log_adapter.rb +1 -1
  76. data/lib/doing/numeric_chronify.rb +40 -0
  77. data/lib/doing/plugins/export/dayone_export.rb +1 -1
  78. data/lib/doing/plugins/export/json_export.rb +2 -2
  79. data/lib/doing/plugins/export/template_export.rb +47 -90
  80. data/lib/doing/plugins/import/calendar_import.rb +13 -1
  81. data/lib/doing/plugins/import/doing_import.rb +12 -1
  82. data/lib/doing/plugins/import/timing_import.rb +13 -1
  83. data/lib/doing/prompt.rb +13 -1
  84. data/lib/doing/string.rb +97 -33
  85. data/lib/doing/string_chronify.rb +83 -13
  86. data/lib/doing/time.rb +6 -6
  87. data/lib/doing/util_backup.rb +1 -1
  88. data/lib/doing/version.rb +1 -1
  89. data/lib/doing/wwid.rb +88 -93
  90. data/lib/doing.rb +31 -27
  91. data/lib/examples/plugins/say_export.rb +1 -4
  92. metadata +46 -2
data/bin/doing CHANGED
@@ -27,6 +27,7 @@ autocomplete_commands true
27
27
 
28
28
  REGEX_BOOL = /^(?:and|all|any|or|not|none|p(?:at(?:tern)?)?)$/i
29
29
  REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i
30
+ REGEX_VALUE_QUERY = /^(?:!)?@?(?:\S+) +(?:!?[<>=][=*]?|[$*^]=) +(?:.*?)$/
30
31
 
31
32
  InvalidExportType = Class.new(RuntimeError)
32
33
  MissingConfigFile = Class.new(RuntimeError)
@@ -69,6 +70,12 @@ if settings.dig('plugins', 'command_path')
69
70
  commands_from File.expand_path(settings.dig('plugins', 'command_path'))
70
71
  end
71
72
 
73
+ class TagArray < Array; end
74
+
75
+ accept TagArray do |value|
76
+ value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '')}.map(&:strip)
77
+ end
78
+
72
79
  program_desc 'A CLI for a What Was I Doing system'
73
80
  program_long_desc %(Doing uses a TaskPaper-like formatting to keep a plain text
74
81
  record of what you've been doing, complete with tag-based time tracking. The
@@ -110,7 +117,7 @@ switch %i[q quiet], default_value: false, negatable: false
110
117
  desc 'Verbose output'
111
118
  switch %i[debug], default_value: false, negatable: false
112
119
 
113
- desc 'Use a specific configuration file. Deprecated, set $DOING_CONFIG instead.'
120
+ desc 'Use a specific configuration file. Deprecated, set $DOING_CONFIG instead'
114
121
  flag [:config_file], default_value: config.config_file
115
122
 
116
123
  desc 'Specify a different doing_file'
@@ -120,7 +127,7 @@ flag %i[f doing_file]
120
127
 
121
128
  # @@again @@resume
122
129
  desc 'Repeat last entry as new entry'
123
- long_desc 'This command is designed to allow multiple time intervals to be created for an entry by duplicating it with a new start (and end, eventually) time.'
130
+ long_desc 'This command is designed to allow multiple time intervals to be created for an entry by duplicating it with a new start (and end, eventually) time'
124
131
  command %i[again resume] do |c|
125
132
  c.example 'doing resume', desc: 'Duplicate the most recent entry with a new start time, removing any @done tag'
126
133
  c.example 'doing again', desc: 'again is an alias for resume'
@@ -136,15 +143,19 @@ command %i[again resume] do |c|
136
143
  c.arg_name 'SECTION_NAME'
137
144
  c.flag [:in]
138
145
 
139
- c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?).'
146
+ c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
140
147
  c.arg_name 'TAG'
141
- c.flag [:tag]
148
+ c.flag [:tag], type: TagArray
142
149
 
143
150
  c.desc 'Repeat last entry matching search. Surround with
144
151
  slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
145
152
  c.arg_name 'QUERY'
146
153
  c.flag [:search]
147
154
 
155
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
156
+ c.arg_name 'QUERY'
157
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
158
+
148
159
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
149
160
  # c.switch [:fuzzy], default_value: false, negatable: false
150
161
 
@@ -158,23 +169,26 @@ command %i[again resume] do |c|
158
169
  c.arg_name 'TYPE'
159
170
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
160
171
 
161
- c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans.'
172
+ c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans'
162
173
  c.arg_name 'BOOLEAN'
163
174
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
164
175
 
165
176
  c.desc "Edit duplicated entry with #{Doing::Util.default_editor} before adding"
166
177
  c.switch %i[e editor], negatable: false, default_value: false
167
178
 
168
- c.desc 'Note'
179
+ c.desc 'Add a note'
169
180
  c.arg_name 'TEXT'
170
181
  c.flag %i[n note]
171
182
 
183
+ c.desc 'Prompt for note via multi-line input'
184
+ c.switch %i[ask], negatable: false, default_value: false
185
+
172
186
  c.desc 'Select item to resume from a menu of matching entries'
173
187
  c.switch %i[i interactive], negatable: false, default_value: false
174
188
 
175
189
  c.action do |_global_options, options, _args|
176
190
  options[:fuzzy] = false
177
- tags = options[:tag].nil? ? [] : options[:tag].to_tags
191
+ tags = options[:tag].nil? ? [] : options[:tag]
178
192
 
179
193
  options[:case] = options[:case].normalize_case
180
194
 
@@ -184,6 +198,11 @@ command %i[again resume] do |c|
184
198
  options[:search] = search
185
199
  end
186
200
 
201
+ note = Doing::Note.new(options[:note])
202
+ note.add(Doing::Prompt.request_lines(prompt: 'Add a note')) if options[:ask]
203
+
204
+ options[:note] = note
205
+
187
206
  opts = options.dup
188
207
 
189
208
  opts[:tag] = tags
@@ -196,7 +215,7 @@ end
196
215
 
197
216
  # @@cancel
198
217
  desc 'End last X entries with no time tracked'
199
- long_desc 'Adds @done tag without datestamp so no elapsed time is recorded. Alias for `doing finish --no-date`.'
218
+ long_desc 'Adds @done tag without datestamp so no elapsed time is recorded. Alias for `doing finish --no-date`'
200
219
  arg_name 'COUNT'
201
220
  command :cancel do |c|
202
221
  c.example 'doing cancel', desc: 'Cancel the last entry'
@@ -209,11 +228,11 @@ command :cancel do |c|
209
228
  c.arg_name 'NAME'
210
229
  c.flag %i[s section]
211
230
 
212
- c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2). Wildcards allowed (*, ?).'
231
+ c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2). Wildcards allowed (*, ?)'
213
232
  c.arg_name 'TAG'
214
- c.flag [:tag]
233
+ c.flag [:tag], type: TagArray
215
234
 
216
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
235
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
217
236
  c.arg_name 'BOOLEAN'
218
237
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
219
238
 
@@ -221,6 +240,10 @@ command :cancel do |c|
221
240
  c.arg_name 'QUERY'
222
241
  c.flag [:search]
223
242
 
243
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
244
+ c.arg_name 'QUERY'
245
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
246
+
224
247
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
225
248
  # c.switch [:fuzzy], default_value: false, negatable: false
226
249
 
@@ -251,7 +274,7 @@ command :cancel do |c|
251
274
  if options[:tag].nil?
252
275
  tags = []
253
276
  else
254
- tags = options[:tag].to_tags
277
+ tags = options[:tag]
255
278
  end
256
279
 
257
280
  raise InvalidArgument, 'Only one argument allowed' if args.length > 1
@@ -285,7 +308,8 @@ command :cancel do |c|
285
308
  tag: tags,
286
309
  tag_bool: options[:bool].normalize_bool,
287
310
  tags: ['done'],
288
- unfinished: options[:unfinished]
311
+ unfinished: options[:unfinished],
312
+ val: options[:val]
289
313
  }
290
314
 
291
315
  wwid.tag_last(opts)
@@ -293,7 +317,7 @@ command :cancel do |c|
293
317
  end
294
318
 
295
319
  # @@done @@did
296
- desc 'Add a completed item with @done(date). No argument finishes last entry.'
320
+ desc 'Add a completed item with @done(date). No argument finishes last entry'
297
321
  long_desc 'Use this command to add an entry after you\'ve already finished it. It will be immediately marked as @done.
298
322
  You can modify the start and end times of the entry using the --back, --took, and --at flags, making it an easy
299
323
  way to add entries in post and maintain accurate (albeit manual) time tracking.'
@@ -326,7 +350,7 @@ command %i[done did] do |c|
326
350
  If used without the --back option, the start date will be moved back to allow
327
351
  the completion date to be the current time.)
328
352
  c.arg_name 'INTERVAL'
329
- c.flag %i[t took]
353
+ c.flag %i[t took for]
330
354
 
331
355
  c.desc 'Section'
332
356
  c.arg_name 'NAME'
@@ -339,6 +363,9 @@ command %i[done did] do |c|
339
363
  c.arg_name 'TEXT'
340
364
  c.flag %i[n note]
341
365
 
366
+ c.desc 'Prompt for note via multi-line input'
367
+ c.switch %i[ask], negatable: false, default_value: false
368
+
342
369
  c.desc 'Finish last entry not already marked @done'
343
370
  c.switch %i[u unfinished], negatable: false, default_value: false
344
371
 
@@ -374,6 +401,8 @@ command %i[done did] do |c|
374
401
  end
375
402
 
376
403
  if options[:date]
404
+ finish_date = wwid.verify_duration(date, finish_date) unless options[:took]
405
+
377
406
  donedate = finish_date.strftime('%F %R')
378
407
  end
379
408
 
@@ -385,6 +414,7 @@ command %i[done did] do |c|
385
414
 
386
415
  note = Doing::Note.new
387
416
  note.add(options[:note]) if options[:note]
417
+ note.add(Doing::Prompt.request_lines(prompt: 'Add a note')) if options[:ask]
388
418
 
389
419
  if options[:editor]
390
420
  raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
@@ -439,7 +469,6 @@ command %i[done did] do |c|
439
469
  if options[:remove]
440
470
  wwid.tag_last({ tags: ['done'], count: 1, section: section, remove: true })
441
471
  else
442
- note = options[:note] ? Doing::Note.new(options[:note]) : nil
443
472
  opt = {
444
473
  archive: options[:archive],
445
474
  back: finish_date,
@@ -520,7 +549,7 @@ command :finish do |c|
520
549
 
521
550
  c.desc 'Set the completed date to the start date plus XX[hmd]'
522
551
  c.arg_name 'INTERVAL'
523
- c.flag %i[t took]
552
+ c.flag %i[t took for]
524
553
 
525
554
  c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
526
555
  c.arg_name 'DATE_STRING'
@@ -529,12 +558,16 @@ command :finish do |c|
529
558
  c.desc 'Finish the last X entries containing TAG.
530
559
  Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
531
560
  c.arg_name 'TAG'
532
- c.flag [:tag]
561
+ c.flag [:tag], type: TagArray
533
562
 
534
563
  c.desc 'Finish the last X entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
535
564
  c.arg_name 'QUERY'
536
565
  c.flag [:search]
537
566
 
567
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
568
+ c.arg_name 'QUERY'
569
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
570
+
538
571
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
539
572
  # c.switch [:fuzzy], default_value: false, negatable: false
540
573
 
@@ -548,7 +581,7 @@ command :finish do |c|
548
581
  c.arg_name 'TYPE'
549
582
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
550
583
 
551
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
584
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
552
585
  c.arg_name 'BOOLEAN'
553
586
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
554
587
 
@@ -604,7 +637,7 @@ command :finish do |c|
604
637
  if options[:tag].nil?
605
638
  tags = []
606
639
  else
607
- tags = options[:tag].to_tags
640
+ tags = options[:tag]
608
641
  end
609
642
 
610
643
  raise InvalidArgument, 'Only one argument allowed' if args.length > 1
@@ -641,7 +674,8 @@ command :finish do |c|
641
674
  tag_bool: options[:bool].normalize_bool,
642
675
  tags: ['done'],
643
676
  took: options[:took],
644
- unfinished: options[:unfinished]
677
+ unfinished: options[:unfinished],
678
+ val: options[:val]
645
679
  }
646
680
 
647
681
  wwid.tag_last(opts)
@@ -666,6 +700,9 @@ command :later do |c|
666
700
  c.arg_name 'TEXT'
667
701
  c.flag %i[n note]
668
702
 
703
+ c.desc 'Prompt for note via multi-line input'
704
+ c.switch %i[ask], negatable: false, default_value: false
705
+
669
706
  c.action do |_global_options, options, args|
670
707
  if options[:back]
671
708
  date = options[:back].chronify(guess: :begin)
@@ -674,11 +711,16 @@ command :later do |c|
674
711
  date = Time.now
675
712
  end
676
713
 
714
+ ask_note = options[:ask] ? Doing::Prompt.request_lines(prompt: 'Add a note') : ''
715
+
677
716
  if options[:editor] || (args.empty? && $stdin.stat.size.zero?)
678
717
  raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
679
718
 
680
719
  input += date.strftime('%F %R | ')
681
720
  input += args.empty? ? '' : args.join(' ')
721
+ input += "\n#{options[:note]}" if options[:note]
722
+ input += "\n#{ask_note}" unless ask_note.empty?
723
+
682
724
  input = wwid.fork_editor(input).strip
683
725
  raise EmptyInput, 'No content' unless input && !input.empty?
684
726
 
@@ -691,6 +733,7 @@ command :later do |c|
691
733
  d, title, note = wwid.format_input(args.join(' '))
692
734
  date = d.nil? ? date : d
693
735
  note.add(options[:note]) if options[:note]
736
+ note.add(ask_note) unless ask_note.empty?
694
737
  wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
695
738
  wwid.write(wwid.doing_file)
696
739
  elsif $stdin.stat.size.positive?
@@ -700,6 +743,7 @@ command :later do |c|
700
743
  date = d
701
744
  end
702
745
  note.add(options[:note]) if options[:note]
746
+ note.add(ask_note) unless ask_note.empty?
703
747
  wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
704
748
  wwid.write(wwid.doing_file)
705
749
  else
@@ -739,12 +783,16 @@ command %i[mark flag] do |c|
739
783
  c.desc 'Flag the last entry containing TAG.
740
784
  Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
741
785
  c.arg_name 'TAG'
742
- c.flag [:tag]
786
+ c.flag [:tag], type: TagArray
743
787
 
744
788
  c.desc 'Flag the last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
745
789
  c.arg_name 'QUERY'
746
790
  c.flag [:search]
747
791
 
792
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
793
+ c.arg_name 'QUERY'
794
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
795
+
748
796
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
749
797
  # c.switch [:fuzzy], default_value: false, negatable: false
750
798
 
@@ -758,7 +806,7 @@ command %i[mark flag] do |c|
758
806
  c.arg_name 'TYPE'
759
807
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
760
808
 
761
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
809
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
762
810
  c.arg_name 'BOOLEAN'
763
811
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
764
812
 
@@ -780,7 +828,7 @@ command %i[mark flag] do |c|
780
828
  if options[:tag].nil?
781
829
  search_tags = []
782
830
  else
783
- search_tags = options[:tag].to_tags
831
+ search_tags = options[:tag]
784
832
  end
785
833
 
786
834
  if options[:interactive]
@@ -862,6 +910,9 @@ command :meanwhile do |c|
862
910
  c.arg_name 'TEXT'
863
911
  c.flag %i[n note]
864
912
 
913
+ c.desc 'Prompt for note via multi-line input'
914
+ c.switch %i[ask], negatable: false, default_value: false
915
+
865
916
  c.action do |_global_options, options, args|
866
917
  if options[:back]
867
918
  date = options[:back].chronify(guess: :begin)
@@ -878,10 +929,15 @@ command :meanwhile do |c|
878
929
  end
879
930
  input = ''
880
931
 
932
+ ask_note = options[:ask] ? Doing::Prompt.request_lines(prompt: 'Add a note') : []
933
+
881
934
  if options[:editor]
882
935
  raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
883
936
  input += date.strftime('%F %R | ')
884
937
  input += args.join(' ') unless args.empty?
938
+ input += "\n#{options[:note]}" if options[:note]
939
+ input += "\n#{ask_note}" unless ask_note.empty?
940
+
885
941
  input = wwid.fork_editor(input).strip
886
942
  elsif !args.empty?
887
943
  input = args.join(' ')
@@ -900,10 +956,9 @@ command :meanwhile do |c|
900
956
  note = []
901
957
  end
902
958
 
903
- if options[:note]
904
- note.push(options[:note])
905
- elsif note.empty?
906
- note = nil
959
+ unless options[:editor]
960
+ note.add(options[:note]) if options[:note]
961
+ note.add(ask_note) unless ask_note.empty?
907
962
  end
908
963
 
909
964
  wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:archive], note: note })
@@ -937,14 +992,18 @@ command :note do |c|
937
992
  c.desc "Replace/Remove last entry's note (default append)"
938
993
  c.switch %i[r remove], negatable: false, default_value: false
939
994
 
940
- c.desc 'Add/remove note from last entry matching tag. Wildcards allowed (*, ?).'
995
+ c.desc 'Add/remove note from last entry matching tag. Wildcards allowed (*, ?)'
941
996
  c.arg_name 'TAG'
942
- c.flag [:tag]
997
+ c.flag [:tag], type: TagArray
943
998
 
944
999
  c.desc 'Add/remove note from last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
945
1000
  c.arg_name 'QUERY'
946
1001
  c.flag [:search]
947
1002
 
1003
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1004
+ c.arg_name 'QUERY'
1005
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1006
+
948
1007
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
949
1008
  # c.switch [:fuzzy], default_value: false, negatable: false
950
1009
 
@@ -958,13 +1017,16 @@ command :note do |c|
958
1017
  c.arg_name 'TYPE'
959
1018
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
960
1019
 
961
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
1020
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
962
1021
  c.arg_name 'BOOLEAN'
963
1022
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
964
1023
 
965
1024
  c.desc 'Select item for new note from a menu of matching entries'
966
1025
  c.switch %i[i interactive], negatable: false, default_value: false
967
1026
 
1027
+ c.desc 'Prompt for note via multi-line input'
1028
+ c.switch %i[ask], negatable: false, default_value: false
1029
+
968
1030
  c.action do |_global_options, options, args|
969
1031
  options[:fuzzy] = false
970
1032
  if options[:section]
@@ -991,8 +1053,9 @@ command :note do |c|
991
1053
 
992
1054
  last_note = last_entry.note || Doing::Note.new
993
1055
  new_note = Doing::Note.new
1056
+ ask_note = options[:ask] ? Doing::Prompt.request_lines(prompt: 'Add a note') : ''
994
1057
 
995
- if options[:editor] || (args.empty? && $stdin.stat.size.zero? && !options[:remove])
1058
+ if options[:editor] || (args.empty? && $stdin.stat.size.zero? && !options[:remove] && !options[:ask])
996
1059
  raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
997
1060
 
998
1061
  input = !args.empty? ? args.join(' ') : ''
@@ -1003,7 +1066,9 @@ command :note do |c|
1003
1066
  prev_input = last_entry.note || Doing::Note.new
1004
1067
  end
1005
1068
 
1069
+
1006
1070
  input = prev_input.add(input)
1071
+ input.add(ask_note) unless ask_note.empty?
1007
1072
 
1008
1073
  input = wwid.fork_editor(prev_input.strip_lines.join("\n"), message: nil).strip
1009
1074
  note = input
@@ -1014,9 +1079,12 @@ command :note do |c|
1014
1079
  elsif $stdin.stat.size.positive?
1015
1080
  new_note.add($stdin.read.strip)
1016
1081
  else
1017
- raise EmptyInput, 'You must provide content when adding a note' unless options[:remove]
1082
+ raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || !ask_note.empty?
1083
+
1018
1084
  end
1019
1085
 
1086
+ new_note.add(ask_note) unless ask_note.empty?
1087
+
1020
1088
  if last_note.equal?(new_note)
1021
1089
  Doing.logger.debug('Skipped:', 'No note change')
1022
1090
  else
@@ -1062,6 +1130,9 @@ command %i[now next] do |c|
1062
1130
  c.arg_name 'TEXT'
1063
1131
  c.flag %i[n note]
1064
1132
 
1133
+ c.desc 'Prompt for note via multi-line input'
1134
+ c.switch %i[ask], negatable: false, default_value: false
1135
+
1065
1136
  # c.desc "Edit entry with specified app"
1066
1137
  # c.arg_name 'editor_app'
1067
1138
  # # c.flag [:a, :app]
@@ -1081,23 +1152,28 @@ command %i[now next] do |c|
1081
1152
  options[:section] = settings['current_section']
1082
1153
  end
1083
1154
 
1155
+ ask_note = options[:ask] ? Doing::Prompt.request_lines(prompt: 'Add a note') : ''
1156
+
1084
1157
  if options[:editor] || (args.empty? && $stdin.stat.size.zero?)
1085
1158
  raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
1086
1159
 
1087
1160
  input = date.strftime('%F %R | ')
1088
1161
  input += args.join(' ') unless args.empty?
1162
+ input += "\n#{options[:note]}" if options[:note]
1163
+ input += "\n#{ask_note}" unless ask_note.empty?
1089
1164
  input = wwid.fork_editor(input).strip
1090
1165
 
1091
1166
  raise EmptyInput, 'No content' if input.empty?
1092
1167
 
1093
1168
  date, title, note = wwid.format_input(input)
1094
- note.add(options[:note]) if options[:note]
1169
+
1095
1170
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1096
1171
  wwid.write(wwid.doing_file)
1097
1172
  elsif args.length.positive?
1098
1173
  d, title, note = wwid.format_input(args.join(' '))
1099
1174
  date = d.nil? ? date : d
1100
1175
  note.add(options[:note]) if options[:note]
1176
+ note.add(ask_note) unless ask_note.empty?
1101
1177
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1102
1178
  wwid.write(wwid.doing_file)
1103
1179
  elsif $stdin.stat.size.positive?
@@ -1108,6 +1184,7 @@ command %i[now next] do |c|
1108
1184
  date = d
1109
1185
  end
1110
1186
  note.add(options[:note]) if options[:note]
1187
+ note.add(ask_note) unless ask_note.empty?
1111
1188
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1112
1189
  wwid.write(wwid.doing_file)
1113
1190
  else
@@ -1135,7 +1212,7 @@ command %i[reset begin] do |c|
1135
1212
  c.desc 'Resume entry (remove @done)'
1136
1213
  c.switch %i[r resume], default_value: true
1137
1214
 
1138
- c.desc 'Reset last entry matching tag. Wildcards allowed (*, ?).'
1215
+ c.desc 'Reset last entry matching tag. Wildcards allowed (*, ?)'
1139
1216
  c.arg_name 'TAG'
1140
1217
  c.flag [:tag]
1141
1218
 
@@ -1143,6 +1220,10 @@ command %i[reset begin] do |c|
1143
1220
  c.arg_name 'QUERY'
1144
1221
  c.flag [:search]
1145
1222
 
1223
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1224
+ c.arg_name 'QUERY'
1225
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1226
+
1146
1227
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
1147
1228
  # c.switch [:fuzzy], default_value: false, negatable: false
1148
1229
 
@@ -1156,7 +1237,7 @@ command %i[reset begin] do |c|
1156
1237
  c.arg_name 'TYPE'
1157
1238
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
1158
1239
 
1159
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
1240
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
1160
1241
  c.arg_name 'BOOLEAN'
1161
1242
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
1162
1243
 
@@ -1256,13 +1337,21 @@ command :select do |c|
1256
1337
 
1257
1338
  c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote, e.g. `--query "\'search"'
1258
1339
  c.arg_name 'QUERY'
1259
- c.flag %i[q query search]
1340
+ c.flag %i[q query]
1341
+
1342
+ c.desc 'Select from entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
1343
+ c.arg_name 'QUERY'
1344
+ c.flag [:search]
1345
+
1346
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1347
+ c.arg_name 'QUERY'
1348
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1260
1349
 
1261
- 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.'
1350
+ 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'
1262
1351
  c.arg_name 'DATE_STRING'
1263
1352
  c.flag [:before]
1264
1353
 
1265
- 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.'
1354
+ 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'
1266
1355
  c.arg_name 'DATE_STRING'
1267
1356
  c.flag [:after]
1268
1357
 
@@ -1287,7 +1376,7 @@ command :select do |c|
1287
1376
  c.arg_name 'TYPE'
1288
1377
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
1289
1378
 
1290
- c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches.'
1379
+ c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches'
1291
1380
  c.switch %i[menu], negatable: true, default_value: true
1292
1381
 
1293
1382
  c.desc 'Cancel selected items (add @done without timestamp)'
@@ -1305,7 +1394,7 @@ command :select do |c|
1305
1394
  c.desc 'Add flag to selected item(s)'
1306
1395
  c.switch %i[flag], negatable: false, default_value: false
1307
1396
 
1308
- c.desc 'Perform action without confirmation.'
1397
+ c.desc 'Perform action without confirmation'
1309
1398
  c.switch %i[force], negatable: false, default_value: false
1310
1399
 
1311
1400
  c.desc 'Save selected entries to file using --output format'
@@ -1365,6 +1454,10 @@ command :tag do |c|
1365
1454
  c.arg_name 'ORIG_TAG'
1366
1455
  c.flag %i[rename]
1367
1456
 
1457
+ c.desc 'Include a value, e.g. @tag(value)'
1458
+ c.arg_name 'VALUE'
1459
+ c.flag %i[v value]
1460
+
1368
1461
  c.desc 'Don\'t ask permission to tag all entries when count is 0'
1369
1462
  c.switch %i[force], negatable: false, default_value: false
1370
1463
 
@@ -1386,12 +1479,16 @@ command :tag do |c|
1386
1479
  c.desc 'Tag the last X entries containing TAG.
1387
1480
  Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
1388
1481
  c.arg_name 'TAG'
1389
- c.flag [:tag]
1482
+ c.flag [:tag], type: TagArray
1390
1483
 
1391
1484
  c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
1392
1485
  c.arg_name 'QUERY'
1393
1486
  c.flag [:search]
1394
1487
 
1488
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1489
+ c.arg_name 'QUERY'
1490
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1491
+
1395
1492
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
1396
1493
  # c.switch [:fuzzy], default_value: false, negatable: false
1397
1494
 
@@ -1405,7 +1502,7 @@ command :tag do |c|
1405
1502
  c.arg_name 'TYPE'
1406
1503
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
1407
1504
 
1408
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
1505
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
1409
1506
  c.arg_name 'BOOLEAN'
1410
1507
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
1411
1508
 
@@ -1428,7 +1525,7 @@ command :tag do |c|
1428
1525
  if options[:tag].nil?
1429
1526
  search_tags = []
1430
1527
  else
1431
- search_tags = options[:tag].to_tags
1528
+ search_tags = options[:tag]
1432
1529
  end
1433
1530
 
1434
1531
  if options[:autotag]
@@ -1528,11 +1625,11 @@ command %i[grep search] do |c|
1528
1625
  c.arg_name 'NAME'
1529
1626
  c.flag %i[s section], default_value: 'All'
1530
1627
 
1531
- 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.'
1628
+ 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'
1532
1629
  c.arg_name 'DATE_STRING'
1533
1630
  c.flag [:before]
1534
1631
 
1535
- 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.'
1632
+ 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'
1536
1633
  c.arg_name 'DATE_STRING'
1537
1634
  c.flag [:after]
1538
1635
 
@@ -1591,6 +1688,13 @@ command %i[grep search] do |c|
1591
1688
  c.desc 'Display an interactive menu of results to perform further operations'
1592
1689
  c.switch %i[i interactive], default_value: false, negatable: false
1593
1690
 
1691
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1692
+ c.arg_name 'QUERY'
1693
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1694
+
1695
+ c.desc 'Combine multiple tags or value queries using AND, OR, or NOT'
1696
+ c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
1697
+
1594
1698
  c.action do |_global_options, options, args|
1595
1699
  options[:fuzzy] = false
1596
1700
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
@@ -1601,6 +1705,7 @@ command %i[grep search] do |c|
1601
1705
  section = wwid.guess_section(options[:section]) if options[:section]
1602
1706
 
1603
1707
  options[:case] = options[:case].normalize_case
1708
+ options[:bool] = options[:bool].normalize_bool
1604
1709
 
1605
1710
  search = args.join(' ')
1606
1711
  search.sub!(/^'?/, "'") if options[:exact]
@@ -1639,11 +1744,11 @@ command :last do |c|
1639
1744
  c.desc "Delete the last entry"
1640
1745
  c.switch %i[d delete], negatable: false, default_value: false
1641
1746
 
1642
- c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?).'
1747
+ c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?)'
1643
1748
  c.arg_name 'TAG'
1644
- c.flag [:tag]
1749
+ c.flag [:tag], type: TagArray
1645
1750
 
1646
- c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
1751
+ c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
1647
1752
  c.arg_name 'BOOLEAN'
1648
1753
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
1649
1754
 
@@ -1651,6 +1756,10 @@ command :last do |c|
1651
1756
  c.arg_name 'QUERY'
1652
1757
  c.flag [:search]
1653
1758
 
1759
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1760
+ c.arg_name 'QUERY'
1761
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1762
+
1654
1763
  c.desc 'Show elapsed time if entry is not tagged @done'
1655
1764
  c.switch [:duration]
1656
1765
 
@@ -1674,7 +1783,7 @@ command :last do |c|
1674
1783
  if options[:tag].nil?
1675
1784
  options[:tag] = []
1676
1785
  else
1677
- options[:tag] = options[:tag].to_tags
1786
+ options[:tag] = options[:tag]
1678
1787
  options[:bool] = options[:bool].normalize_bool
1679
1788
  end
1680
1789
 
@@ -1685,12 +1794,14 @@ command :last do |c|
1685
1794
  if options[:editor]
1686
1795
  wwid.edit_last(section: options[:section],
1687
1796
  options: {
1688
- search: search,
1797
+ search: options[:search],
1689
1798
  fuzzy: options[:fuzzy],
1690
1799
  case: options[:case],
1691
- tag: tags,
1800
+ tag: options[:tag],
1692
1801
  tag_bool: options[:bool],
1693
- not: options[:not]
1802
+ not: options[:not],
1803
+ val: options[:val],
1804
+ bool: options[:bool]
1694
1805
  })
1695
1806
  else
1696
1807
  last = wwid.last(times: true, section: options[:section],
@@ -1702,7 +1813,9 @@ command :last do |c|
1702
1813
  negate: options[:not],
1703
1814
  tag: options[:tag],
1704
1815
  tag_bool: options[:bool],
1705
- delete: options[:delete]
1816
+ delete: options[:delete],
1817
+ bool: options[:bool],
1818
+ val: options[:val]
1706
1819
  })
1707
1820
  Doing::Pager::page last.strip if last
1708
1821
  end
@@ -1798,11 +1911,15 @@ command :show do |c|
1798
1911
  c.example 'doing show Ideas @doing --from "mon to fri"', desc: 'Show entries tagged @doing from the Ideas section added between monday and friday of the current week.'
1799
1912
  c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
1800
1913
 
1801
- c.desc 'Tag filter, combine multiple tags with a comma. Use `--tag pick` for a menu of available tags. Wildcards allowed (*, ?). Added for compatibility with other commands.'
1914
+ c.desc 'Tag filter, combine multiple tags with a comma. Use `--tag pick` for a menu of available tags. Wildcards allowed (*, ?). Added for compatibility with other commands'
1802
1915
  c.arg_name 'TAG'
1803
- c.flag [:tag]
1916
+ c.flag [:tag], type: TagArray
1917
+
1918
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
1919
+ c.arg_name 'QUERY'
1920
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
1804
1921
 
1805
- c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans.'
1922
+ c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans'
1806
1923
  c.arg_name 'BOOLEAN'
1807
1924
  c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
1808
1925
 
@@ -1814,11 +1931,11 @@ command :show do |c|
1814
1931
  c.arg_name 'AGE'
1815
1932
  c.flag %i[a age], default_value: 'newest'
1816
1933
 
1817
- 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.'
1934
+ 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'
1818
1935
  c.arg_name 'DATE_STRING'
1819
1936
  c.flag [:before]
1820
1937
 
1821
- 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.'
1938
+ 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'
1822
1939
  c.arg_name 'DATE_STRING'
1823
1940
  c.flag [:after]
1824
1941
 
@@ -1929,7 +2046,7 @@ command :show do |c|
1929
2046
  section ||= 'All'
1930
2047
  end
1931
2048
 
1932
- tags.concat(options[:tag].to_tags) if options[:tag]
2049
+ tags.concat(options[:tag]) if options[:tag]
1933
2050
 
1934
2051
  options[:times] = true if options[:totals]
1935
2052
 
@@ -2012,7 +2129,7 @@ command :tags do |c|
2012
2129
  c.arg_name 'ORDER'
2013
2130
  c.flag %i[o order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
2014
2131
 
2015
- c.desc 'Get tags for entries matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?).'
2132
+ c.desc 'Get tags for entries matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
2016
2133
  c.arg_name 'TAG'
2017
2134
  c.flag [:tag]
2018
2135
 
@@ -2021,6 +2138,10 @@ command :tags do |c|
2021
2138
  c.arg_name 'QUERY'
2022
2139
  c.flag [:search]
2023
2140
 
2141
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
2142
+ c.arg_name 'QUERY'
2143
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
2144
+
2024
2145
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
2025
2146
  # c.switch [:fuzzy], default_value: false, negatable: false
2026
2147
 
@@ -2034,7 +2155,7 @@ command :tags do |c|
2034
2155
  c.arg_name 'TYPE'
2035
2156
  c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
2036
2157
 
2037
- c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans.'
2158
+ c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans'
2038
2159
  c.arg_name 'BOOLEAN'
2039
2160
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
2040
2161
 
@@ -2291,11 +2412,15 @@ command :view do |c|
2291
2412
  c.desc 'Include colors in output'
2292
2413
  c.switch [:color], default_value: true, negatable: true
2293
2414
 
2294
- c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?).'
2415
+ c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?)'
2295
2416
  c.arg_name 'TAG'
2296
2417
  c.flag [:tag]
2297
2418
 
2298
- c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans.'
2419
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
2420
+ c.arg_name 'QUERY'
2421
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
2422
+
2423
+ c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans'
2299
2424
  c.arg_name 'BOOLEAN'
2300
2425
  c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
2301
2426
 
@@ -2324,11 +2449,11 @@ command :view do |c|
2324
2449
  c.arg_name 'DIRECTION'
2325
2450
  c.flag [:tag_order], must_match: REGEX_SORT_ORDER
2326
2451
 
2327
- 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.'
2452
+ 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'
2328
2453
  c.arg_name 'DATE_STRING'
2329
2454
  c.flag [:before]
2330
2455
 
2331
- 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.'
2456
+ 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'
2332
2457
  c.arg_name 'DATE_STRING'
2333
2458
  c.flag [:after]
2334
2459
 
@@ -2935,11 +3060,11 @@ command %i[archive move] do |c|
2935
3060
  c.desc 'Label moved items with @from(SECTION_NAME)'
2936
3061
  c.switch [:label], default_value: true, negatable: true
2937
3062
 
2938
- c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands.'
3063
+ c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands'
2939
3064
  c.arg_name 'TAG'
2940
- c.flag [:tag]
3065
+ c.flag [:tag], type: TagArray
2941
3066
 
2942
- c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
3067
+ c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
2943
3068
  c.arg_name 'BOOLEAN'
2944
3069
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
2945
3070
 
@@ -2947,6 +3072,10 @@ command %i[archive move] do |c|
2947
3072
  c.arg_name 'QUERY'
2948
3073
  c.flag [:search]
2949
3074
 
3075
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
3076
+ c.arg_name 'QUERY'
3077
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
3078
+
2950
3079
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
2951
3080
  # c.switch [:fuzzy], default_value: false, negatable: false
2952
3081
 
@@ -2982,7 +3111,7 @@ command %i[archive move] do |c|
2982
3111
 
2983
3112
  raise InvalidArgument, '--keep and --count can not be used together' if options[:keep] && options[:count]
2984
3113
 
2985
- tags.concat(options[:tag].to_tags) if options[:tag]
3114
+ tags.concat(options[:tag]) if options[:tag]
2986
3115
 
2987
3116
  search = nil
2988
3117
 
@@ -3042,7 +3171,7 @@ command :import do |c|
3042
3171
 
3043
3172
  c.desc 'Tag all imported entries'
3044
3173
  c.arg_name 'TAGS'
3045
- c.flag :tag
3174
+ c.flag %i[t tag]
3046
3175
 
3047
3176
  c.desc 'Autotag entries'
3048
3177
  c.switch :autotag, negatable: true, default_value: true
@@ -3077,6 +3206,12 @@ command :import do |c|
3077
3206
  options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
3078
3207
  end
3079
3208
 
3209
+ if options[:search]
3210
+ search = options[:search]
3211
+ search.sub!(/^'?/, "'") if options[:exact]
3212
+ options[:search] = search
3213
+ end
3214
+
3080
3215
  if options[:from]
3081
3216
  date_string = options[:from]
3082
3217
  if date_string =~ / (to|through|thru|(un)?til|-+) /
@@ -3085,7 +3220,7 @@ command :import do |c|
3085
3220
  finish = dates[2].chronify(guess: :end)
3086
3221
  else
3087
3222
  start = date_string.chronify(guess: :begin)
3088
- finish = false
3223
+ finish = date_string.chronify(guess: :end)
3089
3224
  end
3090
3225
  raise InvalidTimeExpression, 'Unrecognized date string' unless start
3091
3226
  dates = [start, finish]
@@ -3122,11 +3257,11 @@ command :rotate do |c|
3122
3257
  c.arg_name 'SECTION_NAME'
3123
3258
  c.flag %i[s section], default_value: 'All'
3124
3259
 
3125
- c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands.'
3260
+ c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands'
3126
3261
  c.arg_name 'TAG'
3127
3262
  c.flag [:tag]
3128
3263
 
3129
- c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
3264
+ c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
3130
3265
  c.arg_name 'BOOLEAN'
3131
3266
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
3132
3267
 
@@ -3134,6 +3269,10 @@ command :rotate do |c|
3134
3269
  c.arg_name 'QUERY'
3135
3270
  c.flag [:search]
3136
3271
 
3272
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
3273
+ c.arg_name 'QUERY'
3274
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
3275
+
3137
3276
  # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
3138
3277
  # c.switch [:fuzzy], default_value: false, negatable: false
3139
3278
 
@@ -3357,7 +3496,7 @@ command :undo do |c|
3357
3496
  c.arg_name 'COUNT'
3358
3497
  c.flag %i[p prune], type: Integer
3359
3498
 
3360
- c.desc 'Redo last undo. Note: you cannot undo a redo.'
3499
+ c.desc 'Redo last undo. Note: you cannot undo a redo'
3361
3500
  c.switch %i[r redo]
3362
3501
 
3363
3502
  c.action do |_global_options, options, args|
@@ -3384,7 +3523,7 @@ command :undo do |c|
3384
3523
  end
3385
3524
 
3386
3525
  # @@redo
3387
- long_desc 'Shortcut for `doing undo -r`, reverses the last undo command. You cannot undo a redo.'
3526
+ long_desc 'Shortcut for `doing undo -r`, reverses the last undo command. You cannot undo a redo'
3388
3527
  arg_name 'COUNT'
3389
3528
  command :redo do |c|
3390
3529
  c.desc 'Specify alternate doing file'