doing 2.1.16 → 2.1.21
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/.yardoc/checksums +13 -12
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +136 -53
- data/Gemfile.lock +11 -11
- data/README.md +1 -1
- data/Rakefile +10 -4
- data/bin/doing +146 -169
- data/docs/doc/Array.html +3 -3
- data/docs/doc/BooleanTermParser/Clause.html +3 -3
- data/docs/doc/BooleanTermParser/Operator.html +3 -3
- data/docs/doc/BooleanTermParser/Query.html +3 -3
- data/docs/doc/BooleanTermParser/QueryParser.html +3 -3
- data/docs/doc/BooleanTermParser/QueryTransformer.html +3 -3
- data/docs/doc/BooleanTermParser.html +3 -3
- data/docs/doc/Doing/Color.html +8 -4
- data/docs/doc/Doing/Completion.html +3 -3
- data/docs/doc/Doing/Configuration.html +7 -5
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +3 -3
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +3 -3
- data/docs/doc/Doing/Errors/DoingStandardError.html +3 -3
- data/docs/doc/Doing/Errors/EmptyInput.html +3 -3
- data/docs/doc/Doing/Errors/NoResults.html +3 -3
- data/docs/doc/Doing/Errors/PluginException.html +3 -3
- data/docs/doc/Doing/Errors/UserCancelled.html +3 -3
- data/docs/doc/Doing/Errors/WrongCommand.html +3 -3
- data/docs/doc/Doing/Errors.html +3 -3
- data/docs/doc/Doing/Hooks.html +3 -3
- data/docs/doc/Doing/Item.html +121 -3
- data/docs/doc/Doing/Items.html +3 -3
- data/docs/doc/Doing/LogAdapter.html +3 -3
- data/docs/doc/Doing/Note.html +3 -3
- data/docs/doc/Doing/Pager.html +3 -3
- data/docs/doc/Doing/Plugins.html +3 -3
- data/docs/doc/Doing/Prompt.html +3 -3
- data/docs/doc/Doing/Section.html +3 -3
- data/docs/doc/Doing/TemplateString.html +4 -4
- data/docs/doc/Doing/Util/Backup.html +3 -3
- data/docs/doc/Doing/Util.html +3 -3
- data/docs/doc/Doing/WWID.html +66 -8
- data/docs/doc/Doing.html +4 -4
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +3 -3
- data/docs/doc/GLI/Commands.html +3 -3
- data/docs/doc/GLI.html +3 -3
- data/docs/doc/Hash.html +3 -3
- data/docs/doc/Numeric.html +3 -3
- data/docs/doc/PhraseParser/Operator.html +3 -3
- data/docs/doc/PhraseParser/PhraseClause.html +3 -3
- data/docs/doc/PhraseParser/Query.html +3 -3
- data/docs/doc/PhraseParser/QueryParser.html +3 -3
- data/docs/doc/PhraseParser/QueryTransformer.html +3 -3
- data/docs/doc/PhraseParser/TermClause.html +3 -3
- data/docs/doc/PhraseParser.html +3 -3
- data/docs/doc/Status.html +3 -3
- data/docs/doc/String.html +230 -17
- data/docs/doc/Symbol.html +3 -3
- data/docs/doc/Time.html +3 -3
- data/docs/doc/_index.html +4 -4
- data/docs/doc/file.README.html +4 -4
- data/docs/doc/frames.html +1 -1
- data/docs/doc/index.html +4 -4
- data/docs/doc/method_list.html +311 -239
- data/docs/doc/top-level-namespace.html +94 -3
- data/doing.gemspec +1 -1
- data/doing.rdoc +35 -6
- data/lib/completion/_doing.zsh +10 -10
- data/lib/completion/doing.bash +16 -16
- data/lib/completion/doing.fish +97 -15
- data/lib/doing/colors.rb +4 -0
- data/lib/doing/completion/fish_completion.rb +80 -11
- data/lib/doing/configuration.rb +3 -1
- data/lib/doing/hash.rb +1 -1
- data/lib/doing/item.rb +51 -0
- data/lib/doing/items.rb +3 -1
- data/lib/doing/log_adapter.rb +2 -2
- data/lib/doing/pager.rb +1 -1
- data/lib/doing/plugins/export/dayone_export.rb +1 -1
- data/lib/doing/plugins/export/markdown_export.rb +1 -1
- data/lib/doing/plugins/export/template_export.rb +2 -0
- data/lib/doing/prompt.rb +16 -5
- data/lib/doing/string.rb +54 -0
- data/lib/doing/string_chronify.rb +55 -17
- data/lib/doing/types.rb +19 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +80 -52
- data/lib/examples/commands/later.rb +32 -0
- data/lib/helpers/threaded_tests.rb +250 -0
- metadata +9 -6
data/bin/doing
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
5
5
|
require 'gli'
|
6
6
|
require 'doing'
|
7
|
+
require 'doing/types'
|
7
8
|
require 'tempfile'
|
8
9
|
require 'pp'
|
9
10
|
|
@@ -25,12 +26,13 @@ version Doing::VERSION
|
|
25
26
|
hide_commands_without_desc true
|
26
27
|
autocomplete_commands true
|
27
28
|
|
28
|
-
REGEX_BOOL = /^(?:and|all|any|or|not|none|p(?:at(?:tern)?)?)$/i
|
29
|
-
REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i
|
30
|
-
REGEX_VALUE_QUERY = /^(?:!)?@?(?:\S+) +(?:!?[<>=][=*]?|[$*^]=) +(?:.*?)$/
|
31
|
-
|
32
29
|
InvalidExportType = Class.new(RuntimeError)
|
33
30
|
MissingConfigFile = Class.new(RuntimeError)
|
31
|
+
TagArray = Class.new(Array)
|
32
|
+
DateBeginString = Class.new(DateTime)
|
33
|
+
DateEndString = Class.new(DateTime)
|
34
|
+
DateRangeString = Class.new(Array)
|
35
|
+
DateIntervalString = Class.new(DateTime)
|
34
36
|
|
35
37
|
colors = Doing::Color
|
36
38
|
wwid = Doing::WWID.new
|
@@ -70,7 +72,42 @@ if settings.dig('plugins', 'command_path')
|
|
70
72
|
commands_from File.expand_path(settings.dig('plugins', 'command_path'))
|
71
73
|
end
|
72
74
|
|
73
|
-
|
75
|
+
accept DateBeginString do |value|
|
76
|
+
if value =~ REGEX_TIME
|
77
|
+
res = value
|
78
|
+
else
|
79
|
+
res = value.chronify(guess: :begin, future: false)
|
80
|
+
end
|
81
|
+
raise InvalidTimeExpression, 'Invalid start date' unless res
|
82
|
+
|
83
|
+
res
|
84
|
+
end
|
85
|
+
|
86
|
+
accept DateEndString do |value|
|
87
|
+
if value =~ REGEX_TIME
|
88
|
+
res = value
|
89
|
+
else
|
90
|
+
res = value.chronify(guess: :end, future: false)
|
91
|
+
end
|
92
|
+
raise InvalidTimeExpression, 'Invalid end date' unless res
|
93
|
+
|
94
|
+
res
|
95
|
+
end
|
96
|
+
|
97
|
+
accept DateRangeString do |value|
|
98
|
+
start, finish = value.split_date_range
|
99
|
+
raise InvalidTimeExpression, 'Invalid range' unless start
|
100
|
+
|
101
|
+
finish ||= Time.now
|
102
|
+
[start, finish]
|
103
|
+
end
|
104
|
+
|
105
|
+
accept DateIntervalString do |value|
|
106
|
+
res = value.chronify_qty
|
107
|
+
raise InvalidTimeExpression, 'Invalid time quantity' unless res
|
108
|
+
|
109
|
+
res
|
110
|
+
end
|
74
111
|
|
75
112
|
accept TagArray do |value|
|
76
113
|
value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '')}.map(&:strip)
|
@@ -143,6 +180,10 @@ command %i[again resume] do |c|
|
|
143
180
|
c.arg_name 'SECTION_NAME'
|
144
181
|
c.flag [:in]
|
145
182
|
|
183
|
+
c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
|
184
|
+
c.arg_name 'DATE_STRING'
|
185
|
+
c.flag %i[b back started], type: DateBeginString
|
186
|
+
|
146
187
|
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
147
188
|
c.arg_name 'TAG'
|
148
189
|
c.flag [:tag], type: TagArray
|
@@ -198,6 +239,13 @@ command %i[again resume] do |c|
|
|
198
239
|
options[:search] = search
|
199
240
|
end
|
200
241
|
|
242
|
+
if options[:back]
|
243
|
+
date = options[:back]
|
244
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
245
|
+
else
|
246
|
+
date = Time.now
|
247
|
+
end
|
248
|
+
|
201
249
|
note = Doing::Note.new(options[:note])
|
202
250
|
note.add(Doing::Prompt.read_lines(prompt: 'Add a note')) if options[:ask]
|
203
251
|
|
@@ -208,6 +256,7 @@ command %i[again resume] do |c|
|
|
208
256
|
opts[:tag] = tags
|
209
257
|
opts[:tag_bool] = options[:bool].normalize_bool
|
210
258
|
opts[:interactive] = options[:interactive]
|
259
|
+
opts[:date] = date
|
211
260
|
|
212
261
|
wwid.repeat_last(opts)
|
213
262
|
end
|
@@ -340,24 +389,24 @@ command %i[done did] do |c|
|
|
340
389
|
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm).
|
341
390
|
Used with --took, backdates start date)
|
342
391
|
c.arg_name 'DATE_STRING'
|
343
|
-
c.flag %i[at finished]
|
392
|
+
c.flag %i[at finished], type: DateEndString
|
344
393
|
|
345
394
|
c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
|
346
395
|
c.arg_name 'DATE_STRING'
|
347
|
-
c.flag %i[b back started]
|
396
|
+
c.flag %i[b back started], type: DateBeginString
|
348
397
|
|
349
398
|
c.desc %(
|
350
399
|
Start and end times as a date/time range `doing done --from "1am to 8am"`.
|
351
400
|
Overrides other date flags.
|
352
401
|
)
|
353
402
|
c.arg_name 'TIME_RANGE'
|
354
|
-
c.flag [:from]
|
403
|
+
c.flag [:from], must_match: REGEX_RANGE
|
355
404
|
|
356
405
|
c.desc %(Set completion date to start date plus interval (XX[mhd] or HH:MM).
|
357
406
|
If used without the --back option, the start date will be moved back to allow
|
358
407
|
the completion date to be the current time.)
|
359
408
|
c.arg_name 'INTERVAL'
|
360
|
-
c.flag %i[t took for]
|
409
|
+
c.flag %i[t took for], type: DateIntervalString
|
361
410
|
|
362
411
|
c.desc 'Section'
|
363
412
|
c.arg_name 'NAME'
|
@@ -385,23 +434,27 @@ command %i[done did] do |c|
|
|
385
434
|
donedate = nil
|
386
435
|
|
387
436
|
if options[:from]
|
388
|
-
|
437
|
+
options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
|
438
|
+
time =~ REGEX_TIME ? "today #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}" : time
|
439
|
+
end.join(' to ').split_date_range
|
440
|
+
date, finish_date = options[:from]
|
389
441
|
finish_date ||= Time.now
|
390
442
|
else
|
391
443
|
if options[:took]
|
392
|
-
took = options[:took]
|
444
|
+
took = options[:took]
|
393
445
|
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
394
446
|
end
|
395
447
|
|
396
448
|
if options[:back]
|
397
|
-
date = options[:back]
|
449
|
+
date = options[:back]
|
398
450
|
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
399
451
|
else
|
400
452
|
date = options[:took] ? Time.now - took : Time.now
|
401
453
|
end
|
402
454
|
|
403
455
|
if options[:at]
|
404
|
-
finish_date = options[:at]
|
456
|
+
finish_date = options[:at]
|
457
|
+
finish_date = finish_date.chronify(guess: :begin) if finish_date.is_a? String
|
405
458
|
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
406
459
|
|
407
460
|
if options[:took]
|
@@ -417,6 +470,7 @@ command %i[done did] do |c|
|
|
417
470
|
end
|
418
471
|
|
419
472
|
if options[:date]
|
473
|
+
date = date.chronify(guess: :begin, context: :today) if date =~ REGEX_TIME
|
420
474
|
finish_date = wwid.verify_duration(date, finish_date) unless options[:took] || options[:from]
|
421
475
|
|
422
476
|
donedate = finish_date.strftime('%F %R')
|
@@ -528,7 +582,7 @@ command %i[done did] do |c|
|
|
528
582
|
wwid.content.push(new_entry)
|
529
583
|
Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
|
530
584
|
wwid.write(wwid.doing_file)
|
531
|
-
Doing.logger.info('
|
585
|
+
Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
|
532
586
|
elsif $stdin.stat.size.positive?
|
533
587
|
note = Doing::Note.new(options[:note])
|
534
588
|
d, title, note = wwid.format_input($stdin.read.strip)
|
@@ -553,7 +607,7 @@ command %i[done did] do |c|
|
|
553
607
|
Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
|
554
608
|
|
555
609
|
wwid.write(wwid.doing_file)
|
556
|
-
Doing.logger.info('
|
610
|
+
Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
|
557
611
|
else
|
558
612
|
raise EmptyInput, 'You must provide content when creating a new entry'
|
559
613
|
end
|
@@ -574,15 +628,15 @@ command :finish do |c|
|
|
574
628
|
|
575
629
|
c.desc 'Backdate completed date to date string [4pm|20m|2h|yesterday noon]'
|
576
630
|
c.arg_name 'DATE_STRING'
|
577
|
-
c.flag %i[b back]
|
631
|
+
c.flag %i[b back started], type: DateBeginString
|
578
632
|
|
579
633
|
c.desc 'Set the completed date to the start date plus XX[hmd]'
|
580
634
|
c.arg_name 'INTERVAL'
|
581
|
-
c.flag %i[t took for]
|
635
|
+
c.flag %i[t took for], type: DateIntervalString
|
582
636
|
|
583
637
|
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
|
584
638
|
c.arg_name 'DATE_STRING'
|
585
|
-
c.flag [
|
639
|
+
c.flag %i[at finished], type: DateEndString
|
586
640
|
|
587
641
|
c.desc 'Finish the last X entries containing TAG.
|
588
642
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
@@ -639,7 +693,7 @@ command :finish do |c|
|
|
639
693
|
options[:fuzzy] = false
|
640
694
|
unless options[:auto]
|
641
695
|
if options[:took]
|
642
|
-
took = options[:took]
|
696
|
+
took = options[:took]
|
643
697
|
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
644
698
|
end
|
645
699
|
|
@@ -648,12 +702,13 @@ command :finish do |c|
|
|
648
702
|
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
649
703
|
|
650
704
|
if options[:at]
|
651
|
-
finish_date = options[:at]
|
705
|
+
finish_date = options[:at]
|
706
|
+
finish_date = finish_date.chronify(guess: :begin) if finish_date.is_a? String
|
652
707
|
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
653
708
|
|
654
709
|
date = options[:took] ? finish_date - took : finish_date
|
655
710
|
elsif options[:back]
|
656
|
-
date = options[:back]
|
711
|
+
date = options[:back]
|
657
712
|
|
658
713
|
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
659
714
|
else
|
@@ -661,8 +716,6 @@ command :finish do |c|
|
|
661
716
|
end
|
662
717
|
end
|
663
718
|
|
664
|
-
options[:took] = options[:took].chronify_qty if options[:took]
|
665
|
-
|
666
719
|
if options[:tag].nil?
|
667
720
|
tags = []
|
668
721
|
else
|
@@ -711,92 +764,6 @@ command :finish do |c|
|
|
711
764
|
end
|
712
765
|
end
|
713
766
|
|
714
|
-
# @@later
|
715
|
-
desc 'Add an item to the Later section'
|
716
|
-
arg_name 'ENTRY'
|
717
|
-
command :later do |c|
|
718
|
-
c.example 'doing later "Something I\'ll think about tomorrow"', desc: 'Add an entry to the Later section'
|
719
|
-
c.example 'doing later -e', desc: 'Open $EDITOR to create an entry and optional note'
|
720
|
-
|
721
|
-
c.desc "Edit entry with #{Doing::Util.default_editor}"
|
722
|
-
c.switch %i[e editor], negatable: false, default_value: false
|
723
|
-
|
724
|
-
c.desc 'Backdate start time to date string [4pm|20m|2h|yesterday noon]'
|
725
|
-
c.arg_name 'DATE_STRING'
|
726
|
-
c.flag %i[b back]
|
727
|
-
|
728
|
-
c.desc 'Note'
|
729
|
-
c.arg_name 'TEXT'
|
730
|
-
c.flag %i[n note]
|
731
|
-
|
732
|
-
c.desc 'Prompt for note via multi-line input'
|
733
|
-
c.switch %i[ask], negatable: false, default_value: false
|
734
|
-
|
735
|
-
c.action do |_global_options, options, args|
|
736
|
-
if options[:back]
|
737
|
-
date = options[:back].chronify(guess: :begin)
|
738
|
-
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
739
|
-
else
|
740
|
-
date = Time.now
|
741
|
-
end
|
742
|
-
|
743
|
-
ask_note = options[:ask] && !options[:editor] && args.count.positive? ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
|
744
|
-
|
745
|
-
if options[:editor]
|
746
|
-
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
747
|
-
|
748
|
-
input = ''
|
749
|
-
input += date.strftime('%F %R | ')
|
750
|
-
input += args.empty? ? '' : args.join(' ')
|
751
|
-
input += "\n#{options[:note]}" if options[:note]
|
752
|
-
input += "\n#{ask_note}" unless ask_note.empty?
|
753
|
-
|
754
|
-
input = wwid.fork_editor(input).strip
|
755
|
-
|
756
|
-
d, title, note = wwid.format_input(input)
|
757
|
-
raise EmptyInput, 'No content' if title.empty?
|
758
|
-
|
759
|
-
note.add(options[:note]) if options[:note]
|
760
|
-
if ask_note.empty? && options[:ask]
|
761
|
-
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
762
|
-
note.add(ask_note) unless ask_note.empty?
|
763
|
-
end
|
764
|
-
|
765
|
-
date = d.nil? ? date : d
|
766
|
-
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
767
|
-
wwid.write(wwid.doing_file)
|
768
|
-
elsif !args.empty?
|
769
|
-
d, title, note = wwid.format_input(args.join(' '))
|
770
|
-
date = d.nil? ? date : d
|
771
|
-
note.add(options[:note]) if options[:note]
|
772
|
-
note.add(ask_note) unless ask_note.empty?
|
773
|
-
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
774
|
-
wwid.write(wwid.doing_file)
|
775
|
-
elsif $stdin.stat.size.positive?
|
776
|
-
d, title, note = wwid.format_input($stdin.read)
|
777
|
-
unless d.nil?
|
778
|
-
Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
|
779
|
-
date = d
|
780
|
-
end
|
781
|
-
note.add(options[:note]) if options[:note]
|
782
|
-
note.add(ask_note) unless ask_note.empty?
|
783
|
-
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
784
|
-
wwid.write(wwid.doing_file)
|
785
|
-
else
|
786
|
-
title = Doing::Prompt.read_line(prompt: 'Entry content')
|
787
|
-
raise EmptyInput, 'You must provide content when creating a new entry' if title.strip.empty?
|
788
|
-
|
789
|
-
note = Doing::Note.new
|
790
|
-
res = Doing::Prompt.yn('Add a note', default_response: false)
|
791
|
-
ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
|
792
|
-
note.add(ask_note)
|
793
|
-
|
794
|
-
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
795
|
-
wwid.write(wwid.doing_file)
|
796
|
-
end
|
797
|
-
end
|
798
|
-
end
|
799
|
-
|
800
767
|
# @@mark @@flag
|
801
768
|
desc 'Mark last entry as flagged'
|
802
769
|
command %i[mark flag] do |c|
|
@@ -949,7 +916,7 @@ command :meanwhile do |c|
|
|
949
916
|
|
950
917
|
c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
|
951
918
|
c.arg_name 'DATE_STRING'
|
952
|
-
c.flag %i[b back]
|
919
|
+
c.flag %i[b back started], type: DateBeginString
|
953
920
|
|
954
921
|
c.desc 'Note'
|
955
922
|
c.arg_name 'TEXT'
|
@@ -960,7 +927,7 @@ command :meanwhile do |c|
|
|
960
927
|
|
961
928
|
c.action do |_global_options, options, args|
|
962
929
|
if options[:back]
|
963
|
-
date = options[:back]
|
930
|
+
date = options[:back]
|
964
931
|
|
965
932
|
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
966
933
|
else
|
@@ -1169,7 +1136,7 @@ command %i[now next] do |c|
|
|
1169
1136
|
|
1170
1137
|
c.desc 'Backdate start time [4pm|20m|2h|"yesterday noon"]'
|
1171
1138
|
c.arg_name 'DATE_STRING'
|
1172
|
-
c.flag %i[b back started]
|
1139
|
+
c.flag %i[b back started], type: DateBeginString
|
1173
1140
|
|
1174
1141
|
c.desc 'Timed entry, marks last entry in section as @done'
|
1175
1142
|
c.switch %i[f finish_last], negatable: false, default_value: false
|
@@ -1187,7 +1154,7 @@ command %i[now next] do |c|
|
|
1187
1154
|
|
1188
1155
|
c.action do |_global_options, options, args|
|
1189
1156
|
if options[:back]
|
1190
|
-
date = options[:back]
|
1157
|
+
date = options[:back]
|
1191
1158
|
|
1192
1159
|
raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
|
1193
1160
|
else
|
@@ -1211,7 +1178,7 @@ command %i[now next] do |c|
|
|
1211
1178
|
input += "\n#{ask_note}" unless ask_note.empty?
|
1212
1179
|
input = wwid.fork_editor(input).strip
|
1213
1180
|
|
1214
|
-
|
1181
|
+
d, title, note = wwid.format_input(input)
|
1215
1182
|
raise EmptyInput, 'No content' if title.strip.empty?
|
1216
1183
|
|
1217
1184
|
if ask_note.empty? && options[:ask]
|
@@ -1219,6 +1186,7 @@ command %i[now next] do |c|
|
|
1219
1186
|
note.add(ask_note) unless ask_note.empty?
|
1220
1187
|
end
|
1221
1188
|
|
1189
|
+
date = d.nil? ? date : d
|
1222
1190
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1223
1191
|
wwid.write(wwid.doing_file)
|
1224
1192
|
elsif args.length.positive?
|
@@ -1236,14 +1204,20 @@ command %i[now next] do |c|
|
|
1236
1204
|
date = d
|
1237
1205
|
end
|
1238
1206
|
note.add(options[:note]) if options[:note]
|
1239
|
-
|
1207
|
+
if ask_note.empty? && options[:ask]
|
1208
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
1209
|
+
note.add(ask_note) unless ask_note.empty?
|
1210
|
+
end
|
1240
1211
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1241
1212
|
wwid.write(wwid.doing_file)
|
1242
1213
|
else
|
1243
|
-
|
1214
|
+
tags = wwid.all_tags(wwid.content)
|
1215
|
+
$stderr.puts Doing::Color.boldgreen("Add a new entry. Tab will autocomplete known tags. Ctrl-c to cancel.")
|
1216
|
+
title = Doing::Prompt.read_line(prompt: 'Entry content', completions: tags)
|
1244
1217
|
raise EmptyInput, 'You must provide content when creating a new entry' if title.strip.empty?
|
1245
1218
|
|
1246
1219
|
note = Doing::Note.new
|
1220
|
+
note.add(options[:note]) if options[:note]
|
1247
1221
|
res = Doing::Prompt.yn('Add a note', default_response: false)
|
1248
1222
|
ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
|
1249
1223
|
note.add(ask_note)
|
@@ -1410,11 +1384,11 @@ command :select do |c|
|
|
1410
1384
|
|
1411
1385
|
c.desc 'Select from entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
1412
1386
|
c.arg_name 'DATE_STRING'
|
1413
|
-
c.flag [:before]
|
1387
|
+
c.flag [:before], type: DateBeginString
|
1414
1388
|
|
1415
1389
|
c.desc 'Select from entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
1416
1390
|
c.arg_name 'DATE_STRING'
|
1417
|
-
c.flag [:after]
|
1391
|
+
c.flag [:after], type: DateEndString
|
1418
1392
|
|
1419
1393
|
c.desc %(
|
1420
1394
|
Date range to show, or a single day to filter date on.
|
@@ -1425,7 +1399,7 @@ command :select do |c|
|
|
1425
1399
|
by time of day.
|
1426
1400
|
)
|
1427
1401
|
c.arg_name 'DATE_OR_RANGE'
|
1428
|
-
c.flag [:from]
|
1402
|
+
c.flag [:from], type: DateRangeString
|
1429
1403
|
|
1430
1404
|
c.desc 'Force exact search string matching (case sensitive)'
|
1431
1405
|
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
@@ -1692,11 +1666,11 @@ command %i[grep search] do |c|
|
|
1692
1666
|
|
1693
1667
|
c.desc 'Search entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
1694
1668
|
c.arg_name 'DATE_STRING'
|
1695
|
-
c.flag [:before]
|
1669
|
+
c.flag [:before], type: DateBeginString
|
1696
1670
|
|
1697
1671
|
c.desc 'Search entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
1698
1672
|
c.arg_name 'DATE_STRING'
|
1699
|
-
c.flag [:after]
|
1673
|
+
c.flag [:after], type: DateEndString
|
1700
1674
|
|
1701
1675
|
c.desc %(
|
1702
1676
|
Date range to show, or a single day to filter date on.
|
@@ -1707,7 +1681,7 @@ command %i[grep search] do |c|
|
|
1707
1681
|
by time of day.
|
1708
1682
|
)
|
1709
1683
|
c.arg_name 'DATE_OR_RANGE'
|
1710
|
-
c.flag [:from]
|
1684
|
+
c.flag [:from], type: DateRangeString
|
1711
1685
|
|
1712
1686
|
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
1713
1687
|
c.arg_name 'FORMAT'
|
@@ -1744,6 +1718,9 @@ command %i[grep search] do |c|
|
|
1744
1718
|
c.arg_name 'TYPE'
|
1745
1719
|
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1746
1720
|
|
1721
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
1722
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
1723
|
+
|
1747
1724
|
c.desc "Edit matching entries with #{Doing::Util.default_editor}"
|
1748
1725
|
c.switch %i[e editor], negatable: false, default_value: false
|
1749
1726
|
|
@@ -1821,6 +1798,9 @@ command :last do |c|
|
|
1821
1798
|
c.arg_name 'QUERY'
|
1822
1799
|
c.flag [:search]
|
1823
1800
|
|
1801
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
1802
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
1803
|
+
|
1824
1804
|
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
1825
1805
|
c.arg_name 'QUERY'
|
1826
1806
|
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
@@ -1875,6 +1855,7 @@ command :last do |c|
|
|
1875
1855
|
search: options[:search],
|
1876
1856
|
fuzzy: options[:fuzzy],
|
1877
1857
|
case: options[:case],
|
1858
|
+
hilite: options[:hilite],
|
1878
1859
|
negate: options[:not],
|
1879
1860
|
tag: options[:tag],
|
1880
1861
|
tag_bool: options[:bool],
|
@@ -1998,11 +1979,11 @@ command :show do |c|
|
|
1998
1979
|
|
1999
1980
|
c.desc 'Show entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
2000
1981
|
c.arg_name 'DATE_STRING'
|
2001
|
-
c.flag [:before]
|
1982
|
+
c.flag [:before], type: DateBeginString
|
2002
1983
|
|
2003
1984
|
c.desc 'Show entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
2004
1985
|
c.arg_name 'DATE_STRING'
|
2005
|
-
c.flag [:after]
|
1986
|
+
c.flag [:after], type: DateEndString
|
2006
1987
|
|
2007
1988
|
c.desc %(
|
2008
1989
|
Date range to show, or a single day to filter date on.
|
@@ -2014,12 +1995,15 @@ command :show do |c|
|
|
2014
1995
|
)
|
2015
1996
|
|
2016
1997
|
c.arg_name 'DATE_OR_RANGE'
|
2017
|
-
c.flag [:from]
|
1998
|
+
c.flag [:from], type: DateRangeString
|
2018
1999
|
|
2019
2000
|
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
2020
2001
|
c.arg_name 'QUERY'
|
2021
2002
|
c.flag [:search]
|
2022
2003
|
|
2004
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
2005
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
2006
|
+
|
2023
2007
|
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
2024
2008
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2025
2009
|
|
@@ -2167,6 +2151,7 @@ command :show do |c|
|
|
2167
2151
|
opt[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2168
2152
|
opt[:count] = options[:count].to_i
|
2169
2153
|
opt[:highlight] = true
|
2154
|
+
opt[:hilite] = options[:hilite]
|
2170
2155
|
opt[:order] = options[:sort].normalize_order
|
2171
2156
|
opt[:tag] = nil
|
2172
2157
|
opt[:tag_order] = options[:tag_order].normalize_order
|
@@ -2305,8 +2290,8 @@ command :today do |c|
|
|
2305
2290
|
c.desc %(
|
2306
2291
|
Time range to show `doing today --from "12pm to 4pm"`
|
2307
2292
|
)
|
2308
|
-
c.arg_name '
|
2309
|
-
c.flag [:from]
|
2293
|
+
c.arg_name 'TIME_RANGE'
|
2294
|
+
c.flag [:from], type: DateRangeString
|
2310
2295
|
|
2311
2296
|
c.action do |_global_options, options, _args|
|
2312
2297
|
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
@@ -2319,7 +2304,7 @@ command :today do |c|
|
|
2319
2304
|
end
|
2320
2305
|
end
|
2321
2306
|
|
2322
|
-
#
|
2307
|
+
# @@on
|
2323
2308
|
desc 'List entries for a date'
|
2324
2309
|
long_desc %(Date argument can be natural language. "thursday" would be interpreted as "last thursday,"
|
2325
2310
|
and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates,
|
@@ -2358,16 +2343,11 @@ command :on do |c|
|
|
2358
2343
|
|
2359
2344
|
raise MissingArgument, 'Missing date argument' if args.empty?
|
2360
2345
|
|
2361
|
-
date_string = args.join(' ')
|
2362
|
-
|
2363
|
-
|
2364
|
-
dates = date_string.split(/ (to|through|thru) /)
|
2365
|
-
start = dates[0].chronify(guess: :begin)
|
2366
|
-
finish = dates[2].chronify(guess: :end)
|
2367
|
-
else
|
2368
|
-
start = date_string.chronify(guess: :begin)
|
2369
|
-
finish = false
|
2346
|
+
date_string = args.join(' ').strip
|
2347
|
+
if date_string =~ /^tod(?:ay)?/i
|
2348
|
+
date_string = 'today to tomorrow 12am'
|
2370
2349
|
end
|
2350
|
+
start, finish = date_string.split_date_range
|
2371
2351
|
|
2372
2352
|
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
2373
2353
|
|
@@ -2493,6 +2473,9 @@ command :view do |c|
|
|
2493
2473
|
c.arg_name 'QUERY'
|
2494
2474
|
c.flag [:search]
|
2495
2475
|
|
2476
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
2477
|
+
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
2478
|
+
|
2496
2479
|
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
2497
2480
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2498
2481
|
|
@@ -2516,11 +2499,11 @@ command :view do |c|
|
|
2516
2499
|
|
2517
2500
|
c.desc 'View entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
2518
2501
|
c.arg_name 'DATE_STRING'
|
2519
|
-
c.flag [:before]
|
2502
|
+
c.flag [:before], type: DateBeginString
|
2520
2503
|
|
2521
2504
|
c.desc 'View entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
2522
2505
|
c.arg_name 'DATE_STRING'
|
2523
|
-
c.flag [:after]
|
2506
|
+
c.flag [:after], type: DateEndString
|
2524
2507
|
|
2525
2508
|
c.desc %(
|
2526
2509
|
Date range to show, or a single day to filter date on.
|
@@ -2531,7 +2514,7 @@ command :view do |c|
|
|
2531
2514
|
by time of day.
|
2532
2515
|
)
|
2533
2516
|
c.arg_name 'DATE_OR_RANGE'
|
2534
|
-
c.flag [:from]
|
2517
|
+
c.flag [:from], type: DateRangeString
|
2535
2518
|
|
2536
2519
|
c.desc 'Only show items with recorded time intervals (override view settings)'
|
2537
2520
|
c.switch [:only_timed], default_value: false, negatable: false
|
@@ -2649,10 +2632,10 @@ command :view do |c|
|
|
2649
2632
|
|
2650
2633
|
opts = options.dup
|
2651
2634
|
opts[:age] = options[:age].normalize_age(:newest)
|
2652
|
-
opts[:view_template] = title
|
2653
2635
|
opts[:count] = count
|
2654
2636
|
opts[:format] = date_format
|
2655
2637
|
opts[:highlight] = options[:color]
|
2638
|
+
opts[:hilite] = options[:hilite]
|
2656
2639
|
opts[:only_timed] = only_timed
|
2657
2640
|
opts[:order] = order
|
2658
2641
|
opts[:output] = options[:interactive] ? nil : options[:output]
|
@@ -2665,6 +2648,7 @@ command :view do |c|
|
|
2665
2648
|
opts[:tags_color] = tags_color
|
2666
2649
|
opts[:template] = template
|
2667
2650
|
opts[:totals] = totals
|
2651
|
+
opts[:view_template] = title
|
2668
2652
|
|
2669
2653
|
Doing::Pager.page wwid.list_section(opts)
|
2670
2654
|
elsif title.instance_of?(FalseClass)
|
@@ -2714,11 +2698,9 @@ command :yesterday do |c|
|
|
2714
2698
|
c.arg_name 'TIME_STRING'
|
2715
2699
|
c.flag [:after]
|
2716
2700
|
|
2717
|
-
c.desc
|
2718
|
-
Time range to show, e.g. `doing yesterday --from "1am to 8am"`
|
2719
|
-
)
|
2701
|
+
c.desc 'Time range to show, e.g. `doing yesterday --from "1am to 8am"`'
|
2720
2702
|
c.arg_name 'TIME_RANGE'
|
2721
|
-
c.flag [:from]
|
2703
|
+
c.flag [:from], must_match: REGEX_TIME_RANGE
|
2722
2704
|
|
2723
2705
|
c.desc 'Tag sort direction (asc|desc)'
|
2724
2706
|
c.arg_name 'DIRECTION'
|
@@ -2730,9 +2712,9 @@ command :yesterday do |c|
|
|
2730
2712
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2731
2713
|
|
2732
2714
|
if options[:from]
|
2733
|
-
options[:from] = options[:from].split(/
|
2715
|
+
options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
|
2734
2716
|
"yesterday #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}"
|
2735
|
-
end.join(' to ')
|
2717
|
+
end.join(' to ').split_date_range
|
2736
2718
|
end
|
2737
2719
|
|
2738
2720
|
opt = {
|
@@ -2970,8 +2952,7 @@ command :config do |c|
|
|
2970
2952
|
value = options[:remove] ? nil : args.pop
|
2971
2953
|
keypath = args.join('.')
|
2972
2954
|
real_path = config.resolve_key_path(keypath, create: true)
|
2973
|
-
|
2974
|
-
old_value = settings.dig(*real_path) || nil
|
2955
|
+
old_value = settings.dig(*real_path)
|
2975
2956
|
old_type = old_value&.class.to_s || nil
|
2976
2957
|
|
2977
2958
|
if old_value.is_a?(Hash) && !options[:remove]
|
@@ -2997,7 +2978,6 @@ command :config do |c|
|
|
2997
2978
|
else
|
2998
2979
|
current_value = cfg.dig(*real_path)
|
2999
2980
|
cfg.deep_set(real_path, value.set_type(old_type))
|
3000
|
-
|
3001
2981
|
$stderr.puts "#{' Key path:'.yellow} #{real_path.join('->').boldwhite}"
|
3002
2982
|
$stderr.puts "#{'Inherited:'.yellow} #{(old_value ? old_value.to_s : 'empty').boldwhite}"
|
3003
2983
|
$stderr.puts "#{' Current:'.yellow} #{ (current_value ? current_value.to_s : 'empty').boldwhite }"
|
@@ -3157,7 +3137,7 @@ command %i[archive move] do |c|
|
|
3157
3137
|
c.desc 'Archive entries older than date
|
3158
3138
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
3159
3139
|
c.arg_name 'DATE_STRING'
|
3160
|
-
c.flag [:before]
|
3140
|
+
c.flag [:before], type: DateEndString
|
3161
3141
|
|
3162
3142
|
c.action do |_global_options, options, args|
|
3163
3143
|
options[:fuzzy] = false
|
@@ -3248,11 +3228,11 @@ command :import do |c|
|
|
3248
3228
|
# TODO: Allow time range filtering
|
3249
3229
|
c.desc 'Import entries older than date'
|
3250
3230
|
c.arg_name 'DATE_STRING'
|
3251
|
-
c.flag [:before]
|
3231
|
+
c.flag [:before], type: DateBeginString
|
3252
3232
|
|
3253
3233
|
c.desc 'Import entries newer than date'
|
3254
3234
|
c.arg_name 'DATE_STRING'
|
3255
|
-
c.flag [:after]
|
3235
|
+
c.flag [:after], type: DateEndString
|
3256
3236
|
|
3257
3237
|
c.desc %(
|
3258
3238
|
Date range to import. Date range argument should be quoted. Date specifications can be natural language.
|
@@ -3260,7 +3240,7 @@ command :import do |c|
|
|
3260
3240
|
Has no effect unless the import plugin has implemented date range filtering.
|
3261
3241
|
)
|
3262
3242
|
c.arg_name 'DATE_OR_RANGE'
|
3263
|
-
c.flag %i[f from]
|
3243
|
+
c.flag %i[f from], type: DateRangeString
|
3264
3244
|
|
3265
3245
|
c.desc 'Allow entries that overlap existing times'
|
3266
3246
|
c.switch [:overlap], negatable: true
|
@@ -3278,24 +3258,19 @@ command :import do |c|
|
|
3278
3258
|
end
|
3279
3259
|
|
3280
3260
|
if options[:from]
|
3281
|
-
|
3282
|
-
|
3283
|
-
|
3284
|
-
|
3285
|
-
|
3286
|
-
|
3287
|
-
|
3288
|
-
finish = date_string.chronify(guess: :end)
|
3289
|
-
end
|
3290
|
-
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
3291
|
-
dates = [start, finish]
|
3261
|
+
options[:date_filter] = options[:from]
|
3262
|
+
|
3263
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless options[:date_filter][0]
|
3264
|
+
elsif options[:before] || options[:after]
|
3265
|
+
options[:date_filter] = [nil, nil]
|
3266
|
+
options[:date_filter][1] = options[:before] || Time.now + (1 << 64)
|
3267
|
+
options[:date_filter][0] = options[:after] || Time.now - (1 << 64)
|
3292
3268
|
end
|
3293
3269
|
|
3294
3270
|
options[:case] = options[:case].normalize_case
|
3295
3271
|
|
3296
3272
|
if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
|
3297
3273
|
options[:no_overlap] = !options[:overlap]
|
3298
|
-
options[:date_filter] = dates
|
3299
3274
|
wwid.import(args, options)
|
3300
3275
|
wwid.write(wwid.doing_file)
|
3301
3276
|
else
|
@@ -3646,9 +3621,9 @@ command :commands_accepting do |c|
|
|
3646
3621
|
end
|
3647
3622
|
|
3648
3623
|
if o[:column]
|
3649
|
-
puts cmds
|
3624
|
+
puts cmds.sort
|
3650
3625
|
else
|
3651
|
-
puts "Commands accepting --#{option}: #{cmds.join(', ')}"
|
3626
|
+
puts "Commands accepting --#{option}: #{cmds.sort.join(', ')}"
|
3652
3627
|
end
|
3653
3628
|
end
|
3654
3629
|
end
|
@@ -3693,7 +3668,9 @@ pre do |global, _command, _options, _args|
|
|
3693
3668
|
end
|
3694
3669
|
|
3695
3670
|
on_error do |exception|
|
3696
|
-
if exception.kind_of?(
|
3671
|
+
if exception.kind_of?(GLI::UnknownCommand)
|
3672
|
+
exit run(['now'].concat(ARGV))
|
3673
|
+
elsif exception.kind_of?(SystemExit)
|
3697
3674
|
false
|
3698
3675
|
else
|
3699
3676
|
# Doing.logger.error('Fatal:', exception)
|