doing 2.1.13 → 2.1.17
Sign up to get free protection for your applications and to get access to all the features.
- 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]
|