doing 2.1.13 → 2.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.irbrc +1 -0
- data/.yardoc/checksums +14 -12
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +76 -0
- data/Gemfile.lock +9 -2
- data/README.md +56 -19
- data/bin/doing +218 -68
- data/docs/doc/Array.html +117 -3
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +6 -2
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +8 -4
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +340 -14
- data/docs/doc/Doing/Items.html +2 -2
- data/docs/doc/Doing/LogAdapter.html +1 -1
- data/docs/doc/Doing/Note.html +2 -2
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +103 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/Util/Backup.html +1 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +77 -71
- data/docs/doc/Doing.html +3 -3
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +279 -0
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +997 -118
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/_index.html +14 -9
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +41 -15
- data/docs/doc/index.html +41 -15
- data/docs/doc/method_list.html +449 -305
- data/docs/doc/top-level-namespace.html +2 -2
- data/docs/index.md +56 -19
- data/doing.gemspec +2 -0
- data/doing.rdoc +76 -9
- data/example_plugin.rb +2 -4
- data/lib/completion/_doing.zsh +17 -17
- data/lib/completion/doing.bash +25 -25
- data/lib/completion/doing.fish +18 -6
- data/lib/doing/array_chronify.rb +57 -0
- data/lib/doing/colors.rb +4 -0
- data/lib/doing/configuration.rb +6 -2
- data/lib/doing/item.rb +108 -0
- data/lib/doing/log_adapter.rb +3 -3
- data/lib/doing/numeric_chronify.rb +40 -0
- data/lib/doing/plugins/export/dayone_export.rb +1 -1
- data/lib/doing/plugins/export/json_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.rb +49 -90
- data/lib/doing/plugins/import/calendar_import.rb +13 -1
- data/lib/doing/plugins/import/doing_import.rb +12 -1
- data/lib/doing/plugins/import/timing_import.rb +13 -1
- data/lib/doing/prompt.rb +65 -1
- data/lib/doing/string.rb +137 -33
- data/lib/doing/string_chronify.rb +112 -14
- data/lib/doing/template_string.rb +1 -1
- data/lib/doing/time.rb +6 -6
- data/lib/doing/util_backup.rb +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +117 -106
- data/lib/doing.rb +36 -31
- data/lib/examples/plugins/say_export.rb +1 -4
- metadata +46 -2
data/bin/doing
CHANGED
@@ -70,6 +70,12 @@ if settings.dig('plugins', 'command_path')
|
|
70
70
|
commands_from File.expand_path(settings.dig('plugins', 'command_path'))
|
71
71
|
end
|
72
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
|
+
|
73
79
|
program_desc 'A CLI for a What Was I Doing system'
|
74
80
|
program_long_desc %(Doing uses a TaskPaper-like formatting to keep a plain text
|
75
81
|
record of what you've been doing, complete with tag-based time tracking. The
|
@@ -139,7 +145,7 @@ command %i[again resume] do |c|
|
|
139
145
|
|
140
146
|
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
141
147
|
c.arg_name 'TAG'
|
142
|
-
c.flag [:tag]
|
148
|
+
c.flag [:tag], type: TagArray
|
143
149
|
|
144
150
|
c.desc 'Repeat last entry matching search. Surround with
|
145
151
|
slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
|
@@ -170,16 +176,19 @@ command %i[again resume] do |c|
|
|
170
176
|
c.desc "Edit duplicated entry with #{Doing::Util.default_editor} before adding"
|
171
177
|
c.switch %i[e editor], negatable: false, default_value: false
|
172
178
|
|
173
|
-
c.desc '
|
179
|
+
c.desc 'Add a note'
|
174
180
|
c.arg_name 'TEXT'
|
175
181
|
c.flag %i[n note]
|
176
182
|
|
183
|
+
c.desc 'Prompt for note via multi-line input'
|
184
|
+
c.switch %i[ask], negatable: false, default_value: false
|
185
|
+
|
177
186
|
c.desc 'Select item to resume from a menu of matching entries'
|
178
187
|
c.switch %i[i interactive], negatable: false, default_value: false
|
179
188
|
|
180
189
|
c.action do |_global_options, options, _args|
|
181
190
|
options[:fuzzy] = false
|
182
|
-
tags = options[:tag].nil? ? [] : options[:tag]
|
191
|
+
tags = options[:tag].nil? ? [] : options[:tag]
|
183
192
|
|
184
193
|
options[:case] = options[:case].normalize_case
|
185
194
|
|
@@ -189,6 +198,11 @@ command %i[again resume] do |c|
|
|
189
198
|
options[:search] = search
|
190
199
|
end
|
191
200
|
|
201
|
+
note = Doing::Note.new(options[:note])
|
202
|
+
note.add(Doing::Prompt.read_lines(prompt: 'Add a note')) if options[:ask]
|
203
|
+
|
204
|
+
options[:note] = note
|
205
|
+
|
192
206
|
opts = options.dup
|
193
207
|
|
194
208
|
opts[:tag] = tags
|
@@ -216,7 +230,7 @@ command :cancel do |c|
|
|
216
230
|
|
217
231
|
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2). Wildcards allowed (*, ?)'
|
218
232
|
c.arg_name 'TAG'
|
219
|
-
c.flag [:tag]
|
233
|
+
c.flag [:tag], type: TagArray
|
220
234
|
|
221
235
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
|
222
236
|
c.arg_name 'BOOLEAN'
|
@@ -260,7 +274,7 @@ command :cancel do |c|
|
|
260
274
|
if options[:tag].nil?
|
261
275
|
tags = []
|
262
276
|
else
|
263
|
-
tags = options[:tag]
|
277
|
+
tags = options[:tag]
|
264
278
|
end
|
265
279
|
|
266
280
|
raise InvalidArgument, 'Only one argument allowed' if args.length > 1
|
@@ -324,19 +338,26 @@ command %i[done did] do |c|
|
|
324
338
|
c.switch %i[a archive], negatable: false, default_value: false
|
325
339
|
|
326
340
|
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm).
|
327
|
-
|
341
|
+
Used with --took, backdates start date)
|
328
342
|
c.arg_name 'DATE_STRING'
|
329
|
-
c.flag [
|
343
|
+
c.flag %i[at finished]
|
330
344
|
|
331
345
|
c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
|
332
346
|
c.arg_name 'DATE_STRING'
|
333
347
|
c.flag %i[b back started]
|
334
348
|
|
349
|
+
c.desc %(
|
350
|
+
Start and end times as a date/time range `doing done --from "1am to 8am"`.
|
351
|
+
Overrides other date flags.
|
352
|
+
)
|
353
|
+
c.arg_name 'TIME_RANGE'
|
354
|
+
c.flag [:from]
|
355
|
+
|
335
356
|
c.desc %(Set completion date to start date plus interval (XX[mhd] or HH:MM).
|
336
357
|
If used without the --back option, the start date will be moved back to allow
|
337
358
|
the completion date to be the current time.)
|
338
359
|
c.arg_name 'INTERVAL'
|
339
|
-
c.flag %i[t took]
|
360
|
+
c.flag %i[t took for]
|
340
361
|
|
341
362
|
c.desc 'Section'
|
342
363
|
c.arg_name 'NAME'
|
@@ -349,6 +370,9 @@ command %i[done did] do |c|
|
|
349
370
|
c.arg_name 'TEXT'
|
350
371
|
c.flag %i[n note]
|
351
372
|
|
373
|
+
c.desc 'Prompt for note via multi-line input'
|
374
|
+
c.switch %i[ask], negatable: false, default_value: false
|
375
|
+
|
352
376
|
c.desc 'Finish last entry not already marked @done'
|
353
377
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
354
378
|
|
@@ -360,30 +384,41 @@ command %i[done did] do |c|
|
|
360
384
|
took = 0
|
361
385
|
donedate = nil
|
362
386
|
|
363
|
-
if options[:
|
364
|
-
|
365
|
-
|
366
|
-
end
|
367
|
-
|
368
|
-
if options[:back]
|
369
|
-
date = options[:back].chronify(guess: :begin)
|
370
|
-
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
387
|
+
if options[:from]
|
388
|
+
date, finish_date = options[:from].split_date_range
|
389
|
+
finish_date ||= Time.now
|
371
390
|
else
|
372
|
-
|
373
|
-
|
391
|
+
if options[:took]
|
392
|
+
took = options[:took].chronify_qty
|
393
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
394
|
+
end
|
374
395
|
|
375
|
-
|
376
|
-
|
377
|
-
|
396
|
+
if options[:back]
|
397
|
+
date = options[:back].chronify(guess: :begin)
|
398
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
399
|
+
else
|
400
|
+
date = options[:took] ? Time.now - took : Time.now
|
401
|
+
end
|
378
402
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
403
|
+
if options[:at]
|
404
|
+
finish_date = options[:at].chronify(guess: :begin)
|
405
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
406
|
+
|
407
|
+
if options[:took]
|
408
|
+
date = finish_date - took
|
409
|
+
else
|
410
|
+
date ||= finish_date
|
411
|
+
end
|
412
|
+
elsif options[:took]
|
413
|
+
finish_date = date + took
|
414
|
+
else
|
415
|
+
finish_date = Time.now
|
416
|
+
end
|
384
417
|
end
|
385
418
|
|
386
419
|
if options[:date]
|
420
|
+
finish_date = wwid.verify_duration(date, finish_date) unless options[:took] || options[:from]
|
421
|
+
|
387
422
|
donedate = finish_date.strftime('%F %R')
|
388
423
|
end
|
389
424
|
|
@@ -393,9 +428,14 @@ command %i[done did] do |c|
|
|
393
428
|
section = settings['current_section']
|
394
429
|
end
|
395
430
|
|
431
|
+
|
396
432
|
note = Doing::Note.new
|
397
433
|
note.add(options[:note]) if options[:note]
|
398
434
|
|
435
|
+
if options[:ask] && !options[:editor]
|
436
|
+
note.add(Doing::Prompt.read_lines(prompt: 'Add a note'))
|
437
|
+
end
|
438
|
+
|
399
439
|
if options[:editor]
|
400
440
|
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
401
441
|
is_new = false
|
@@ -420,6 +460,12 @@ command %i[done did] do |c|
|
|
420
460
|
raise EmptyInput, 'No content' unless input && !input.empty?
|
421
461
|
|
422
462
|
d, title, note = wwid.format_input(input)
|
463
|
+
|
464
|
+
if options[:ask]
|
465
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
466
|
+
note.add(ask_note) unless ask_note.empty?
|
467
|
+
end
|
468
|
+
|
423
469
|
date = d.nil? ? date : d
|
424
470
|
new_entry = Doing::Item.new(date, title, section, note)
|
425
471
|
if new_entry.should_finish?
|
@@ -449,7 +495,6 @@ command %i[done did] do |c|
|
|
449
495
|
if options[:remove]
|
450
496
|
wwid.tag_last({ tags: ['done'], count: 1, section: section, remove: true })
|
451
497
|
else
|
452
|
-
note = options[:note] ? Doing::Note.new(options[:note]) : nil
|
453
498
|
opt = {
|
454
499
|
archive: options[:archive],
|
455
500
|
back: finish_date,
|
@@ -464,12 +509,13 @@ command %i[done did] do |c|
|
|
464
509
|
wwid.tag_last(opt)
|
465
510
|
end
|
466
511
|
elsif !args.empty?
|
467
|
-
note = Doing::Note.new(options[:note])
|
468
512
|
d, title, new_note = wwid.format_input([args.join(' '), note.strip_lines.join("\n")].join("\n"))
|
469
513
|
date = d.nil? ? date : d
|
514
|
+
new_note.add(options[:note])
|
470
515
|
title.chomp!
|
471
516
|
section = 'Archive' if options[:archive]
|
472
517
|
new_entry = Doing::Item.new(date, title, section, new_note)
|
518
|
+
|
473
519
|
if new_entry.should_finish?
|
474
520
|
if new_entry.should_time?
|
475
521
|
new_entry.tag('done', value: donedate)
|
@@ -477,12 +523,14 @@ command %i[done did] do |c|
|
|
477
523
|
new_entry.tag('done')
|
478
524
|
end
|
479
525
|
end
|
526
|
+
|
480
527
|
Doing::Hooks.trigger :pre_entry_add, wwid, new_entry
|
481
528
|
wwid.content.push(new_entry)
|
482
529
|
Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
|
483
530
|
wwid.write(wwid.doing_file)
|
484
531
|
Doing.logger.info('Entry Added:', new_entry.title)
|
485
532
|
elsif $stdin.stat.size.positive?
|
533
|
+
note = Doing::Note.new(options[:note])
|
486
534
|
d, title, note = wwid.format_input($stdin.read.strip)
|
487
535
|
unless d.nil?
|
488
536
|
Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
|
@@ -530,7 +578,7 @@ command :finish do |c|
|
|
530
578
|
|
531
579
|
c.desc 'Set the completed date to the start date plus XX[hmd]'
|
532
580
|
c.arg_name 'INTERVAL'
|
533
|
-
c.flag %i[t took]
|
581
|
+
c.flag %i[t took for]
|
534
582
|
|
535
583
|
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
|
536
584
|
c.arg_name 'DATE_STRING'
|
@@ -539,7 +587,7 @@ command :finish do |c|
|
|
539
587
|
c.desc 'Finish the last X entries containing TAG.
|
540
588
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
541
589
|
c.arg_name 'TAG'
|
542
|
-
c.flag [:tag]
|
590
|
+
c.flag [:tag], type: TagArray
|
543
591
|
|
544
592
|
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")'
|
545
593
|
c.arg_name 'QUERY'
|
@@ -618,7 +666,7 @@ command :finish do |c|
|
|
618
666
|
if options[:tag].nil?
|
619
667
|
tags = []
|
620
668
|
else
|
621
|
-
tags = options[:tag]
|
669
|
+
tags = options[:tag]
|
622
670
|
end
|
623
671
|
|
624
672
|
raise InvalidArgument, 'Only one argument allowed' if args.length > 1
|
@@ -681,6 +729,9 @@ command :later do |c|
|
|
681
729
|
c.arg_name 'TEXT'
|
682
730
|
c.flag %i[n note]
|
683
731
|
|
732
|
+
c.desc 'Prompt for note via multi-line input'
|
733
|
+
c.switch %i[ask], negatable: false, default_value: false
|
734
|
+
|
684
735
|
c.action do |_global_options, options, args|
|
685
736
|
if options[:back]
|
686
737
|
date = options[:back].chronify(guess: :begin)
|
@@ -689,23 +740,36 @@ command :later do |c|
|
|
689
740
|
date = Time.now
|
690
741
|
end
|
691
742
|
|
692
|
-
|
743
|
+
ask_note = options[:ask] && !options[:editor] && args.count.positive? ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
|
744
|
+
|
745
|
+
if options[:editor]
|
693
746
|
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
694
747
|
|
748
|
+
input = ''
|
695
749
|
input += date.strftime('%F %R | ')
|
696
750
|
input += args.empty? ? '' : args.join(' ')
|
751
|
+
input += "\n#{options[:note]}" if options[:note]
|
752
|
+
input += "\n#{ask_note}" unless ask_note.empty?
|
753
|
+
|
697
754
|
input = wwid.fork_editor(input).strip
|
698
|
-
raise EmptyInput, 'No content' unless input && !input.empty?
|
699
755
|
|
700
756
|
d, title, note = wwid.format_input(input)
|
701
|
-
|
757
|
+
raise EmptyInput, 'No content' if title.empty?
|
758
|
+
|
702
759
|
note.add(options[:note]) if options[:note]
|
760
|
+
if ask_note.empty? && options[:ask]
|
761
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
762
|
+
note.add(ask_note) unless ask_note.empty?
|
763
|
+
end
|
764
|
+
|
765
|
+
date = d.nil? ? date : d
|
703
766
|
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
704
767
|
wwid.write(wwid.doing_file)
|
705
768
|
elsif !args.empty?
|
706
769
|
d, title, note = wwid.format_input(args.join(' '))
|
707
770
|
date = d.nil? ? date : d
|
708
771
|
note.add(options[:note]) if options[:note]
|
772
|
+
note.add(ask_note) unless ask_note.empty?
|
709
773
|
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
710
774
|
wwid.write(wwid.doing_file)
|
711
775
|
elsif $stdin.stat.size.positive?
|
@@ -715,10 +779,20 @@ command :later do |c|
|
|
715
779
|
date = d
|
716
780
|
end
|
717
781
|
note.add(options[:note]) if options[:note]
|
782
|
+
note.add(ask_note) unless ask_note.empty?
|
718
783
|
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
719
784
|
wwid.write(wwid.doing_file)
|
720
785
|
else
|
721
|
-
|
786
|
+
title = Doing::Prompt.read_line(prompt: 'Entry content')
|
787
|
+
raise EmptyInput, 'You must provide content when creating a new entry' if title.strip.empty?
|
788
|
+
|
789
|
+
note = Doing::Note.new
|
790
|
+
res = Doing::Prompt.yn('Add a note', default_response: false)
|
791
|
+
ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
|
792
|
+
note.add(ask_note)
|
793
|
+
|
794
|
+
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
795
|
+
wwid.write(wwid.doing_file)
|
722
796
|
end
|
723
797
|
end
|
724
798
|
end
|
@@ -754,7 +828,7 @@ command %i[mark flag] do |c|
|
|
754
828
|
c.desc 'Flag the last entry containing TAG.
|
755
829
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
756
830
|
c.arg_name 'TAG'
|
757
|
-
c.flag [:tag]
|
831
|
+
c.flag [:tag], type: TagArray
|
758
832
|
|
759
833
|
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")'
|
760
834
|
c.arg_name 'QUERY'
|
@@ -799,7 +873,7 @@ command %i[mark flag] do |c|
|
|
799
873
|
if options[:tag].nil?
|
800
874
|
search_tags = []
|
801
875
|
else
|
802
|
-
search_tags = options[:tag]
|
876
|
+
search_tags = options[:tag]
|
803
877
|
end
|
804
878
|
|
805
879
|
if options[:interactive]
|
@@ -881,6 +955,9 @@ command :meanwhile do |c|
|
|
881
955
|
c.arg_name 'TEXT'
|
882
956
|
c.flag %i[n note]
|
883
957
|
|
958
|
+
c.desc 'Prompt for note via multi-line input'
|
959
|
+
c.switch %i[ask], negatable: false, default_value: false
|
960
|
+
|
884
961
|
c.action do |_global_options, options, args|
|
885
962
|
if options[:back]
|
886
963
|
date = options[:back].chronify(guess: :begin)
|
@@ -897,10 +974,15 @@ command :meanwhile do |c|
|
|
897
974
|
end
|
898
975
|
input = ''
|
899
976
|
|
977
|
+
ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : []
|
978
|
+
|
900
979
|
if options[:editor]
|
901
980
|
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
902
981
|
input += date.strftime('%F %R | ')
|
903
982
|
input += args.join(' ') unless args.empty?
|
983
|
+
input += "\n#{options[:note]}" if options[:note]
|
984
|
+
input += "\n#{ask_note}" unless ask_note.empty?
|
985
|
+
|
904
986
|
input = wwid.fork_editor(input).strip
|
905
987
|
elsif !args.empty?
|
906
988
|
input = args.join(' ')
|
@@ -919,10 +1001,9 @@ command :meanwhile do |c|
|
|
919
1001
|
note = []
|
920
1002
|
end
|
921
1003
|
|
922
|
-
|
923
|
-
note.
|
924
|
-
|
925
|
-
note = nil
|
1004
|
+
unless options[:editor]
|
1005
|
+
note.add(options[:note]) if options[:note]
|
1006
|
+
note.add(ask_note) unless ask_note.empty?
|
926
1007
|
end
|
927
1008
|
|
928
1009
|
wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:archive], note: note })
|
@@ -958,7 +1039,7 @@ command :note do |c|
|
|
958
1039
|
|
959
1040
|
c.desc 'Add/remove note from last entry matching tag. Wildcards allowed (*, ?)'
|
960
1041
|
c.arg_name 'TAG'
|
961
|
-
c.flag [:tag]
|
1042
|
+
c.flag [:tag], type: TagArray
|
962
1043
|
|
963
1044
|
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")'
|
964
1045
|
c.arg_name 'QUERY'
|
@@ -988,6 +1069,9 @@ command :note do |c|
|
|
988
1069
|
c.desc 'Select item for new note from a menu of matching entries'
|
989
1070
|
c.switch %i[i interactive], negatable: false, default_value: false
|
990
1071
|
|
1072
|
+
c.desc 'Prompt for note via multi-line input'
|
1073
|
+
c.switch %i[ask], negatable: false, default_value: false
|
1074
|
+
|
991
1075
|
c.action do |_global_options, options, args|
|
992
1076
|
options[:fuzzy] = false
|
993
1077
|
if options[:section]
|
@@ -1014,8 +1098,9 @@ command :note do |c|
|
|
1014
1098
|
|
1015
1099
|
last_note = last_entry.note || Doing::Note.new
|
1016
1100
|
new_note = Doing::Note.new
|
1101
|
+
ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
|
1017
1102
|
|
1018
|
-
if options[:editor] || (args.empty? && $stdin.stat.size.zero? && !options[:remove])
|
1103
|
+
if options[:editor] || (args.empty? && $stdin.stat.size.zero? && !options[:remove] && !options[:ask])
|
1019
1104
|
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
1020
1105
|
|
1021
1106
|
input = !args.empty? ? args.join(' ') : ''
|
@@ -1026,7 +1111,9 @@ command :note do |c|
|
|
1026
1111
|
prev_input = last_entry.note || Doing::Note.new
|
1027
1112
|
end
|
1028
1113
|
|
1114
|
+
|
1029
1115
|
input = prev_input.add(input)
|
1116
|
+
input.add(ask_note) unless ask_note.empty?
|
1030
1117
|
|
1031
1118
|
input = wwid.fork_editor(prev_input.strip_lines.join("\n"), message: nil).strip
|
1032
1119
|
note = input
|
@@ -1037,9 +1124,12 @@ command :note do |c|
|
|
1037
1124
|
elsif $stdin.stat.size.positive?
|
1038
1125
|
new_note.add($stdin.read.strip)
|
1039
1126
|
else
|
1040
|
-
raise EmptyInput, 'You must provide content when adding a note' unless options[:remove]
|
1127
|
+
raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || !ask_note.empty?
|
1128
|
+
|
1041
1129
|
end
|
1042
1130
|
|
1131
|
+
new_note.add(ask_note) unless ask_note.empty?
|
1132
|
+
|
1043
1133
|
if last_note.equal?(new_note)
|
1044
1134
|
Doing.logger.debug('Skipped:', 'No note change')
|
1045
1135
|
else
|
@@ -1058,10 +1148,13 @@ long_desc %(Record what you're starting now, or backdate the start time using na
|
|
1058
1148
|
|
1059
1149
|
A parenthetical at the end of the entry will be converted to a note.
|
1060
1150
|
|
1061
|
-
Run
|
1151
|
+
Run without arguments to create a new entry interactively.
|
1152
|
+
|
1153
|
+
Run with --editor to create a new entry using #{Doing::Util.default_editor}.)
|
1062
1154
|
arg_name 'ENTRY'
|
1063
1155
|
command %i[now next] do |c|
|
1064
|
-
c.example 'doing now', desc:
|
1156
|
+
c.example 'doing now', desc: 'Create a new entry with interactive prompts'
|
1157
|
+
c.example 'doing now -e', desc: "Open #{Doing::Util.default_editor} to input an entry and optional note"
|
1065
1158
|
c.example 'doing now working on a new project', desc: 'Add a new entry at the current time'
|
1066
1159
|
c.example 'doing now debugging @project2', desc: 'Add an entry with a tag'
|
1067
1160
|
c.example 'doing now adding an entry (with a note)', desc: 'Parenthetical at end is converted to note'
|
@@ -1085,6 +1178,9 @@ command %i[now next] do |c|
|
|
1085
1178
|
c.arg_name 'TEXT'
|
1086
1179
|
c.flag %i[n note]
|
1087
1180
|
|
1181
|
+
c.desc 'Prompt for note via multi-line input'
|
1182
|
+
c.switch %i[ask], negatable: false, default_value: false
|
1183
|
+
|
1088
1184
|
# c.desc "Edit entry with specified app"
|
1089
1185
|
# c.arg_name 'editor_app'
|
1090
1186
|
# # c.flag [:a, :app]
|
@@ -1104,23 +1200,32 @@ command %i[now next] do |c|
|
|
1104
1200
|
options[:section] = settings['current_section']
|
1105
1201
|
end
|
1106
1202
|
|
1107
|
-
|
1203
|
+
ask_note = options[:ask] && !options[:editor] && args.count.positive? ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
|
1204
|
+
|
1205
|
+
if options[:editor]
|
1108
1206
|
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
1109
1207
|
|
1110
1208
|
input = date.strftime('%F %R | ')
|
1111
1209
|
input += args.join(' ') unless args.empty?
|
1210
|
+
input += "\n#{options[:note]}" if options[:note]
|
1211
|
+
input += "\n#{ask_note}" unless ask_note.empty?
|
1112
1212
|
input = wwid.fork_editor(input).strip
|
1113
1213
|
|
1114
|
-
raise EmptyInput, 'No content' if input.empty?
|
1115
|
-
|
1116
1214
|
date, title, note = wwid.format_input(input)
|
1117
|
-
|
1215
|
+
raise EmptyInput, 'No content' if title.strip.empty?
|
1216
|
+
|
1217
|
+
if ask_note.empty? && options[:ask]
|
1218
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
1219
|
+
note.add(ask_note) unless ask_note.empty?
|
1220
|
+
end
|
1221
|
+
|
1118
1222
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1119
1223
|
wwid.write(wwid.doing_file)
|
1120
1224
|
elsif args.length.positive?
|
1121
1225
|
d, title, note = wwid.format_input(args.join(' '))
|
1122
1226
|
date = d.nil? ? date : d
|
1123
1227
|
note.add(options[:note]) if options[:note]
|
1228
|
+
note.add(ask_note) unless ask_note.empty?
|
1124
1229
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1125
1230
|
wwid.write(wwid.doing_file)
|
1126
1231
|
elsif $stdin.stat.size.positive?
|
@@ -1131,10 +1236,26 @@ command %i[now next] do |c|
|
|
1131
1236
|
date = d
|
1132
1237
|
end
|
1133
1238
|
note.add(options[:note]) if options[:note]
|
1239
|
+
if ask_note.empty? && options[:ask]
|
1240
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
1241
|
+
note.add(ask_note) unless ask_note.empty?
|
1242
|
+
end
|
1134
1243
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1135
1244
|
wwid.write(wwid.doing_file)
|
1136
1245
|
else
|
1137
|
-
|
1246
|
+
tags = wwid.all_tags(wwid.content)
|
1247
|
+
$stderr.puts Doing::Color.boldgreen("Add a new entry. Tab will autocomplete known tags. Ctrl-c to cancel.")
|
1248
|
+
title = Doing::Prompt.read_line(prompt: 'Entry content', completions: tags)
|
1249
|
+
raise EmptyInput, 'You must provide content when creating a new entry' if title.strip.empty?
|
1250
|
+
|
1251
|
+
note = Doing::Note.new
|
1252
|
+
note.add(options[:note]) if options[:note]
|
1253
|
+
res = Doing::Prompt.yn('Add a note', default_response: false)
|
1254
|
+
ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
|
1255
|
+
note.add(ask_note)
|
1256
|
+
|
1257
|
+
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1258
|
+
wwid.write(wwid.doing_file)
|
1138
1259
|
end
|
1139
1260
|
end
|
1140
1261
|
end
|
@@ -1283,7 +1404,11 @@ command :select do |c|
|
|
1283
1404
|
|
1284
1405
|
c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote, e.g. `--query "\'search"'
|
1285
1406
|
c.arg_name 'QUERY'
|
1286
|
-
c.flag %i[q query
|
1407
|
+
c.flag %i[q query]
|
1408
|
+
|
1409
|
+
c.desc 'Select from entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
|
1410
|
+
c.arg_name 'QUERY'
|
1411
|
+
c.flag [:search]
|
1287
1412
|
|
1288
1413
|
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
1289
1414
|
c.arg_name 'QUERY'
|
@@ -1421,7 +1546,7 @@ command :tag do |c|
|
|
1421
1546
|
c.desc 'Tag the last X entries containing TAG.
|
1422
1547
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
1423
1548
|
c.arg_name 'TAG'
|
1424
|
-
c.flag [:tag]
|
1549
|
+
c.flag [:tag], type: TagArray
|
1425
1550
|
|
1426
1551
|
c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
|
1427
1552
|
c.arg_name 'QUERY'
|
@@ -1453,7 +1578,7 @@ command :tag do |c|
|
|
1453
1578
|
|
1454
1579
|
c.action do |_global_options, options, args|
|
1455
1580
|
options[:fuzzy] = false
|
1456
|
-
raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
|
1581
|
+
# raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
|
1457
1582
|
|
1458
1583
|
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
1459
1584
|
|
@@ -1467,17 +1592,21 @@ command :tag do |c|
|
|
1467
1592
|
if options[:tag].nil?
|
1468
1593
|
search_tags = []
|
1469
1594
|
else
|
1470
|
-
search_tags = options[:tag]
|
1595
|
+
search_tags = options[:tag]
|
1471
1596
|
end
|
1472
1597
|
|
1473
1598
|
if options[:autotag]
|
1474
1599
|
tags = []
|
1475
1600
|
else
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1601
|
+
if args.empty?
|
1602
|
+
tags = []
|
1603
|
+
else
|
1604
|
+
tags = if args.join('') =~ /,/
|
1605
|
+
args.join('').split(/ *, */)
|
1606
|
+
else
|
1607
|
+
args.join(' ').split(' ') # in case tags are quoted as one arg
|
1608
|
+
end
|
1609
|
+
end
|
1481
1610
|
|
1482
1611
|
tags.map! { |tag| tag.sub(/^@/, '').strip }
|
1483
1612
|
end
|
@@ -1621,6 +1750,9 @@ command %i[grep search] do |c|
|
|
1621
1750
|
c.arg_name 'TYPE'
|
1622
1751
|
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1623
1752
|
|
1753
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
1754
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
1755
|
+
|
1624
1756
|
c.desc "Edit matching entries with #{Doing::Util.default_editor}"
|
1625
1757
|
c.switch %i[e editor], negatable: false, default_value: false
|
1626
1758
|
|
@@ -1688,7 +1820,7 @@ command :last do |c|
|
|
1688
1820
|
|
1689
1821
|
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
1690
1822
|
c.arg_name 'TAG'
|
1691
|
-
c.flag [:tag]
|
1823
|
+
c.flag [:tag], type: TagArray
|
1692
1824
|
|
1693
1825
|
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
|
1694
1826
|
c.arg_name 'BOOLEAN'
|
@@ -1698,6 +1830,9 @@ command :last do |c|
|
|
1698
1830
|
c.arg_name 'QUERY'
|
1699
1831
|
c.flag [:search]
|
1700
1832
|
|
1833
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
1834
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
1835
|
+
|
1701
1836
|
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
1702
1837
|
c.arg_name 'QUERY'
|
1703
1838
|
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
@@ -1725,7 +1860,7 @@ command :last do |c|
|
|
1725
1860
|
if options[:tag].nil?
|
1726
1861
|
options[:tag] = []
|
1727
1862
|
else
|
1728
|
-
options[:tag] = options[:tag]
|
1863
|
+
options[:tag] = options[:tag]
|
1729
1864
|
options[:bool] = options[:bool].normalize_bool
|
1730
1865
|
end
|
1731
1866
|
|
@@ -1752,6 +1887,7 @@ command :last do |c|
|
|
1752
1887
|
search: options[:search],
|
1753
1888
|
fuzzy: options[:fuzzy],
|
1754
1889
|
case: options[:case],
|
1890
|
+
hilite: options[:hilite],
|
1755
1891
|
negate: options[:not],
|
1756
1892
|
tag: options[:tag],
|
1757
1893
|
tag_bool: options[:bool],
|
@@ -1855,7 +1991,7 @@ command :show do |c|
|
|
1855
1991
|
|
1856
1992
|
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'
|
1857
1993
|
c.arg_name 'TAG'
|
1858
|
-
c.flag [:tag]
|
1994
|
+
c.flag [:tag], type: TagArray
|
1859
1995
|
|
1860
1996
|
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
1861
1997
|
c.arg_name 'QUERY'
|
@@ -1897,6 +2033,9 @@ command :show do |c|
|
|
1897
2033
|
c.arg_name 'QUERY'
|
1898
2034
|
c.flag [:search]
|
1899
2035
|
|
2036
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
2037
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
2038
|
+
|
1900
2039
|
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
1901
2040
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1902
2041
|
|
@@ -1988,7 +2127,7 @@ command :show do |c|
|
|
1988
2127
|
section ||= 'All'
|
1989
2128
|
end
|
1990
2129
|
|
1991
|
-
tags.concat(options[:tag]
|
2130
|
+
tags.concat(options[:tag]) if options[:tag]
|
1992
2131
|
|
1993
2132
|
options[:times] = true if options[:totals]
|
1994
2133
|
|
@@ -2044,6 +2183,7 @@ command :show do |c|
|
|
2044
2183
|
opt[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2045
2184
|
opt[:count] = options[:count].to_i
|
2046
2185
|
opt[:highlight] = true
|
2186
|
+
opt[:hilite] = options[:hilite]
|
2047
2187
|
opt[:order] = options[:sort].normalize_order
|
2048
2188
|
opt[:tag] = nil
|
2049
2189
|
opt[:tag_order] = options[:tag_order].normalize_order
|
@@ -2370,6 +2510,9 @@ command :view do |c|
|
|
2370
2510
|
c.arg_name 'QUERY'
|
2371
2511
|
c.flag [:search]
|
2372
2512
|
|
2513
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
2514
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
2515
|
+
|
2373
2516
|
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
2374
2517
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2375
2518
|
|
@@ -2530,6 +2673,7 @@ command :view do |c|
|
|
2530
2673
|
opts[:count] = count
|
2531
2674
|
opts[:format] = date_format
|
2532
2675
|
opts[:highlight] = options[:color]
|
2676
|
+
opts[:hilite] = options[:hilite]
|
2533
2677
|
opts[:only_timed] = only_timed
|
2534
2678
|
opts[:order] = order
|
2535
2679
|
opts[:output] = options[:interactive] ? nil : options[:output]
|
@@ -3004,7 +3148,7 @@ command %i[archive move] do |c|
|
|
3004
3148
|
|
3005
3149
|
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands'
|
3006
3150
|
c.arg_name 'TAG'
|
3007
|
-
c.flag [:tag]
|
3151
|
+
c.flag [:tag], type: TagArray
|
3008
3152
|
|
3009
3153
|
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
|
3010
3154
|
c.arg_name 'BOOLEAN'
|
@@ -3053,7 +3197,7 @@ command %i[archive move] do |c|
|
|
3053
3197
|
|
3054
3198
|
raise InvalidArgument, '--keep and --count can not be used together' if options[:keep] && options[:count]
|
3055
3199
|
|
3056
|
-
tags.concat(options[:tag]
|
3200
|
+
tags.concat(options[:tag]) if options[:tag]
|
3057
3201
|
|
3058
3202
|
search = nil
|
3059
3203
|
|
@@ -3113,7 +3257,7 @@ command :import do |c|
|
|
3113
3257
|
|
3114
3258
|
c.desc 'Tag all imported entries'
|
3115
3259
|
c.arg_name 'TAGS'
|
3116
|
-
c.flag
|
3260
|
+
c.flag %i[t tag]
|
3117
3261
|
|
3118
3262
|
c.desc 'Autotag entries'
|
3119
3263
|
c.switch :autotag, negatable: true, default_value: true
|
@@ -3148,6 +3292,12 @@ command :import do |c|
|
|
3148
3292
|
options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
|
3149
3293
|
end
|
3150
3294
|
|
3295
|
+
if options[:search]
|
3296
|
+
search = options[:search]
|
3297
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
3298
|
+
options[:search] = search
|
3299
|
+
end
|
3300
|
+
|
3151
3301
|
if options[:from]
|
3152
3302
|
date_string = options[:from]
|
3153
3303
|
if date_string =~ / (to|through|thru|(un)?til|-+) /
|
@@ -3156,7 +3306,7 @@ command :import do |c|
|
|
3156
3306
|
finish = dates[2].chronify(guess: :end)
|
3157
3307
|
else
|
3158
3308
|
start = date_string.chronify(guess: :begin)
|
3159
|
-
finish =
|
3309
|
+
finish = date_string.chronify(guess: :end)
|
3160
3310
|
end
|
3161
3311
|
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
3162
3312
|
dates = [start, finish]
|