doing 2.0.3.pre → 2.0.8.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -1
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/doing +316 -114
- data/doing.rdoc +244 -19
- data/example_plugin.rb +1 -1
- data/generate_completions.sh +1 -0
- data/lib/completion/_doing.zsh +179 -127
- data/lib/completion/doing.bash +60 -27
- data/lib/completion/doing.fish +74 -23
- data/lib/doing/cli_status.rb +4 -0
- data/lib/doing/configuration.rb +2 -0
- data/lib/doing/errors.rb +22 -15
- data/lib/doing/item.rb +12 -11
- data/lib/doing/log_adapter.rb +27 -25
- data/lib/doing/plugin_manager.rb +1 -1
- data/lib/doing/plugins/export/json_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.rb +1 -1
- data/lib/doing/plugins/import/calendar_import.rb +7 -1
- data/lib/doing/plugins/import/doing_import.rb +6 -6
- data/lib/doing/plugins/import/timing_import.rb +7 -1
- data/lib/doing/string.rb +9 -7
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +160 -92
- data/lib/examples/commands/autotag.rb +63 -0
- data/lib/examples/commands/wiki.rb +1 -0
- data/lib/examples/plugins/say_export.rb +1 -1
- data/lib/examples/plugins/{templates → wiki_export/templates}/wiki.css +0 -0
- data/lib/examples/plugins/{templates → wiki_export/templates}/wiki.haml +0 -0
- data/lib/examples/plugins/{templates → wiki_export/templates}/wiki_index.haml +0 -0
- data/lib/examples/plugins/{wiki_export.rb → wiki_export/wiki_export.rb} +0 -0
- data/scripts/generate_bash_completions.rb +3 -2
- data/scripts/generate_fish_completions.rb +4 -1
- data/scripts/generate_zsh_completions.rb +44 -39
- metadata +7 -7
- data/doing.fish +0 -278
data/bin/doing
CHANGED
@@ -57,8 +57,8 @@ config = Doing.config
|
|
57
57
|
settings = config.settings
|
58
58
|
wwid.config = settings
|
59
59
|
|
60
|
-
if
|
61
|
-
commands_from File.expand_path(
|
60
|
+
if settings.dig('plugins', 'command_path')
|
61
|
+
commands_from File.expand_path(settings.dig('plugins', 'command_path'))
|
62
62
|
end
|
63
63
|
|
64
64
|
program_desc 'A CLI for a What Was I Doing system'
|
@@ -137,7 +137,7 @@ command %i[now next] do |c|
|
|
137
137
|
if options[:back]
|
138
138
|
date = wwid.chronify(options[:back], guess: :begin)
|
139
139
|
|
140
|
-
raise
|
140
|
+
raise InvalidTimeExpression.new('unable to parse date string', topic: 'Date parser:') if date.nil?
|
141
141
|
else
|
142
142
|
date = Time.now
|
143
143
|
end
|
@@ -149,13 +149,13 @@ command %i[now next] do |c|
|
|
149
149
|
end
|
150
150
|
|
151
151
|
if options[:e] || (args.empty? && $stdin.stat.size.zero?)
|
152
|
-
raise
|
152
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
153
153
|
|
154
154
|
input = ''
|
155
155
|
input += args.join(' ') unless args.empty?
|
156
156
|
input = wwid.fork_editor(input).strip
|
157
157
|
|
158
|
-
raise
|
158
|
+
raise EmptyInput, 'No content' if input.empty?
|
159
159
|
|
160
160
|
title, note = wwid.format_input(input)
|
161
161
|
note.push(options[:n]) if options[:n]
|
@@ -173,14 +173,14 @@ command %i[now next] do |c|
|
|
173
173
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
|
174
174
|
wwid.write(wwid.doing_file)
|
175
175
|
else
|
176
|
-
raise
|
176
|
+
raise EmptyInput, 'You must provide content when creating a new entry'
|
177
177
|
end
|
178
178
|
end
|
179
179
|
end
|
180
180
|
|
181
181
|
desc 'Reset the start time of an entry'
|
182
182
|
command %i[reset begin] do |c|
|
183
|
-
c.desc '
|
183
|
+
c.desc 'Limit search to section'
|
184
184
|
c.arg_name 'NAME'
|
185
185
|
c.flag %i[s section], default_value: 'All'
|
186
186
|
|
@@ -195,12 +195,18 @@ command %i[reset begin] do |c|
|
|
195
195
|
c.arg_name 'QUERY'
|
196
196
|
c.flag [:search]
|
197
197
|
|
198
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
199
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
200
|
+
|
201
|
+
c.desc 'Reset items that *don\'t* match search/tag filters'
|
202
|
+
c.switch [:not], default_value: false, negatable: false
|
203
|
+
|
198
204
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
199
205
|
c.arg_name 'BOOLEAN'
|
200
206
|
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
201
207
|
|
202
208
|
c.desc 'Select from a menu of matching entries'
|
203
|
-
c.switch %i[i interactive]
|
209
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
204
210
|
|
205
211
|
c.action do |global_options, options, args|
|
206
212
|
if options[:section]
|
@@ -209,6 +215,13 @@ command %i[reset begin] do |c|
|
|
209
215
|
|
210
216
|
options[:tag_bool] = options[:bool].normalize_bool
|
211
217
|
|
218
|
+
if options[:search]
|
219
|
+
search = options[:search]
|
220
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
221
|
+
options[:search] = search
|
222
|
+
end
|
223
|
+
|
224
|
+
|
212
225
|
items = wwid.filter_items([], opt: options)
|
213
226
|
|
214
227
|
if options[:interactive]
|
@@ -266,12 +279,18 @@ command :note do |c|
|
|
266
279
|
c.arg_name 'QUERY'
|
267
280
|
c.flag [:search]
|
268
281
|
|
282
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
283
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
284
|
+
|
285
|
+
c.desc 'Add note to item that *doesn\'t* match search/tag filters'
|
286
|
+
c.switch [:not], default_value: false, negatable: false
|
287
|
+
|
269
288
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
270
289
|
c.arg_name 'BOOLEAN'
|
271
290
|
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
272
291
|
|
273
292
|
c.desc 'Select item for new note from a menu of matching entries'
|
274
|
-
c.switch %i[i interactive]
|
293
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
275
294
|
|
276
295
|
c.action do |_global_options, options, args|
|
277
296
|
if options[:section]
|
@@ -280,6 +299,13 @@ command :note do |c|
|
|
280
299
|
|
281
300
|
options[:tag_bool] = options[:bool].normalize_bool
|
282
301
|
|
302
|
+
if options[:search]
|
303
|
+
search = options[:search]
|
304
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
305
|
+
options[:search] = search
|
306
|
+
end
|
307
|
+
|
308
|
+
|
283
309
|
last_entry = wwid.last_entry(options)
|
284
310
|
|
285
311
|
unless last_entry
|
@@ -291,7 +317,7 @@ command :note do |c|
|
|
291
317
|
new_note = Doing::Note.new
|
292
318
|
|
293
319
|
if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
|
294
|
-
raise
|
320
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
295
321
|
|
296
322
|
input = !args.empty? ? args.join(' ') : ''
|
297
323
|
|
@@ -312,7 +338,7 @@ command :note do |c|
|
|
312
338
|
elsif $stdin.stat.size.positive?
|
313
339
|
new_note.add($stdin.read)
|
314
340
|
else
|
315
|
-
raise
|
341
|
+
raise EmptyInput, 'You must provide content when adding a note' unless options[:remove]
|
316
342
|
end
|
317
343
|
|
318
344
|
if last_note.equal?(new_note)
|
@@ -338,7 +364,7 @@ command :meanwhile do |c|
|
|
338
364
|
c.switch %i[e editor], negatable: false, default_value: false
|
339
365
|
|
340
366
|
c.desc 'Archive previous @meanwhile entry'
|
341
|
-
c.switch %i[a archive], default_value: false
|
367
|
+
c.switch %i[a archive], negatable: false, default_value: false
|
342
368
|
|
343
369
|
c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
|
344
370
|
c.arg_name 'DATE_STRING'
|
@@ -352,7 +378,7 @@ command :meanwhile do |c|
|
|
352
378
|
if options[:back]
|
353
379
|
date = wwid.chronify(options[:back], guess: :begin)
|
354
380
|
|
355
|
-
raise
|
381
|
+
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
356
382
|
else
|
357
383
|
date = Time.now
|
358
384
|
end
|
@@ -365,7 +391,7 @@ command :meanwhile do |c|
|
|
365
391
|
input = ''
|
366
392
|
|
367
393
|
if options[:e]
|
368
|
-
raise
|
394
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
369
395
|
|
370
396
|
input += args.join(' ') unless args.empty?
|
371
397
|
input = wwid.fork_editor(input).strip
|
@@ -403,7 +429,7 @@ long_desc %(
|
|
403
429
|
arg_name 'TYPE', must_match: Doing::Plugins.template_regex
|
404
430
|
command :template do |c|
|
405
431
|
c.desc 'List all available templates'
|
406
|
-
c.switch %i[l list]
|
432
|
+
c.switch %i[l list], negatable: false
|
407
433
|
|
408
434
|
c.desc 'List in single column for completion'
|
409
435
|
c.switch %i[c]
|
@@ -424,7 +450,7 @@ command :template do |c|
|
|
424
450
|
type = args[0]
|
425
451
|
end
|
426
452
|
|
427
|
-
raise
|
453
|
+
raise InvalidPluginType, "No type specified, use `doing template [#{Doing::Plugins.plugin_templates.join('|')}]`" unless type
|
428
454
|
|
429
455
|
$stdout.puts Doing::Plugins.template_for_trigger(type)
|
430
456
|
|
@@ -473,6 +499,12 @@ command :select do |c|
|
|
473
499
|
c.arg_name 'QUERY'
|
474
500
|
c.flag %i[q query search]
|
475
501
|
|
502
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
503
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
504
|
+
|
505
|
+
c.desc 'Select items that *don\'t* match search/tag filters'
|
506
|
+
c.switch [:not], default_value: false, negatable: false
|
507
|
+
|
476
508
|
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.'
|
477
509
|
c.switch %i[menu], negatable: true, default_value: true
|
478
510
|
|
@@ -503,12 +535,12 @@ command :select do |c|
|
|
503
535
|
c.flag %i[o output]
|
504
536
|
|
505
537
|
c.desc "Copy selection as a new entry with current time and no @done tag. Only works with single selections. Can be combined with --editor."
|
506
|
-
c.switch %i[again resume]
|
538
|
+
c.switch %i[again resume], negatable: false, default_value: false
|
507
539
|
|
508
540
|
c.action do |_global_options, options, args|
|
509
|
-
raise
|
541
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
510
542
|
|
511
|
-
raise
|
543
|
+
raise InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
|
512
544
|
|
513
545
|
wwid.interactive(options)
|
514
546
|
end
|
@@ -531,17 +563,17 @@ command :later do |c|
|
|
531
563
|
c.action do |_global_options, options, args|
|
532
564
|
if options[:back]
|
533
565
|
date = wwid.chronify(options[:back], guess: :begin)
|
534
|
-
raise
|
566
|
+
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
535
567
|
else
|
536
568
|
date = Time.now
|
537
569
|
end
|
538
570
|
|
539
571
|
if options[:editor] || (args.empty? && $stdin.stat.size.zero?)
|
540
|
-
raise
|
572
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
541
573
|
|
542
574
|
input = args.empty? ? '' : args.join(' ')
|
543
575
|
input = wwid.fork_editor(input).strip
|
544
|
-
raise
|
576
|
+
raise EmptyInput, 'No content' unless input && !input.empty?
|
545
577
|
|
546
578
|
title, note = wwid.format_input(input)
|
547
579
|
note.push(options[:n]) if options[:n]
|
@@ -558,7 +590,7 @@ command :later do |c|
|
|
558
590
|
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
559
591
|
wwid.write(wwid.doing_file)
|
560
592
|
else
|
561
|
-
raise
|
593
|
+
raise EmptyInput, 'You must provide content when creating a new entry'
|
562
594
|
end
|
563
595
|
end
|
564
596
|
end
|
@@ -614,19 +646,19 @@ command %i[done did] do |c|
|
|
614
646
|
|
615
647
|
if options[:took]
|
616
648
|
took = wwid.chronify_qty(options[:took])
|
617
|
-
raise
|
649
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
618
650
|
end
|
619
651
|
|
620
652
|
if options[:back]
|
621
653
|
date = wwid.chronify(options[:back], guess: :begin)
|
622
|
-
raise
|
654
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
623
655
|
else
|
624
656
|
date = options[:took] ? Time.now - took : Time.now
|
625
657
|
end
|
626
658
|
|
627
659
|
if options[:at]
|
628
660
|
finish_date = wwid.chronify(options[:at], guess: :begin)
|
629
|
-
raise
|
661
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
630
662
|
|
631
663
|
date = options[:took] ? finish_date - took : finish_date
|
632
664
|
elsif options[:took]
|
@@ -651,7 +683,7 @@ command %i[done did] do |c|
|
|
651
683
|
note.add(options[:note]) if options[:note]
|
652
684
|
|
653
685
|
if options[:editor]
|
654
|
-
raise
|
686
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
655
687
|
is_new = false
|
656
688
|
|
657
689
|
if args.empty?
|
@@ -659,7 +691,7 @@ command %i[done did] do |c|
|
|
659
691
|
|
660
692
|
unless last_entry
|
661
693
|
Doing.logger.debug('Skipped:', options[:unfinished] ? 'No unfinished entry' : 'Last entry already @done')
|
662
|
-
raise
|
694
|
+
raise NoResults, 'No results'
|
663
695
|
end
|
664
696
|
|
665
697
|
old_entry = last_entry.dup
|
@@ -671,7 +703,7 @@ command %i[done did] do |c|
|
|
671
703
|
end
|
672
704
|
|
673
705
|
input = wwid.fork_editor(input).strip
|
674
|
-
raise
|
706
|
+
raise EmptyInput, 'No content' unless input && !input.empty?
|
675
707
|
|
676
708
|
title, note = wwid.format_input(input)
|
677
709
|
new_entry = Doing::Item.new(date, title, section, note)
|
@@ -746,7 +778,7 @@ command %i[done did] do |c|
|
|
746
778
|
wwid.write(wwid.doing_file)
|
747
779
|
Doing.logger.info('Entry Added:', new_entry.title)
|
748
780
|
else
|
749
|
-
raise
|
781
|
+
raise EmptyInput, 'You must provide content when creating a new entry'
|
750
782
|
end
|
751
783
|
end
|
752
784
|
end
|
@@ -774,7 +806,7 @@ command :cancel do |c|
|
|
774
806
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
775
807
|
|
776
808
|
c.desc 'Select item(s) to cancel from a menu of matching entries'
|
777
|
-
c.switch %i[i interactive]
|
809
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
778
810
|
|
779
811
|
c.action do |_global_options, options, args|
|
780
812
|
if options[:section]
|
@@ -789,9 +821,9 @@ command :cancel do |c|
|
|
789
821
|
tags = options[:tag].to_tags
|
790
822
|
end
|
791
823
|
|
792
|
-
raise
|
824
|
+
raise InvalidArgument, 'Only one argument allowed' if args.length > 1
|
793
825
|
|
794
|
-
raise
|
826
|
+
raise InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
|
795
827
|
|
796
828
|
if options[:interactive]
|
797
829
|
count = 0
|
@@ -844,6 +876,12 @@ command :finish do |c|
|
|
844
876
|
c.arg_name 'QUERY'
|
845
877
|
c.flag [:search]
|
846
878
|
|
879
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
880
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
881
|
+
|
882
|
+
c.desc 'Finish items that *don\'t* match search/tag filters'
|
883
|
+
c.switch [:not], default_value: false, negatable: false
|
884
|
+
|
847
885
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
848
886
|
c.arg_name 'BOOLEAN'
|
849
887
|
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
@@ -867,28 +905,28 @@ command :finish do |c|
|
|
867
905
|
c.flag %i[s section]
|
868
906
|
|
869
907
|
c.desc 'Select item(s) to finish from a menu of matching entries'
|
870
|
-
c.switch %i[i interactive]
|
908
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
871
909
|
|
872
910
|
c.action do |_global_options, options, args|
|
873
911
|
unless options[:auto]
|
874
912
|
if options[:took]
|
875
913
|
took = wwid.chronify_qty(options[:took])
|
876
|
-
raise
|
914
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
877
915
|
end
|
878
916
|
|
879
|
-
raise
|
917
|
+
raise InvalidArgument, '--back and --took can not be used together' if options[:back] && options[:took]
|
880
918
|
|
881
|
-
raise
|
919
|
+
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
882
920
|
|
883
921
|
if options[:at]
|
884
922
|
finish_date = wwid.chronify(options[:at], guess: :begin)
|
885
|
-
raise
|
923
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
886
924
|
|
887
925
|
date = options[:took] ? finish_date - took : finish_date
|
888
926
|
elsif options[:back]
|
889
927
|
date = wwid.chronify(options[:back])
|
890
928
|
|
891
|
-
raise
|
929
|
+
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
892
930
|
elsif options[:took]
|
893
931
|
date = wwid.chronify_qty(options[:took])
|
894
932
|
else
|
@@ -902,9 +940,9 @@ command :finish do |c|
|
|
902
940
|
tags = options[:tag].to_tags
|
903
941
|
end
|
904
942
|
|
905
|
-
raise
|
943
|
+
raise InvalidArgument, 'Only one argument allowed' if args.length > 1
|
906
944
|
|
907
|
-
raise
|
945
|
+
raise InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
|
908
946
|
|
909
947
|
if options[:interactive]
|
910
948
|
count = 0
|
@@ -912,12 +950,20 @@ command :finish do |c|
|
|
912
950
|
count = args[0] ? args[0].to_i : 1
|
913
951
|
end
|
914
952
|
|
953
|
+
search = nil
|
954
|
+
|
955
|
+
if options[:search]
|
956
|
+
search = options[:search]
|
957
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
958
|
+
end
|
959
|
+
|
915
960
|
opts = {
|
916
961
|
archive: options[:archive],
|
917
962
|
back: date,
|
918
963
|
count: count,
|
919
964
|
date: options[:date],
|
920
|
-
search:
|
965
|
+
search: search,
|
966
|
+
not: options[:not],
|
921
967
|
section: options[:section],
|
922
968
|
sequential: options[:auto],
|
923
969
|
tag: tags,
|
@@ -951,6 +997,12 @@ command %i[again resume] do |c|
|
|
951
997
|
c.arg_name 'QUERY'
|
952
998
|
c.flag [:search]
|
953
999
|
|
1000
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
1001
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
1002
|
+
|
1003
|
+
c.desc 'Resume items that *don\'t* match search/tag filters'
|
1004
|
+
c.switch [:not], default_value: false, negatable: false
|
1005
|
+
|
954
1006
|
c.desc 'Boolean used to combine multiple tags'
|
955
1007
|
c.arg_name 'BOOLEAN'
|
956
1008
|
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
@@ -963,11 +1015,19 @@ command %i[again resume] do |c|
|
|
963
1015
|
c.flag %i[n note]
|
964
1016
|
|
965
1017
|
c.desc 'Select item to resume from a menu of matching entries'
|
966
|
-
c.switch %i[i interactive]
|
1018
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
967
1019
|
|
968
1020
|
c.action do |_global_options, options, _args|
|
969
1021
|
tags = options[:tag].nil? ? [] : options[:tag].to_tags
|
1022
|
+
|
1023
|
+
if options[:search]
|
1024
|
+
search = options[:search]
|
1025
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
1026
|
+
options[:search] = search
|
1027
|
+
end
|
1028
|
+
|
970
1029
|
opts = options
|
1030
|
+
|
971
1031
|
opts[:tag] = tags
|
972
1032
|
opts[:tag_bool] = options[:bool].normalize_bool
|
973
1033
|
opts[:interactive] = options[:interactive]
|
@@ -1037,17 +1097,23 @@ command :tag do |c|
|
|
1037
1097
|
c.arg_name 'QUERY'
|
1038
1098
|
c.flag [:search]
|
1039
1099
|
|
1100
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
1101
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
1102
|
+
|
1103
|
+
c.desc 'Tag items that *don\'t* match search/tag filters'
|
1104
|
+
c.switch [:not], default_value: false, negatable: false
|
1105
|
+
|
1040
1106
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
1041
1107
|
c.arg_name 'BOOLEAN'
|
1042
1108
|
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
1043
1109
|
|
1044
1110
|
c.desc 'Select item(s) to tag from a menu of matching entries'
|
1045
|
-
c.switch %i[i interactive]
|
1111
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
1046
1112
|
|
1047
1113
|
c.action do |_global_options, options, args|
|
1048
|
-
raise
|
1114
|
+
raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
|
1049
1115
|
|
1050
|
-
raise
|
1116
|
+
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
1051
1117
|
|
1052
1118
|
section = 'All'
|
1053
1119
|
|
@@ -1081,6 +1147,11 @@ command :tag do |c|
|
|
1081
1147
|
count = options[:count].to_i
|
1082
1148
|
end
|
1083
1149
|
|
1150
|
+
if options[:search]
|
1151
|
+
search = options[:search]
|
1152
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
1153
|
+
options[:search] = search
|
1154
|
+
end
|
1084
1155
|
|
1085
1156
|
if count.zero? && !options[:force]
|
1086
1157
|
if options[:search]
|
@@ -1104,7 +1175,7 @@ command :tag do |c|
|
|
1104
1175
|
|
1105
1176
|
res = wwid.yn(question, default_response: false)
|
1106
1177
|
|
1107
|
-
|
1178
|
+
raise UserCancelled unless res
|
1108
1179
|
end
|
1109
1180
|
|
1110
1181
|
options[:count] = count
|
@@ -1117,19 +1188,6 @@ command :tag do |c|
|
|
1117
1188
|
end
|
1118
1189
|
end
|
1119
1190
|
|
1120
|
-
# desc 'Autotag last X entries'
|
1121
|
-
# arg_name 'COUNT'
|
1122
|
-
# command :autotag do |c|
|
1123
|
-
# c.action do |global_options, options, args|
|
1124
|
-
# options = {
|
1125
|
-
# autotag: true,
|
1126
|
-
# count: args[0].to_i
|
1127
|
-
# }
|
1128
|
-
# cmd = commands[:tag]
|
1129
|
-
# cmd.action.(global_options, options, [])
|
1130
|
-
# end
|
1131
|
-
# end
|
1132
|
-
|
1133
1191
|
desc 'Mark last entry as flagged'
|
1134
1192
|
command [:mark, :flag] do |c|
|
1135
1193
|
c.example 'doing flag', desc: 'Add @flagged to the last entry created'
|
@@ -1165,17 +1223,23 @@ command [:mark, :flag] do |c|
|
|
1165
1223
|
c.arg_name 'QUERY'
|
1166
1224
|
c.flag [:search]
|
1167
1225
|
|
1226
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
1227
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
1228
|
+
|
1229
|
+
c.desc 'Flag items that *don\'t* match search/tag/date filters'
|
1230
|
+
c.switch [:not], default_value: false, negatable: false
|
1231
|
+
|
1168
1232
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
1169
1233
|
c.arg_name 'BOOLEAN'
|
1170
1234
|
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
1171
1235
|
|
1172
1236
|
c.desc 'Select item(s) to flag from a menu of matching entries'
|
1173
|
-
c.switch %i[i interactive]
|
1237
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
1174
1238
|
|
1175
1239
|
c.action do |_global_options, options, _args|
|
1176
1240
|
mark = settings['marker_tag'] || 'flagged'
|
1177
1241
|
|
1178
|
-
raise
|
1242
|
+
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
1179
1243
|
|
1180
1244
|
section = 'All'
|
1181
1245
|
|
@@ -1196,6 +1260,12 @@ command [:mark, :flag] do |c|
|
|
1196
1260
|
count = options[:count].to_i
|
1197
1261
|
end
|
1198
1262
|
|
1263
|
+
if options[:search]
|
1264
|
+
search = options[:search]
|
1265
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
1266
|
+
options[:search] = search
|
1267
|
+
end
|
1268
|
+
|
1199
1269
|
if count.zero? && !options[:force]
|
1200
1270
|
if options[:search]
|
1201
1271
|
section_q = ' matching your search terms'
|
@@ -1270,6 +1340,12 @@ command :show do |c|
|
|
1270
1340
|
c.arg_name 'QUERY'
|
1271
1341
|
c.flag [:search]
|
1272
1342
|
|
1343
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
1344
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
1345
|
+
|
1346
|
+
c.desc 'Show items that *don\'t* match search/tag/date filters'
|
1347
|
+
c.switch [:not], default_value: false, negatable: false
|
1348
|
+
|
1273
1349
|
c.desc 'Sort order (asc/desc)'
|
1274
1350
|
c.arg_name 'ORDER'
|
1275
1351
|
c.flag %i[s sort], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
@@ -1302,13 +1378,13 @@ command :show do |c|
|
|
1302
1378
|
c.switch [:only_timed], default_value: false, negatable: false
|
1303
1379
|
|
1304
1380
|
c.desc 'Select from a menu of matching entries to perform additional operations'
|
1305
|
-
c.switch %i[i interactive]
|
1381
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
1306
1382
|
|
1307
1383
|
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
1308
1384
|
c.arg_name 'FORMAT'
|
1309
1385
|
c.flag %i[o output]
|
1310
|
-
c.action do |
|
1311
|
-
raise
|
1386
|
+
c.action do |global_options, options, args|
|
1387
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1312
1388
|
|
1313
1389
|
tag_filter = false
|
1314
1390
|
tags = []
|
@@ -1323,8 +1399,15 @@ command :show do |c|
|
|
1323
1399
|
when /^@/
|
1324
1400
|
section = 'All'
|
1325
1401
|
else
|
1326
|
-
|
1327
|
-
|
1402
|
+
begin
|
1403
|
+
section = wwid.guess_section(args[0])
|
1404
|
+
rescue WrongCommand => exception
|
1405
|
+
cmd = commands[:view]
|
1406
|
+
action = cmd.send(:get_action, nil)
|
1407
|
+
return action.call(global_options, options, args)
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
raise InvalidSection, "No such section: #{args[0]}" unless section
|
1328
1411
|
|
1329
1412
|
args.shift
|
1330
1413
|
end
|
@@ -1359,7 +1442,7 @@ command :show do |c|
|
|
1359
1442
|
start = wwid.chronify(date_string, guess: :begin)
|
1360
1443
|
finish = false
|
1361
1444
|
end
|
1362
|
-
raise
|
1445
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
1363
1446
|
dates = [start, finish]
|
1364
1447
|
end
|
1365
1448
|
|
@@ -1367,6 +1450,12 @@ command :show do |c|
|
|
1367
1450
|
|
1368
1451
|
tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
|
1369
1452
|
|
1453
|
+
if options[:search]
|
1454
|
+
search = options[:search]
|
1455
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
1456
|
+
options[:search] = search
|
1457
|
+
end
|
1458
|
+
|
1370
1459
|
opt = options.dup
|
1371
1460
|
|
1372
1461
|
opt[:sort_tags] = options[:tag_sort] =~ /^n/i
|
@@ -1429,20 +1518,31 @@ command %i[grep search] do |c|
|
|
1429
1518
|
c.desc 'Only show items with recorded time intervals'
|
1430
1519
|
c.switch [:only_timed], default_value: false, negatable: false
|
1431
1520
|
|
1521
|
+
c.desc 'Force exact string matching (case sensitive)'
|
1522
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
1523
|
+
|
1524
|
+
c.desc 'Force case sensitive matching'
|
1525
|
+
c.switch %i[case]
|
1526
|
+
|
1527
|
+
c.desc 'Show items that *don\'t* match search string'
|
1528
|
+
c.switch [:not], default_value: false, negatable: false
|
1529
|
+
|
1432
1530
|
c.desc 'Display an interactive menu of results to perform further operations'
|
1433
1531
|
c.switch %i[i interactive], default_value: false, negatable: false
|
1434
1532
|
|
1435
1533
|
c.action do |_global_options, options, args|
|
1436
|
-
raise
|
1534
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1437
1535
|
|
1438
1536
|
tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
|
1439
1537
|
|
1440
1538
|
section = wwid.guess_section(options[:section]) if options[:section]
|
1539
|
+
search = args.join(' ')
|
1540
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
1441
1541
|
|
1442
1542
|
options[:times] = true if options[:totals]
|
1443
1543
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
1444
1544
|
options[:highlight] = true
|
1445
|
-
options[:search] =
|
1545
|
+
options[:search] = search
|
1446
1546
|
options[:section] = section
|
1447
1547
|
options[:tags_color] = tags_color
|
1448
1548
|
|
@@ -1476,7 +1576,7 @@ command :recent do |c|
|
|
1476
1576
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1477
1577
|
|
1478
1578
|
c.desc 'Select from a menu of matching entries to perform additional operations'
|
1479
|
-
c.switch %i[i interactive]
|
1579
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
1480
1580
|
|
1481
1581
|
c.action do |global_options, options, args|
|
1482
1582
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
@@ -1548,7 +1648,7 @@ command :today do |c|
|
|
1548
1648
|
c.flag [:after]
|
1549
1649
|
|
1550
1650
|
c.action do |_global_options, options, _args|
|
1551
|
-
raise
|
1651
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1552
1652
|
|
1553
1653
|
options[:t] = true if options[:totals]
|
1554
1654
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
@@ -1595,9 +1695,9 @@ command :on do |c|
|
|
1595
1695
|
c.flag %i[o output]
|
1596
1696
|
|
1597
1697
|
c.action do |_global_options, options, args|
|
1598
|
-
raise
|
1698
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1599
1699
|
|
1600
|
-
raise
|
1700
|
+
raise MissingArgument, 'Missing date argument' if args.empty?
|
1601
1701
|
|
1602
1702
|
date_string = args.join(' ')
|
1603
1703
|
|
@@ -1610,7 +1710,7 @@ command :on do |c|
|
|
1610
1710
|
finish = false
|
1611
1711
|
end
|
1612
1712
|
|
1613
|
-
raise
|
1713
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
1614
1714
|
|
1615
1715
|
message = "Date interpreted as #{start}"
|
1616
1716
|
message += " to #{finish}" if finish
|
@@ -1653,9 +1753,9 @@ command :since do |c|
|
|
1653
1753
|
c.flag %i[o output]
|
1654
1754
|
|
1655
1755
|
c.action do |_global_options, options, args|
|
1656
|
-
raise
|
1756
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1657
1757
|
|
1658
|
-
raise
|
1758
|
+
raise MissingArgument, 'Missing date argument' if args.empty?
|
1659
1759
|
|
1660
1760
|
date_string = args.join(' ')
|
1661
1761
|
|
@@ -1665,7 +1765,7 @@ command :since do |c|
|
|
1665
1765
|
start = wwid.chronify(date_string, guess: :begin)
|
1666
1766
|
finish = Time.now
|
1667
1767
|
|
1668
|
-
raise
|
1768
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
1669
1769
|
|
1670
1770
|
Doing.logger.debug("Date interpreted as #{start} through the current time")
|
1671
1771
|
|
@@ -1698,7 +1798,6 @@ command :yesterday do |c|
|
|
1698
1798
|
c.switch [:totals], default_value: false, negatable: false
|
1699
1799
|
|
1700
1800
|
c.desc 'Sort tags by (name|time)'
|
1701
|
-
default = 'time'
|
1702
1801
|
default = settings['tag_sort'] || 'name'
|
1703
1802
|
c.arg_name 'KEY'
|
1704
1803
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
@@ -1716,7 +1815,7 @@ command :yesterday do |c|
|
|
1716
1815
|
c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
1717
1816
|
|
1718
1817
|
c.action do |_global_options, options, _args|
|
1719
|
-
raise
|
1818
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1720
1819
|
|
1721
1820
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
1722
1821
|
|
@@ -1761,8 +1860,14 @@ command :last do |c|
|
|
1761
1860
|
c.arg_name 'QUERY'
|
1762
1861
|
c.flag [:search]
|
1763
1862
|
|
1863
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
1864
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
1865
|
+
|
1866
|
+
c.desc 'Show items that *don\'t* match search string or tag filter'
|
1867
|
+
c.switch [:not], default_value: false, negatable: false
|
1868
|
+
|
1764
1869
|
c.action do |global_options, options, _args|
|
1765
|
-
raise
|
1870
|
+
raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
|
1766
1871
|
|
1767
1872
|
if options[:tag].nil?
|
1768
1873
|
tags = []
|
@@ -1779,11 +1884,18 @@ command :last do |c|
|
|
1779
1884
|
|
1780
1885
|
end
|
1781
1886
|
|
1887
|
+
search = nil
|
1888
|
+
|
1889
|
+
if options[:search]
|
1890
|
+
search = options[:search]
|
1891
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
1892
|
+
end
|
1893
|
+
|
1782
1894
|
if options[:editor]
|
1783
|
-
wwid.edit_last(section: options[:s], options: { search:
|
1895
|
+
wwid.edit_last(section: options[:s], options: { search: search, tag: tags, tag_bool: options[:bool], not: options[:not] })
|
1784
1896
|
else
|
1785
1897
|
Doing::Pager::page wwid.last(times: true, section: options[:s],
|
1786
|
-
options: { search: options[:
|
1898
|
+
options: { search: search, negate: options[:not], tag: tags, tag_bool: options[:bool] }).strip
|
1787
1899
|
end
|
1788
1900
|
end
|
1789
1901
|
end
|
@@ -1791,7 +1903,7 @@ end
|
|
1791
1903
|
desc 'List sections'
|
1792
1904
|
command :sections do |c|
|
1793
1905
|
c.desc 'List in single column'
|
1794
|
-
c.switch %i[c column], default_value: false
|
1906
|
+
c.switch %i[c column], negatable: false, default_value: false
|
1795
1907
|
|
1796
1908
|
c.action do |_global_options, options, _args|
|
1797
1909
|
joiner = options[:c] ? "\n" : "\t"
|
@@ -1814,7 +1926,7 @@ command :add_section do |c|
|
|
1814
1926
|
c.example 'doing add_section Ideas', desc: 'Add a section called Ideas to the doing file'
|
1815
1927
|
|
1816
1928
|
c.action do |_global_options, _options, args|
|
1817
|
-
raise
|
1929
|
+
raise InvalidArgument, "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
|
1818
1930
|
|
1819
1931
|
wwid.add_section(args.join(' ').cap_first)
|
1820
1932
|
wwid.write(wwid.doing_file)
|
@@ -1853,16 +1965,54 @@ command :plugins do |c|
|
|
1853
1965
|
|
1854
1966
|
c.desc 'List plugins of type (import, export)'
|
1855
1967
|
c.arg_name 'TYPE'
|
1856
|
-
c.flag %i[t type], must_match: /^[iea]
|
1968
|
+
c.flag %i[t type], must_match: /^(?:[iea].*)$/i, default_value: 'all'
|
1857
1969
|
|
1858
1970
|
c.desc 'List in single column for completion'
|
1859
|
-
c.switch %i[c column], default_value: false
|
1971
|
+
c.switch %i[c column], negatable: false, default_value: false
|
1860
1972
|
|
1861
1973
|
c.action do |_global_options, options, _args|
|
1862
1974
|
Doing::Plugins.list_plugins(options)
|
1863
1975
|
end
|
1864
1976
|
end
|
1865
1977
|
|
1978
|
+
desc 'Generate shell completion scripts'
|
1979
|
+
command :completion do |c|
|
1980
|
+
c.example 'doing completion', desc: 'Output zsh (default) to STDOUT'
|
1981
|
+
c.example 'doing completion --type zsh --file ~/.zsh-completions/_doing.zsh', desc: 'Output zsh completions to file'
|
1982
|
+
c.example 'doing completion --type fish --file ~/.config/fish/completions/doing.fish', desc: 'Output fish completions to file'
|
1983
|
+
c.example 'doing completion --type bash --file ~/.bash_it/completion/enabled/doing.bash', desc: 'Output bash completions to file'
|
1984
|
+
|
1985
|
+
c.desc 'Shell to generate for (bash, zsh, fish)'
|
1986
|
+
c.arg_name 'SHELL'
|
1987
|
+
c.flag %i[t type], must_match: /^[bzf](?:[ai]?sh)?$/i, default_value: 'zsh'
|
1988
|
+
|
1989
|
+
c.desc 'File to write output to'
|
1990
|
+
c.arg_name 'PATH'
|
1991
|
+
c.flag %i[f file], default_value: 'stdout'
|
1992
|
+
|
1993
|
+
c.action do |_global_options, options, _args|
|
1994
|
+
script_dir = File.join(File.dirname(__FILE__), '..', 'scripts')
|
1995
|
+
|
1996
|
+
case options[:type]
|
1997
|
+
when /^b/
|
1998
|
+
result = `ruby #{File.join(script_dir, 'generate_bash_completions.rb')}`
|
1999
|
+
when /^z/
|
2000
|
+
result = `ruby #{File.join(script_dir, 'generate_zsh_completions.rb')}`
|
2001
|
+
when /^f/
|
2002
|
+
result = `ruby #{File.join(script_dir, 'generate_fish_completions.rb')}`
|
2003
|
+
end
|
2004
|
+
|
2005
|
+
if options[:file] =~ /^stdout$/i
|
2006
|
+
$stdout.puts result
|
2007
|
+
else
|
2008
|
+
File.open(File.expand_path(options[:file]), 'w') do |f|
|
2009
|
+
f.puts result
|
2010
|
+
end
|
2011
|
+
Doing.logger.warn('File written:', "#{options[:type]} completions written to #{options[:file]}")
|
2012
|
+
end
|
2013
|
+
end
|
2014
|
+
end
|
2015
|
+
|
1866
2016
|
desc 'Display a user-created view'
|
1867
2017
|
long_desc 'Command line options override view configuration'
|
1868
2018
|
arg_name 'VIEW_NAME'
|
@@ -1903,6 +2053,12 @@ command :view do |c|
|
|
1903
2053
|
c.arg_name 'QUERY'
|
1904
2054
|
c.flag [:search]
|
1905
2055
|
|
2056
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
2057
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
2058
|
+
|
2059
|
+
c.desc 'Show items that *don\'t* match search string'
|
2060
|
+
c.switch [:not], default_value: false, negatable: false
|
2061
|
+
|
1906
2062
|
c.desc 'Sort tags by (name|time)'
|
1907
2063
|
c.arg_name 'KEY'
|
1908
2064
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i
|
@@ -1923,17 +2079,24 @@ command :view do |c|
|
|
1923
2079
|
c.switch [:only_timed], default_value: false, negatable: false
|
1924
2080
|
|
1925
2081
|
c.desc 'Select from a menu of matching entries to perform additional operations'
|
1926
|
-
c.switch %i[i interactive]
|
2082
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
1927
2083
|
|
1928
|
-
c.action do |
|
1929
|
-
raise
|
2084
|
+
c.action do |global_options, options, args|
|
2085
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1930
2086
|
|
1931
|
-
raise
|
2087
|
+
raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
|
1932
2088
|
|
1933
2089
|
title = if args.empty?
|
1934
2090
|
wwid.choose_view
|
1935
2091
|
else
|
1936
|
-
|
2092
|
+
begin
|
2093
|
+
wwid.guess_view(args[0])
|
2094
|
+
rescue WrongCommand => exception
|
2095
|
+
cmd = commands[:show]
|
2096
|
+
options[:sort] = 'asc'
|
2097
|
+
action = cmd.send(:get_action, nil)
|
2098
|
+
return action.call(global_options, options, args)
|
2099
|
+
end
|
1937
2100
|
end
|
1938
2101
|
|
1939
2102
|
if options[:section]
|
@@ -2023,11 +2186,19 @@ command :view do |c|
|
|
2023
2186
|
start = wwid.chronify(date_string, guess: :begin)
|
2024
2187
|
finish = false
|
2025
2188
|
end
|
2026
|
-
raise
|
2189
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
2027
2190
|
dates = [start, finish]
|
2028
2191
|
end
|
2029
2192
|
|
2193
|
+
search = nil
|
2194
|
+
|
2195
|
+
if options[:search]
|
2196
|
+
search = options[:search]
|
2197
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
2198
|
+
end
|
2199
|
+
|
2030
2200
|
opts = options
|
2201
|
+
opts[:search] = search
|
2031
2202
|
opts[:output] = output_format
|
2032
2203
|
opts[:count] = count
|
2033
2204
|
opts[:format] = date_format
|
@@ -2046,9 +2217,9 @@ command :view do |c|
|
|
2046
2217
|
|
2047
2218
|
Doing::Pager.page wwid.list_section(opts)
|
2048
2219
|
elsif title.instance_of?(FalseClass)
|
2049
|
-
|
2220
|
+
raise UserCancelled, 'Cancelled' unless res
|
2050
2221
|
else
|
2051
|
-
raise
|
2222
|
+
raise InvalidView, "View #{title} not found in config"
|
2052
2223
|
end
|
2053
2224
|
end
|
2054
2225
|
end
|
@@ -2100,6 +2271,12 @@ command %i[archive move] do |c|
|
|
2100
2271
|
c.arg_name 'QUERY'
|
2101
2272
|
c.flag [:search]
|
2102
2273
|
|
2274
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
2275
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
2276
|
+
|
2277
|
+
c.desc 'Show items that *don\'t* match search string'
|
2278
|
+
c.switch [:not], default_value: false, negatable: false
|
2279
|
+
|
2103
2280
|
c.desc 'Archive entries older than date
|
2104
2281
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
2105
2282
|
c.arg_name 'DATE_STRING'
|
@@ -2119,11 +2296,19 @@ command %i[archive move] do |c|
|
|
2119
2296
|
tags = args.length > 1 ? args[1..].map { |t| t.sub(/^@/, '').strip } : []
|
2120
2297
|
end
|
2121
2298
|
|
2122
|
-
raise
|
2299
|
+
raise InvalidArgument, '--keep and --count can not be used together' if options[:keep] && options[:count]
|
2123
2300
|
|
2124
2301
|
tags.concat(options[:tag].to_tags) if options[:tag]
|
2125
2302
|
|
2303
|
+
search = nil
|
2304
|
+
|
2305
|
+
if options[:search]
|
2306
|
+
search = options[:search]
|
2307
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
2308
|
+
end
|
2309
|
+
|
2126
2310
|
opts = options
|
2311
|
+
opts[:search] = search
|
2127
2312
|
opts[:bool] = options[:bool].normalize_bool
|
2128
2313
|
opts[:destination] = options[:to]
|
2129
2314
|
opts[:tags] = tags
|
@@ -2158,6 +2343,12 @@ command :rotate do |c|
|
|
2158
2343
|
c.arg_name 'QUERY'
|
2159
2344
|
c.flag [:search]
|
2160
2345
|
|
2346
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
2347
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
2348
|
+
|
2349
|
+
c.desc 'Rotate items that *don\'t* match search string or tag filter'
|
2350
|
+
c.switch [:not], default_value: false, negatable: false
|
2351
|
+
|
2161
2352
|
c.desc 'Rotate entries older than date
|
2162
2353
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
2163
2354
|
c.arg_name 'DATE_STRING'
|
@@ -2170,6 +2361,14 @@ command :rotate do |c|
|
|
2170
2361
|
|
2171
2362
|
options[:bool] = options[:bool].normalize_bool
|
2172
2363
|
|
2364
|
+
search = nil
|
2365
|
+
|
2366
|
+
if options[:search]
|
2367
|
+
search = options[:search]
|
2368
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
2369
|
+
options[:search] = search
|
2370
|
+
end
|
2371
|
+
|
2173
2372
|
wwid.rotate(options)
|
2174
2373
|
end
|
2175
2374
|
end
|
@@ -2208,7 +2407,7 @@ command :open do |c|
|
|
2208
2407
|
system %(open "#{File.expand_path(wwid.doing_file)}")
|
2209
2408
|
end
|
2210
2409
|
else
|
2211
|
-
raise
|
2410
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
2212
2411
|
|
2213
2412
|
system %(#{Doing::Util.default_editor} "#{File.expand_path(wwid.doing_file)}")
|
2214
2413
|
end
|
@@ -2236,7 +2435,7 @@ command :config do |c|
|
|
2236
2435
|
c.flag %i[e editor], default_value: nil
|
2237
2436
|
|
2238
2437
|
c.desc 'Show a config key value based on arguments. Separate key paths with colons or dots, e.g. "export_templates.html". Empty arguments outputs the entire config.'
|
2239
|
-
c.switch %i[d dump]
|
2438
|
+
c.switch %i[d dump], negatable: false
|
2240
2439
|
|
2241
2440
|
c.desc 'Format for --dump (json|yaml|raw)'
|
2242
2441
|
c.arg_name 'FORMAT'
|
@@ -2261,7 +2460,6 @@ command :config do |c|
|
|
2261
2460
|
c.action do |_global_options, options, args|
|
2262
2461
|
if options[:update]
|
2263
2462
|
config.configure({rewrite: true, ignore_local: true})
|
2264
|
-
# Doing.logger.warn("Config file rewritten: #{config.config_file}")
|
2265
2463
|
return
|
2266
2464
|
end
|
2267
2465
|
|
@@ -2276,7 +2474,6 @@ command :config do |c|
|
|
2276
2474
|
when /^r/
|
2277
2475
|
cfg
|
2278
2476
|
else
|
2279
|
-
# cfg = { last_key => cfg } unless last_key.nil?
|
2280
2477
|
YAML.dump(cfg)
|
2281
2478
|
end
|
2282
2479
|
else
|
@@ -2291,7 +2488,7 @@ command :config do |c|
|
|
2291
2488
|
choices.concat(config.additional_configs)
|
2292
2489
|
res = wwid.choose_from(choices.uniq.sort.reverse, sorted: false, prompt: 'Local configs found, select which to edit > ')
|
2293
2490
|
|
2294
|
-
raise
|
2491
|
+
raise UserCancelled, 'Cancelled' unless res
|
2295
2492
|
|
2296
2493
|
config_file = res.strip || config.config_file
|
2297
2494
|
else
|
@@ -2308,7 +2505,7 @@ command :config do |c|
|
|
2308
2505
|
`open -a "#{editor}" "#{config_file}"`
|
2309
2506
|
end
|
2310
2507
|
else
|
2311
|
-
raise
|
2508
|
+
raise InvalidArgument, 'No viable editor found in config or environment.'
|
2312
2509
|
end
|
2313
2510
|
elsif options[:a] || options[:b]
|
2314
2511
|
if options[:a]
|
@@ -2319,7 +2516,7 @@ command :config do |c|
|
|
2319
2516
|
else
|
2320
2517
|
editor = options[:e] || Doing::Util.find_default_editor('config')
|
2321
2518
|
|
2322
|
-
raise
|
2519
|
+
raise MissingEditor, 'No viable editor defined in config or environment' unless editor
|
2323
2520
|
|
2324
2521
|
if Doing::Util.exec_available(editor)
|
2325
2522
|
system %(#{editor} "#{config_file}")
|
@@ -2329,7 +2526,7 @@ command :config do |c|
|
|
2329
2526
|
end
|
2330
2527
|
else
|
2331
2528
|
editor = options[:e] || Doing::Util.default_editor
|
2332
|
-
raise
|
2529
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' unless editor && Doing::Util.exec_available(editor)
|
2333
2530
|
|
2334
2531
|
system %(#{editor} "#{config_file}")
|
2335
2532
|
end
|
@@ -2360,6 +2557,12 @@ command :import do |c|
|
|
2360
2557
|
c.arg_name 'QUERY'
|
2361
2558
|
c.flag [:search]
|
2362
2559
|
|
2560
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
2561
|
+
c.switch %i[x exact], default_value: false, negatable: false
|
2562
|
+
|
2563
|
+
c.desc 'Import items that *don\'t* match search/tag/date filters'
|
2564
|
+
c.switch [:not], default_value: false, negatable: false
|
2565
|
+
|
2363
2566
|
c.desc 'Only import items with recorded time intervals'
|
2364
2567
|
c.switch [:only_timed], default_value: false, negatable: false
|
2365
2568
|
|
@@ -2413,7 +2616,7 @@ command :import do |c|
|
|
2413
2616
|
start = wwid.chronify(date_string, guess: :begin)
|
2414
2617
|
finish = false
|
2415
2618
|
end
|
2416
|
-
raise
|
2619
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
2417
2620
|
dates = [start, finish]
|
2418
2621
|
end
|
2419
2622
|
|
@@ -2423,7 +2626,7 @@ command :import do |c|
|
|
2423
2626
|
wwid.import(args, options)
|
2424
2627
|
wwid.write(wwid.doing_file)
|
2425
2628
|
else
|
2426
|
-
raise
|
2629
|
+
raise InvalidPluginType, "Invalid import type: #{options[:type]}"
|
2427
2630
|
end
|
2428
2631
|
end
|
2429
2632
|
end
|
@@ -2448,14 +2651,13 @@ pre do |global, _command, _options, _args|
|
|
2448
2651
|
end
|
2449
2652
|
|
2450
2653
|
on_error do |exception|
|
2451
|
-
|
2452
|
-
|
2453
|
-
|
2454
|
-
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2458
|
-
false
|
2654
|
+
if exception.kind_of?(SystemExit)
|
2655
|
+
false
|
2656
|
+
else
|
2657
|
+
# Doing.logger.error('Fatal:', exception)
|
2658
|
+
Doing.logger.output_results
|
2659
|
+
true
|
2660
|
+
end
|
2459
2661
|
end
|
2460
2662
|
|
2461
2663
|
post do |global, _command, _options, _args|
|