doing 2.1.19 → 2.1.24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardoc/checksums +19 -16
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +70 -0
- data/Gemfile.lock +11 -11
- data/README.md +1 -1
- data/Rakefile +12 -4
- data/bin/doing +297 -234
- data/docs/doc/Array.html +7 -30
- 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 +3 -3
- data/docs/doc/Doing/Completion.html +3 -3
- data/docs/doc/Doing/Configuration.html +6 -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 +3 -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 +7 -7
- data/docs/doc/Doing/Section.html +3 -3
- data/docs/doc/Doing/TemplateString.html +4 -4
- data/docs/doc/Doing/Types.html +201 -0
- data/docs/doc/Doing/Util/Backup.html +3 -3
- data/docs/doc/Doing/Util.html +4 -7
- data/docs/doc/Doing/WWID.html +66 -8
- data/docs/doc/Doing.html +6 -6
- data/docs/doc/GLI/Commands/Help.html +185 -0
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +3 -3
- data/docs/doc/GLI/Commands.html +7 -5
- data/docs/doc/GLI.html +6 -4
- data/docs/doc/Hash.html +80 -16
- 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 +195 -26
- data/docs/doc/Symbol.html +3 -3
- data/docs/doc/Time.html +3 -3
- data/docs/doc/_index.html +22 -8
- data/docs/doc/class_list.html +1 -1
- 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 +334 -270
- data/docs/doc/top-level-namespace.html +3 -3
- data/docs/index.md +1 -1
- data/doing.gemspec +1 -1
- data/doing.rdoc +173 -15
- data/lib/completion/_doing.zsh +20 -20
- data/lib/completion/doing.bash +37 -26
- data/lib/completion/doing.fish +116 -17
- data/lib/doing/array.rb +5 -4
- data/lib/doing/array_chronify.rb +4 -3
- data/lib/doing/changelog/change.rb +115 -0
- data/lib/doing/changelog/changes.rb +73 -0
- data/lib/doing/changelog/entry.rb +21 -0
- data/lib/doing/changelog/version.rb +97 -0
- data/lib/doing/changelog.rb +6 -0
- data/lib/doing/completion/fish_completion.rb +82 -12
- data/lib/doing/configuration.rb +17 -8
- data/lib/doing/hash.rb +25 -6
- data/lib/doing/help_monkey_patch.rb +31 -0
- data/lib/doing/hooks.rb +5 -1
- data/lib/doing/item.rb +10 -25
- data/lib/doing/items.rb +3 -1
- data/lib/doing/log_adapter.rb +1 -1
- data/lib/doing/pager.rb +2 -2
- 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 +9 -3
- data/lib/doing/prompt.rb +4 -2
- data/lib/doing/string.rb +40 -11
- data/lib/doing/string_chronify.rb +56 -18
- data/lib/doing/template_string.rb +7 -0
- data/lib/doing/types.rb +25 -0
- data/lib/doing/util.rb +2 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +91 -67
- data/lib/doing.rb +2 -0
- data/lib/examples/commands/later.rb +32 -0
- data/lib/helpers/threaded_tests.rb +286 -0
- metadata +17 -6
data/bin/doing
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
5
5
|
require 'gli'
|
6
|
+
require 'doing/help_monkey_patch'
|
6
7
|
require 'doing'
|
7
8
|
require 'tempfile'
|
8
9
|
require 'pp'
|
@@ -21,16 +22,12 @@ end
|
|
21
22
|
|
22
23
|
include GLI::App
|
23
24
|
include Doing::Errors
|
25
|
+
|
24
26
|
version Doing::VERSION
|
25
27
|
hide_commands_without_desc true
|
26
28
|
autocomplete_commands true
|
27
29
|
|
28
|
-
|
29
|
-
REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i
|
30
|
-
REGEX_VALUE_QUERY = /^(?:!)?@?(?:\S+) +(?:!?[<>=][=*]?|[$*^]=) +(?:.*?)$/
|
31
|
-
|
32
|
-
InvalidExportType = Class.new(RuntimeError)
|
33
|
-
MissingConfigFile = Class.new(RuntimeError)
|
30
|
+
include Doing::Types
|
34
31
|
|
35
32
|
colors = Doing::Color
|
36
33
|
wwid = Doing::WWID.new
|
@@ -70,7 +67,49 @@ if settings.dig('plugins', 'command_path')
|
|
70
67
|
commands_from File.expand_path(settings.dig('plugins', 'command_path'))
|
71
68
|
end
|
72
69
|
|
73
|
-
|
70
|
+
accept TemplateName do |value|
|
71
|
+
res = settings['templates'].keys.select { |k| k =~ value.to_rx(distance: 2) }
|
72
|
+
raise InvalidArgument, "Unknown template: #{value}" if res.empty?
|
73
|
+
|
74
|
+
res.group_by(&:length).min.last[0]
|
75
|
+
end
|
76
|
+
|
77
|
+
accept DateBeginString do |value|
|
78
|
+
if value =~ REGEX_TIME
|
79
|
+
res = value
|
80
|
+
else
|
81
|
+
res = value.chronify(guess: :begin, future: false)
|
82
|
+
end
|
83
|
+
raise InvalidTimeExpression, 'Invalid start date' unless res
|
84
|
+
|
85
|
+
res
|
86
|
+
end
|
87
|
+
|
88
|
+
accept DateEndString do |value|
|
89
|
+
if value =~ REGEX_TIME
|
90
|
+
res = value
|
91
|
+
else
|
92
|
+
res = value.chronify(guess: :end, future: false)
|
93
|
+
end
|
94
|
+
raise InvalidTimeExpression, 'Invalid end date' unless res
|
95
|
+
|
96
|
+
res
|
97
|
+
end
|
98
|
+
|
99
|
+
accept DateRangeString do |value|
|
100
|
+
start, finish = value.split_date_range
|
101
|
+
raise InvalidTimeExpression, 'Invalid range' unless start
|
102
|
+
|
103
|
+
finish ||= Time.now
|
104
|
+
[start, finish]
|
105
|
+
end
|
106
|
+
|
107
|
+
accept DateIntervalString do |value|
|
108
|
+
res = value.chronify_qty
|
109
|
+
raise InvalidTimeExpression, 'Invalid time quantity' unless res
|
110
|
+
|
111
|
+
res
|
112
|
+
end
|
74
113
|
|
75
114
|
accept TagArray do |value|
|
76
115
|
value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '')}.map(&:strip)
|
@@ -97,7 +136,7 @@ desc 'Use a pager when output is longer than screen'
|
|
97
136
|
switch %i[p pager], default_value: settings['paginate']
|
98
137
|
|
99
138
|
desc 'Answer yes/no menus with default option'
|
100
|
-
switch [:default], default_value: false
|
139
|
+
switch [:default], default_value: false, negatable: false
|
101
140
|
|
102
141
|
desc 'Answer all yes/no menus with yes'
|
103
142
|
switch [:yes], negatable: false
|
@@ -143,6 +182,10 @@ command %i[again resume] do |c|
|
|
143
182
|
c.arg_name 'SECTION_NAME'
|
144
183
|
c.flag [:in]
|
145
184
|
|
185
|
+
c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
|
186
|
+
c.arg_name 'DATE_STRING'
|
187
|
+
c.flag %i[b back started], type: DateBeginString
|
188
|
+
|
146
189
|
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
147
190
|
c.arg_name 'TAG'
|
148
191
|
c.flag [:tag], type: TagArray
|
@@ -198,6 +241,13 @@ command %i[again resume] do |c|
|
|
198
241
|
options[:search] = search
|
199
242
|
end
|
200
243
|
|
244
|
+
if options[:back]
|
245
|
+
date = options[:back]
|
246
|
+
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
247
|
+
else
|
248
|
+
date = Time.now
|
249
|
+
end
|
250
|
+
|
201
251
|
note = Doing::Note.new(options[:note])
|
202
252
|
note.add(Doing::Prompt.read_lines(prompt: 'Add a note')) if options[:ask]
|
203
253
|
|
@@ -208,6 +258,7 @@ command %i[again resume] do |c|
|
|
208
258
|
opts[:tag] = tags
|
209
259
|
opts[:tag_bool] = options[:bool].normalize_bool
|
210
260
|
opts[:interactive] = options[:interactive]
|
261
|
+
opts[:date] = date
|
211
262
|
|
212
263
|
wwid.repeat_last(opts)
|
213
264
|
end
|
@@ -321,7 +372,7 @@ desc 'Add a completed item with @done(date). No argument finishes last entry'
|
|
321
372
|
long_desc 'Use this command to add an entry after you\'ve already finished it. It will be immediately marked as @done.
|
322
373
|
You can modify the start and end times of the entry using the --back, --took, and --at flags, making it an easy
|
323
374
|
way to add entries in post and maintain accurate (albeit manual) time tracking.'
|
324
|
-
arg_name 'ENTRY'
|
375
|
+
arg_name 'ENTRY', optional: true
|
325
376
|
command %i[done did] do |c|
|
326
377
|
c.example 'doing done', desc: 'Tag the last entry @done'
|
327
378
|
c.example 'doing done I already finished this', desc: 'Add a new entry and immediately mark it @done'
|
@@ -340,24 +391,24 @@ command %i[done did] do |c|
|
|
340
391
|
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm).
|
341
392
|
Used with --took, backdates start date)
|
342
393
|
c.arg_name 'DATE_STRING'
|
343
|
-
c.flag %i[at finished]
|
394
|
+
c.flag %i[at finished], type: DateEndString
|
344
395
|
|
345
396
|
c.desc 'Backdate start date by interval or set to time [4pm|20m|2h|"yesterday noon"]'
|
346
397
|
c.arg_name 'DATE_STRING'
|
347
|
-
c.flag %i[b back started]
|
398
|
+
c.flag %i[b back started], type: DateBeginString
|
348
399
|
|
349
400
|
c.desc %(
|
350
401
|
Start and end times as a date/time range `doing done --from "1am to 8am"`.
|
351
402
|
Overrides other date flags.
|
352
403
|
)
|
353
404
|
c.arg_name 'TIME_RANGE'
|
354
|
-
c.flag [:from]
|
405
|
+
c.flag [:from], must_match: REGEX_RANGE
|
355
406
|
|
356
407
|
c.desc %(Set completion date to start date plus interval (XX[mhd] or HH:MM).
|
357
408
|
If used without the --back option, the start date will be moved back to allow
|
358
409
|
the completion date to be the current time.)
|
359
410
|
c.arg_name 'INTERVAL'
|
360
|
-
c.flag %i[t took for]
|
411
|
+
c.flag %i[t took for], type: DateIntervalString
|
361
412
|
|
362
413
|
c.desc 'Section'
|
363
414
|
c.arg_name 'NAME'
|
@@ -385,23 +436,27 @@ command %i[done did] do |c|
|
|
385
436
|
donedate = nil
|
386
437
|
|
387
438
|
if options[:from]
|
388
|
-
|
439
|
+
options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
|
440
|
+
time =~ REGEX_TIME ? "today #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}" : time
|
441
|
+
end.join(' to ').split_date_range
|
442
|
+
date, finish_date = options[:from]
|
389
443
|
finish_date ||= Time.now
|
390
444
|
else
|
391
445
|
if options[:took]
|
392
|
-
took = options[:took]
|
446
|
+
took = options[:took]
|
393
447
|
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
394
448
|
end
|
395
449
|
|
396
450
|
if options[:back]
|
397
|
-
date = options[:back]
|
451
|
+
date = options[:back]
|
398
452
|
raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
|
399
453
|
else
|
400
454
|
date = options[:took] ? Time.now - took : Time.now
|
401
455
|
end
|
402
456
|
|
403
457
|
if options[:at]
|
404
|
-
finish_date = options[:at]
|
458
|
+
finish_date = options[:at]
|
459
|
+
finish_date = finish_date.chronify(guess: :begin) if finish_date.is_a? String
|
405
460
|
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
406
461
|
|
407
462
|
if options[:took]
|
@@ -417,6 +472,7 @@ command %i[done did] do |c|
|
|
417
472
|
end
|
418
473
|
|
419
474
|
if options[:date]
|
475
|
+
date = date.chronify(guess: :begin, context: :today) if date =~ REGEX_TIME
|
420
476
|
finish_date = wwid.verify_duration(date, finish_date) unless options[:took] || options[:from]
|
421
477
|
|
422
478
|
donedate = finish_date.strftime('%F %R')
|
@@ -528,7 +584,7 @@ command %i[done did] do |c|
|
|
528
584
|
wwid.content.push(new_entry)
|
529
585
|
Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
|
530
586
|
wwid.write(wwid.doing_file)
|
531
|
-
Doing.logger.info('
|
587
|
+
Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
|
532
588
|
elsif $stdin.stat.size.positive?
|
533
589
|
note = Doing::Note.new(options[:note])
|
534
590
|
d, title, note = wwid.format_input($stdin.read.strip)
|
@@ -553,7 +609,7 @@ command %i[done did] do |c|
|
|
553
609
|
Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
|
554
610
|
|
555
611
|
wwid.write(wwid.doing_file)
|
556
|
-
Doing.logger.info('
|
612
|
+
Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
|
557
613
|
else
|
558
614
|
raise EmptyInput, 'You must provide content when creating a new entry'
|
559
615
|
end
|
@@ -563,7 +619,7 @@ end
|
|
563
619
|
# @@finish
|
564
620
|
desc 'Mark last X entries as @done'
|
565
621
|
long_desc 'Marks the last X entries with a @done tag and current date. Does not alter already completed entries.'
|
566
|
-
arg_name 'COUNT'
|
622
|
+
arg_name 'COUNT', optional: true
|
567
623
|
command :finish do |c|
|
568
624
|
c.example 'doing finish', desc: 'Mark the last entry @done'
|
569
625
|
c.example 'doing finish --auto --section Later 10', desc: 'Add @done to any unfinished entries in the last 10 in Later, setting the finish time based on the start time of the task after it'
|
@@ -574,15 +630,15 @@ command :finish do |c|
|
|
574
630
|
|
575
631
|
c.desc 'Backdate completed date to date string [4pm|20m|2h|yesterday noon]'
|
576
632
|
c.arg_name 'DATE_STRING'
|
577
|
-
c.flag %i[b back]
|
633
|
+
c.flag %i[b back started], type: DateBeginString
|
578
634
|
|
579
635
|
c.desc 'Set the completed date to the start date plus XX[hmd]'
|
580
636
|
c.arg_name 'INTERVAL'
|
581
|
-
c.flag %i[t took for]
|
637
|
+
c.flag %i[t took for], type: DateIntervalString
|
582
638
|
|
583
639
|
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
|
584
640
|
c.arg_name 'DATE_STRING'
|
585
|
-
c.flag [
|
641
|
+
c.flag %i[at finished], type: DateEndString
|
586
642
|
|
587
643
|
c.desc 'Finish the last X entries containing TAG.
|
588
644
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
@@ -639,7 +695,7 @@ command :finish do |c|
|
|
639
695
|
options[:fuzzy] = false
|
640
696
|
unless options[:auto]
|
641
697
|
if options[:took]
|
642
|
-
took = options[:took]
|
698
|
+
took = options[:took]
|
643
699
|
raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
|
644
700
|
end
|
645
701
|
|
@@ -648,12 +704,13 @@ command :finish do |c|
|
|
648
704
|
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
649
705
|
|
650
706
|
if options[:at]
|
651
|
-
finish_date = options[:at]
|
707
|
+
finish_date = options[:at]
|
708
|
+
finish_date = finish_date.chronify(guess: :begin) if finish_date.is_a? String
|
652
709
|
raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
|
653
710
|
|
654
711
|
date = options[:took] ? finish_date - took : finish_date
|
655
712
|
elsif options[:back]
|
656
|
-
date = options[:back]
|
713
|
+
date = options[:back]
|
657
714
|
|
658
715
|
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
659
716
|
else
|
@@ -661,8 +718,6 @@ command :finish do |c|
|
|
661
718
|
end
|
662
719
|
end
|
663
720
|
|
664
|
-
options[:took] = options[:took].chronify_qty if options[:took]
|
665
|
-
|
666
721
|
if options[:tag].nil?
|
667
722
|
tags = []
|
668
723
|
else
|
@@ -711,92 +766,6 @@ command :finish do |c|
|
|
711
766
|
end
|
712
767
|
end
|
713
768
|
|
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
769
|
# @@mark @@flag
|
801
770
|
desc 'Mark last entry as flagged'
|
802
771
|
command %i[mark flag] do |c|
|
@@ -930,7 +899,7 @@ long_desc 'The @meanwhile tag allows you to have long-running entries that encom
|
|
930
899
|
This command makes it easy to start and stop these overarching entries. Just run `doing meanwhile Starting work on this
|
931
900
|
big project` to start a @meanwhile entry, add other entries as you work on the project, then use `doing meanwhile` by
|
932
901
|
itself to mark the entry as @done.'
|
933
|
-
arg_name 'ENTRY'
|
902
|
+
arg_name 'ENTRY', optional: true
|
934
903
|
command :meanwhile do |c|
|
935
904
|
c.example 'doing meanwhile "Long task that will have others after it before it\'s done"', desc: 'Add a new long-running entry, completing any current @meanwhile entry'
|
936
905
|
c.example 'doing meanwhile', desc: 'Finish any open @meanwhile entry'
|
@@ -949,7 +918,7 @@ command :meanwhile do |c|
|
|
949
918
|
|
950
919
|
c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
|
951
920
|
c.arg_name 'DATE_STRING'
|
952
|
-
c.flag %i[b back]
|
921
|
+
c.flag %i[b back started], type: DateBeginString
|
953
922
|
|
954
923
|
c.desc 'Note'
|
955
924
|
c.arg_name 'TEXT'
|
@@ -960,7 +929,7 @@ command :meanwhile do |c|
|
|
960
929
|
|
961
930
|
c.action do |_global_options, options, args|
|
962
931
|
if options[:back]
|
963
|
-
date = options[:back]
|
932
|
+
date = options[:back]
|
964
933
|
|
965
934
|
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
966
935
|
else
|
@@ -1020,7 +989,7 @@ long_desc %(
|
|
1020
989
|
|
1021
990
|
Use -e to load the last entry in a text editor where you can append a note.
|
1022
991
|
)
|
1023
|
-
arg_name 'NOTE_TEXT'
|
992
|
+
arg_name 'NOTE_TEXT', optional: true
|
1024
993
|
command :note do |c|
|
1025
994
|
c.example 'doing note', desc: 'Open the last entry in $EDITOR to append a note'
|
1026
995
|
c.example 'doing note "Just a quick annotation"', desc: 'Add a quick note to the last entry'
|
@@ -1088,7 +1057,6 @@ command :note do |c|
|
|
1088
1057
|
options[:search] = search
|
1089
1058
|
end
|
1090
1059
|
|
1091
|
-
|
1092
1060
|
last_entry = wwid.last_entry(options)
|
1093
1061
|
|
1094
1062
|
unless last_entry
|
@@ -1098,37 +1066,37 @@ command :note do |c|
|
|
1098
1066
|
|
1099
1067
|
last_note = last_entry.note || Doing::Note.new
|
1100
1068
|
new_note = Doing::Note.new
|
1101
|
-
ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
|
1102
1069
|
|
1103
|
-
if
|
1104
|
-
|
1070
|
+
if $stdin.stat.size.positive?
|
1071
|
+
new_note.add($stdin.read.strip)
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
unless args.empty?
|
1075
|
+
new_note.add(args.join(' '))
|
1076
|
+
end
|
1105
1077
|
|
1106
|
-
|
1078
|
+
if options[:editor]
|
1079
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
1107
1080
|
|
1108
1081
|
if options[:remove]
|
1109
|
-
|
1082
|
+
input = Doing::Note.new
|
1110
1083
|
else
|
1111
|
-
|
1084
|
+
input = last_entry.note || Doing::Note.new
|
1112
1085
|
end
|
1113
1086
|
|
1087
|
+
input.add(new_note)
|
1114
1088
|
|
1115
|
-
|
1116
|
-
input.add(ask_note) unless ask_note.empty?
|
1117
|
-
|
1118
|
-
input = wwid.fork_editor(prev_input.strip_lines.join("\n"), message: nil).strip
|
1119
|
-
note = input
|
1089
|
+
new_note = Doing::Note.new(wwid.fork_editor(input.strip_lines.join("\n"), message: nil).strip)
|
1120
1090
|
options[:remove] = true
|
1121
|
-
|
1122
|
-
elsif !args.empty?
|
1123
|
-
new_note.add(args.join(' '))
|
1124
|
-
elsif $stdin.stat.size.positive?
|
1125
|
-
new_note.add($stdin.read.strip)
|
1126
|
-
else
|
1127
|
-
raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || !ask_note.empty?
|
1091
|
+
end
|
1128
1092
|
|
1093
|
+
if (new_note.empty? && !options[:remove]) || options[:ask]
|
1094
|
+
$stderr.puts last_note unless last_note.empty?
|
1095
|
+
$stderr.puts new_note unless new_note.empty?
|
1096
|
+
new_note.add(Doing::Prompt.read_lines(prompt: 'Add a note'))
|
1129
1097
|
end
|
1130
1098
|
|
1131
|
-
|
1099
|
+
raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || !new_note.empty?
|
1132
1100
|
|
1133
1101
|
if last_note.equal?(new_note)
|
1134
1102
|
Doing.logger.debug('Skipped:', 'No note change')
|
@@ -1169,7 +1137,7 @@ command %i[now next] do |c|
|
|
1169
1137
|
|
1170
1138
|
c.desc 'Backdate start time [4pm|20m|2h|"yesterday noon"]'
|
1171
1139
|
c.arg_name 'DATE_STRING'
|
1172
|
-
c.flag %i[b back started]
|
1140
|
+
c.flag %i[b back started], type: DateBeginString
|
1173
1141
|
|
1174
1142
|
c.desc 'Timed entry, marks last entry in section as @done'
|
1175
1143
|
c.switch %i[f finish_last], negatable: false, default_value: false
|
@@ -1187,7 +1155,7 @@ command %i[now next] do |c|
|
|
1187
1155
|
|
1188
1156
|
c.action do |_global_options, options, args|
|
1189
1157
|
if options[:back]
|
1190
|
-
date = options[:back]
|
1158
|
+
date = options[:back]
|
1191
1159
|
|
1192
1160
|
raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
|
1193
1161
|
else
|
@@ -1211,7 +1179,7 @@ command %i[now next] do |c|
|
|
1211
1179
|
input += "\n#{ask_note}" unless ask_note.empty?
|
1212
1180
|
input = wwid.fork_editor(input).strip
|
1213
1181
|
|
1214
|
-
|
1182
|
+
d, title, note = wwid.format_input(input)
|
1215
1183
|
raise EmptyInput, 'No content' if title.strip.empty?
|
1216
1184
|
|
1217
1185
|
if ask_note.empty? && options[:ask]
|
@@ -1219,6 +1187,7 @@ command %i[now next] do |c|
|
|
1219
1187
|
note.add(ask_note) unless ask_note.empty?
|
1220
1188
|
end
|
1221
1189
|
|
1190
|
+
date = d.nil? ? date : d
|
1222
1191
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
1223
1192
|
wwid.write(wwid.doing_file)
|
1224
1193
|
elsif args.length.positive?
|
@@ -1265,7 +1234,7 @@ desc 'Reset the start time of an entry'
|
|
1265
1234
|
long_desc 'Update the start time of the last entry or the last entry matching a tag/search filter.
|
1266
1235
|
If no argument is provided, the start time will be reset to the current time.
|
1267
1236
|
If a date string is provided as an argument, the start time will be set to the parsed result.'
|
1268
|
-
arg_name 'DATE_STRING'
|
1237
|
+
arg_name 'DATE_STRING', optional: true
|
1269
1238
|
command %i[reset begin] do |c|
|
1270
1239
|
c.example 'doing reset', desc: 'Reset the start time of the last entry to the current time'
|
1271
1240
|
c.example 'doing reset --tag project1', desc: 'Reset the start time of the most recent entry tagged @project1 to the current time'
|
@@ -1279,6 +1248,9 @@ command %i[reset begin] do |c|
|
|
1279
1248
|
c.desc 'Resume entry (remove @done)'
|
1280
1249
|
c.switch %i[r resume], default_value: true
|
1281
1250
|
|
1251
|
+
c.desc 'Change start date but do not remove @done (shortcut for --no-resume)'
|
1252
|
+
c.switch [:n]
|
1253
|
+
|
1282
1254
|
c.desc 'Reset last entry matching tag. Wildcards allowed (*, ?)'
|
1283
1255
|
c.arg_name 'TAG'
|
1284
1256
|
c.flag [:tag]
|
@@ -1416,11 +1388,11 @@ command :select do |c|
|
|
1416
1388
|
|
1417
1389
|
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'
|
1418
1390
|
c.arg_name 'DATE_STRING'
|
1419
|
-
c.flag [:before]
|
1391
|
+
c.flag [:before], type: DateBeginString
|
1420
1392
|
|
1421
1393
|
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'
|
1422
1394
|
c.arg_name 'DATE_STRING'
|
1423
|
-
c.flag [:after]
|
1395
|
+
c.flag [:after], type: DateEndString
|
1424
1396
|
|
1425
1397
|
c.desc %(
|
1426
1398
|
Date range to show, or a single day to filter date on.
|
@@ -1431,7 +1403,7 @@ command :select do |c|
|
|
1431
1403
|
by time of day.
|
1432
1404
|
)
|
1433
1405
|
c.arg_name 'DATE_OR_RANGE'
|
1434
|
-
c.flag [:from]
|
1406
|
+
c.flag [:from], type: DateRangeString
|
1435
1407
|
|
1436
1408
|
c.desc 'Force exact search string matching (case sensitive)'
|
1437
1409
|
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
@@ -1698,11 +1670,11 @@ command %i[grep search] do |c|
|
|
1698
1670
|
|
1699
1671
|
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'
|
1700
1672
|
c.arg_name 'DATE_STRING'
|
1701
|
-
c.flag [:before]
|
1673
|
+
c.flag [:before], type: DateBeginString
|
1702
1674
|
|
1703
1675
|
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'
|
1704
1676
|
c.arg_name 'DATE_STRING'
|
1705
|
-
c.flag [:after]
|
1677
|
+
c.flag [:after], type: DateEndString
|
1706
1678
|
|
1707
1679
|
c.desc %(
|
1708
1680
|
Date range to show, or a single day to filter date on.
|
@@ -1713,12 +1685,20 @@ command %i[grep search] do |c|
|
|
1713
1685
|
by time of day.
|
1714
1686
|
)
|
1715
1687
|
c.arg_name 'DATE_OR_RANGE'
|
1716
|
-
c.flag [:from]
|
1688
|
+
c.flag [:from], type: DateRangeString
|
1717
1689
|
|
1718
1690
|
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
1719
1691
|
c.arg_name 'FORMAT'
|
1720
1692
|
c.flag %i[o output]
|
1721
1693
|
|
1694
|
+
c.desc "Output using a template from configuration"
|
1695
|
+
c.arg_name 'TEMPLATE_KEY'
|
1696
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
1697
|
+
|
1698
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
1699
|
+
c.arg_name 'TEMPLATE_STRING'
|
1700
|
+
c.flag [:template]
|
1701
|
+
|
1722
1702
|
c.desc 'Show time intervals on @done tasks'
|
1723
1703
|
c.switch %i[t times], default_value: true, negatable: true
|
1724
1704
|
|
@@ -1773,7 +1753,7 @@ command %i[grep search] do |c|
|
|
1773
1753
|
options[:fuzzy] = false
|
1774
1754
|
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
1775
1755
|
|
1776
|
-
template = settings['templates'][
|
1756
|
+
template = settings['templates'][options[:config_template]].deep_merge(settings)
|
1777
1757
|
tags_color = template.key?('tags_color') ? template['tags_color'] : nil
|
1778
1758
|
|
1779
1759
|
section = wwid.guess_section(options[:section]) if options[:section]
|
@@ -1830,6 +1810,14 @@ command :last do |c|
|
|
1830
1810
|
c.arg_name 'QUERY'
|
1831
1811
|
c.flag [:search]
|
1832
1812
|
|
1813
|
+
c.desc "Output using a template from configuration"
|
1814
|
+
c.arg_name 'TEMPLATE_KEY'
|
1815
|
+
c.flag [:config_template], type: TemplateName, default_value: 'last'
|
1816
|
+
|
1817
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
1818
|
+
c.arg_name 'TEMPLATE_STRING'
|
1819
|
+
c.flag [:template]
|
1820
|
+
|
1833
1821
|
c.desc "Highlight search matches in output. Only affects command line output"
|
1834
1822
|
c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
|
1835
1823
|
|
@@ -1883,6 +1871,8 @@ command :last do |c|
|
|
1883
1871
|
else
|
1884
1872
|
last = wwid.last(times: true, section: options[:section],
|
1885
1873
|
options: {
|
1874
|
+
config_template: options[:config_template],
|
1875
|
+
template: options[:template],
|
1886
1876
|
duration: options[:duration],
|
1887
1877
|
search: options[:search],
|
1888
1878
|
fuzzy: options[:fuzzy],
|
@@ -1917,6 +1907,14 @@ command :recent do |c|
|
|
1917
1907
|
c.desc 'Show time intervals on @done tasks'
|
1918
1908
|
c.switch %i[t times], default_value: true, negatable: true
|
1919
1909
|
|
1910
|
+
c.desc "Output using a template from configuration"
|
1911
|
+
c.arg_name 'TEMPLATE_KEY'
|
1912
|
+
c.flag [:config_template], type: TemplateName, default_value: 'recent'
|
1913
|
+
|
1914
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
1915
|
+
c.arg_name 'TEMPLATE_STRING'
|
1916
|
+
c.flag [:template]
|
1917
|
+
|
1920
1918
|
c.desc 'Show elapsed time on entries without @done tag'
|
1921
1919
|
c.switch [:duration]
|
1922
1920
|
|
@@ -1960,7 +1958,9 @@ command :recent do |c|
|
|
1960
1958
|
times: options[:times],
|
1961
1959
|
totals: options[:totals],
|
1962
1960
|
interactive: options[:interactive],
|
1963
|
-
duration: options[:duration]
|
1961
|
+
duration: options[:duration],
|
1962
|
+
config_template: options[:config_template],
|
1963
|
+
template: options[:template]
|
1964
1964
|
}
|
1965
1965
|
|
1966
1966
|
Doing::Pager::page wwid.recent(count, section.cap_first, opts)
|
@@ -2011,11 +2011,11 @@ command :show do |c|
|
|
2011
2011
|
|
2012
2012
|
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'
|
2013
2013
|
c.arg_name 'DATE_STRING'
|
2014
|
-
c.flag [:before]
|
2014
|
+
c.flag [:before], type: DateBeginString
|
2015
2015
|
|
2016
2016
|
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'
|
2017
2017
|
c.arg_name 'DATE_STRING'
|
2018
|
-
c.flag [:after]
|
2018
|
+
c.flag [:after], type: DateEndString
|
2019
2019
|
|
2020
2020
|
c.desc %(
|
2021
2021
|
Date range to show, or a single day to filter date on.
|
@@ -2027,7 +2027,7 @@ command :show do |c|
|
|
2027
2027
|
)
|
2028
2028
|
|
2029
2029
|
c.arg_name 'DATE_OR_RANGE'
|
2030
|
-
c.flag [:from]
|
2030
|
+
c.flag [:from], type: DateRangeString
|
2031
2031
|
|
2032
2032
|
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
2033
2033
|
c.arg_name 'QUERY'
|
@@ -2075,6 +2075,14 @@ command :show do |c|
|
|
2075
2075
|
c.desc 'Only show items with recorded time intervals'
|
2076
2076
|
c.switch [:only_timed], default_value: false, negatable: false
|
2077
2077
|
|
2078
|
+
c.desc "Output using a template from configuration"
|
2079
|
+
c.arg_name 'TEMPLATE_KEY'
|
2080
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
2081
|
+
|
2082
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
2083
|
+
c.arg_name 'TEMPLATE_STRING'
|
2084
|
+
c.flag [:template]
|
2085
|
+
|
2078
2086
|
c.desc 'Select section or tag to display from a menu'
|
2079
2087
|
c.switch %i[m menu], negatable: false, default_value: false
|
2080
2088
|
|
@@ -2131,7 +2139,7 @@ command :show do |c|
|
|
2131
2139
|
|
2132
2140
|
options[:times] = true if options[:totals]
|
2133
2141
|
|
2134
|
-
template = settings['templates'][
|
2142
|
+
template = settings['templates'][options[:config_template]].deep_merge({
|
2135
2143
|
'wrap_width' => settings['wrap_width'] || 0,
|
2136
2144
|
'date_format' => settings['default_date_format'],
|
2137
2145
|
'order' => settings['order'] || 'asc',
|
@@ -2195,6 +2203,7 @@ end
|
|
2195
2203
|
|
2196
2204
|
# @@tags
|
2197
2205
|
desc 'List all tags in the current Doing file'
|
2206
|
+
arg_name 'MAX_COUNT', optional: true, type: Integer
|
2198
2207
|
command :tags do |c|
|
2199
2208
|
c.desc 'Section'
|
2200
2209
|
c.arg_name 'SECTION_NAME'
|
@@ -2203,6 +2212,9 @@ command :tags do |c|
|
|
2203
2212
|
c.desc 'Show count of occurrences'
|
2204
2213
|
c.switch %i[c counts]
|
2205
2214
|
|
2215
|
+
c.desc 'Output in a single line with @ symbols. Ignored if --counts is specified.'
|
2216
|
+
c.switch %i[l line]
|
2217
|
+
|
2206
2218
|
c.desc 'Sort by name or count'
|
2207
2219
|
c.arg_name 'SORT_ORDER'
|
2208
2220
|
c.flag %i[sort], default_value: 'name', must_match: /^(?:n(?:ame)?|c(?:ount)?)$/
|
@@ -2246,6 +2258,7 @@ command :tags do |c|
|
|
2246
2258
|
|
2247
2259
|
c.action do |_global, options, args|
|
2248
2260
|
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
2261
|
+
options[:count] = args.count.positive? ? args[0].to_i : 0
|
2249
2262
|
|
2250
2263
|
items = wwid.filter_items([], opt: options)
|
2251
2264
|
|
@@ -2273,7 +2286,11 @@ command :tags do |c|
|
|
2273
2286
|
if options[:counts]
|
2274
2287
|
tags.each { |t, c| puts "#{t} (#{c})" }
|
2275
2288
|
else
|
2276
|
-
|
2289
|
+
if options[:line]
|
2290
|
+
puts tags.map { |t, c| t }.to_tags.join(' ')
|
2291
|
+
else
|
2292
|
+
tags.each { |t, c| puts "#{t}" }
|
2293
|
+
end
|
2277
2294
|
end
|
2278
2295
|
end
|
2279
2296
|
end
|
@@ -2311,6 +2328,14 @@ command :today do |c|
|
|
2311
2328
|
c.arg_name 'FORMAT'
|
2312
2329
|
c.flag %i[o output]
|
2313
2330
|
|
2331
|
+
c.desc "Output using a template from configuration"
|
2332
|
+
c.arg_name 'TEMPLATE_KEY'
|
2333
|
+
c.flag [:config_template], type: TemplateName, default_value: 'today'
|
2334
|
+
|
2335
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
2336
|
+
c.arg_name 'TEMPLATE_STRING'
|
2337
|
+
c.flag [:template]
|
2338
|
+
|
2314
2339
|
c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
|
2315
2340
|
c.arg_name 'TIME_STRING'
|
2316
2341
|
c.flag [:before]
|
@@ -2322,21 +2347,21 @@ command :today do |c|
|
|
2322
2347
|
c.desc %(
|
2323
2348
|
Time range to show `doing today --from "12pm to 4pm"`
|
2324
2349
|
)
|
2325
|
-
c.arg_name '
|
2326
|
-
c.flag [:from]
|
2350
|
+
c.arg_name 'TIME_RANGE'
|
2351
|
+
c.flag [:from], type: DateRangeString
|
2327
2352
|
|
2328
2353
|
c.action do |_global_options, options, _args|
|
2329
2354
|
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
2330
2355
|
|
2331
2356
|
options[:times] = true if options[:totals]
|
2332
2357
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2333
|
-
filter_options = %i[after before duration from section sort_tags totals].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
|
2358
|
+
filter_options = %i[after before duration from section sort_tags totals template config_template].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
|
2334
2359
|
|
2335
2360
|
Doing::Pager.page wwid.today(options[:times], options[:output], filter_options).chomp
|
2336
2361
|
end
|
2337
2362
|
end
|
2338
2363
|
|
2339
|
-
#
|
2364
|
+
# @@on
|
2340
2365
|
desc 'List entries for a date'
|
2341
2366
|
long_desc %(Date argument can be natural language. "thursday" would be interpreted as "last thursday,"
|
2342
2367
|
and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates,
|
@@ -2370,21 +2395,24 @@ command :on do |c|
|
|
2370
2395
|
c.arg_name 'FORMAT'
|
2371
2396
|
c.flag %i[o output]
|
2372
2397
|
|
2398
|
+
c.desc "Output using a template from configuration"
|
2399
|
+
c.arg_name 'TEMPLATE_KEY'
|
2400
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
2401
|
+
|
2402
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
2403
|
+
c.arg_name 'TEMPLATE_STRING'
|
2404
|
+
c.flag [:template]
|
2405
|
+
|
2373
2406
|
c.action do |_global_options, options, args|
|
2374
2407
|
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
2375
2408
|
|
2376
2409
|
raise MissingArgument, 'Missing date argument' if args.empty?
|
2377
2410
|
|
2378
|
-
date_string = args.join(' ')
|
2379
|
-
|
2380
|
-
|
2381
|
-
dates = date_string.split(/ (to|through|thru) /)
|
2382
|
-
start = dates[0].chronify(guess: :begin)
|
2383
|
-
finish = dates[2].chronify(guess: :end)
|
2384
|
-
else
|
2385
|
-
start = date_string.chronify(guess: :begin)
|
2386
|
-
finish = false
|
2411
|
+
date_string = args.join(' ').strip
|
2412
|
+
if date_string =~ /^tod(?:ay)?/i
|
2413
|
+
date_string = 'today to tomorrow 12am'
|
2387
2414
|
end
|
2415
|
+
start, finish = date_string.split_date_range
|
2388
2416
|
|
2389
2417
|
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
2390
2418
|
|
@@ -2396,7 +2424,7 @@ command :on do |c|
|
|
2396
2424
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2397
2425
|
|
2398
2426
|
Doing::Pager.page wwid.list_date([start, finish], options[:section], options[:times], options[:output],
|
2399
|
-
{ duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
2427
|
+
{ template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
2400
2428
|
end
|
2401
2429
|
end
|
2402
2430
|
|
@@ -2432,6 +2460,14 @@ command :since do |c|
|
|
2432
2460
|
c.arg_name 'FORMAT'
|
2433
2461
|
c.flag %i[o output]
|
2434
2462
|
|
2463
|
+
c.desc "Output using a template from configuration"
|
2464
|
+
c.arg_name 'TEMPLATE_KEY'
|
2465
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
2466
|
+
|
2467
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
2468
|
+
c.arg_name 'TEMPLATE_STRING'
|
2469
|
+
c.flag [:template]
|
2470
|
+
|
2435
2471
|
c.action do |_global_options, options, args|
|
2436
2472
|
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
2437
2473
|
|
@@ -2453,7 +2489,7 @@ command :since do |c|
|
|
2453
2489
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2454
2490
|
|
2455
2491
|
Doing::Pager.page wwid.list_date([start, finish], options[:section], options[:times], options[:output],
|
2456
|
-
{ duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
2492
|
+
{ template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
2457
2493
|
end
|
2458
2494
|
end
|
2459
2495
|
|
@@ -2536,11 +2572,11 @@ command :view do |c|
|
|
2536
2572
|
|
2537
2573
|
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'
|
2538
2574
|
c.arg_name 'DATE_STRING'
|
2539
|
-
c.flag [:before]
|
2575
|
+
c.flag [:before], type: DateBeginString
|
2540
2576
|
|
2541
2577
|
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'
|
2542
2578
|
c.arg_name 'DATE_STRING'
|
2543
|
-
c.flag [:after]
|
2579
|
+
c.flag [:after], type: DateEndString
|
2544
2580
|
|
2545
2581
|
c.desc %(
|
2546
2582
|
Date range to show, or a single day to filter date on.
|
@@ -2551,7 +2587,7 @@ command :view do |c|
|
|
2551
2587
|
by time of day.
|
2552
2588
|
)
|
2553
2589
|
c.arg_name 'DATE_OR_RANGE'
|
2554
|
-
c.flag [:from]
|
2590
|
+
c.flag [:from], type: DateRangeString
|
2555
2591
|
|
2556
2592
|
c.desc 'Only show items with recorded time intervals (override view settings)'
|
2557
2593
|
c.switch [:only_timed], default_value: false, negatable: false
|
@@ -2588,17 +2624,17 @@ command :view do |c|
|
|
2588
2624
|
view = wwid.get_view(title)
|
2589
2625
|
|
2590
2626
|
if view
|
2591
|
-
page_title = view
|
2627
|
+
page_title = view['title'] || title.cap_first
|
2592
2628
|
only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
|
2593
2629
|
true
|
2594
2630
|
else
|
2595
2631
|
false
|
2596
2632
|
end
|
2597
2633
|
|
2598
|
-
template = view
|
2599
|
-
date_format = view
|
2634
|
+
template = view['template'] || nil
|
2635
|
+
date_format = view['date_format'] || nil
|
2600
2636
|
|
2601
|
-
tags_color = view
|
2637
|
+
tags_color = view['tags_color'] || nil
|
2602
2638
|
tag_filter = false
|
2603
2639
|
if options[:tag]
|
2604
2640
|
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
@@ -2628,19 +2664,19 @@ command :view do |c|
|
|
2628
2664
|
section = if options[:section]
|
2629
2665
|
section
|
2630
2666
|
else
|
2631
|
-
view
|
2667
|
+
view['section'] || settings['current_section']
|
2632
2668
|
end
|
2633
|
-
order = view
|
2669
|
+
order = view['order']&.normalize_order || 'asc'
|
2634
2670
|
|
2635
2671
|
totals = if options[:totals]
|
2636
2672
|
true
|
2637
2673
|
else
|
2638
|
-
view
|
2674
|
+
view['totals'] || false
|
2639
2675
|
end
|
2640
2676
|
tag_order = if options[:tag_order]
|
2641
2677
|
options[:tag_order].normalize_order
|
2642
2678
|
else
|
2643
|
-
view
|
2679
|
+
view['tag_order']&.normalize_order || 'asc'
|
2644
2680
|
end
|
2645
2681
|
|
2646
2682
|
options[:times] = true if totals
|
@@ -2669,7 +2705,6 @@ command :view do |c|
|
|
2669
2705
|
|
2670
2706
|
opts = options.dup
|
2671
2707
|
opts[:age] = options[:age].normalize_age(:newest)
|
2672
|
-
opts[:view_template] = title
|
2673
2708
|
opts[:count] = count
|
2674
2709
|
opts[:format] = date_format
|
2675
2710
|
opts[:highlight] = options[:color]
|
@@ -2686,6 +2721,7 @@ command :view do |c|
|
|
2686
2721
|
opts[:tags_color] = tags_color
|
2687
2722
|
opts[:template] = template
|
2688
2723
|
opts[:totals] = totals
|
2724
|
+
opts[:view_template] = title
|
2689
2725
|
|
2690
2726
|
Doing::Pager.page wwid.list_section(opts)
|
2691
2727
|
elsif title.instance_of?(FalseClass)
|
@@ -2713,6 +2749,14 @@ command :yesterday do |c|
|
|
2713
2749
|
c.arg_name 'FORMAT'
|
2714
2750
|
c.flag %i[o output]
|
2715
2751
|
|
2752
|
+
c.desc "Output using a template from configuration"
|
2753
|
+
c.arg_name 'TEMPLATE_KEY'
|
2754
|
+
c.flag [:config_template], type: TemplateName, default_value: 'today'
|
2755
|
+
|
2756
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
2757
|
+
c.arg_name 'TEMPLATE_STRING'
|
2758
|
+
c.flag [:template]
|
2759
|
+
|
2716
2760
|
c.desc 'Show time intervals on @done tasks'
|
2717
2761
|
c.switch %i[t times], default_value: true, negatable: true
|
2718
2762
|
|
@@ -2735,11 +2779,9 @@ command :yesterday do |c|
|
|
2735
2779
|
c.arg_name 'TIME_STRING'
|
2736
2780
|
c.flag [:after]
|
2737
2781
|
|
2738
|
-
c.desc
|
2739
|
-
Time range to show, e.g. `doing yesterday --from "1am to 8am"`
|
2740
|
-
)
|
2782
|
+
c.desc 'Time range to show, e.g. `doing yesterday --from "1am to 8am"`'
|
2741
2783
|
c.arg_name 'TIME_RANGE'
|
2742
|
-
c.flag [:from]
|
2784
|
+
c.flag [:from], must_match: REGEX_TIME_RANGE
|
2743
2785
|
|
2744
2786
|
c.desc 'Tag sort direction (asc|desc)'
|
2745
2787
|
c.arg_name 'DIRECTION'
|
@@ -2751,21 +2793,15 @@ command :yesterday do |c|
|
|
2751
2793
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
2752
2794
|
|
2753
2795
|
if options[:from]
|
2754
|
-
options[:from] = options[:from].split(/
|
2796
|
+
options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
|
2755
2797
|
"yesterday #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}"
|
2756
|
-
end.join(' to ')
|
2757
|
-
end
|
2758
|
-
|
2759
|
-
opt =
|
2760
|
-
|
2761
|
-
|
2762
|
-
|
2763
|
-
from: options[:from],
|
2764
|
-
sort_tags: options[:sort_tags],
|
2765
|
-
tag_order: options[:tag_order].normalize_order,
|
2766
|
-
totals: options[:totals],
|
2767
|
-
order: settings.dig('templates', 'today', 'order')
|
2768
|
-
}
|
2798
|
+
end.join(' to ').split_date_range
|
2799
|
+
end
|
2800
|
+
|
2801
|
+
opt = options.dup
|
2802
|
+
opt[:tag_order] = options[:tag_order].normalize_order
|
2803
|
+
opt[:order] = settings.dig('templates', options[:config_template], 'order')
|
2804
|
+
|
2769
2805
|
Doing::Pager.page wwid.yesterday(options[:section], options[:times], options[:output], opt).chomp
|
2770
2806
|
end
|
2771
2807
|
end
|
@@ -2991,8 +3027,7 @@ command :config do |c|
|
|
2991
3027
|
value = options[:remove] ? nil : args.pop
|
2992
3028
|
keypath = args.join('.')
|
2993
3029
|
real_path = config.resolve_key_path(keypath, create: true)
|
2994
|
-
|
2995
|
-
old_value = settings.dig(*real_path) || nil
|
3030
|
+
old_value = settings.dig(*real_path)
|
2996
3031
|
old_type = old_value&.class.to_s || nil
|
2997
3032
|
|
2998
3033
|
if old_value.is_a?(Hash) && !options[:remove]
|
@@ -3018,7 +3053,6 @@ command :config do |c|
|
|
3018
3053
|
else
|
3019
3054
|
current_value = cfg.dig(*real_path)
|
3020
3055
|
cfg.deep_set(real_path, value.set_type(old_type))
|
3021
|
-
|
3022
3056
|
$stderr.puts "#{' Key path:'.yellow} #{real_path.join('->').boldwhite}"
|
3023
3057
|
$stderr.puts "#{'Inherited:'.yellow} #{(old_value ? old_value.to_s : 'empty').boldwhite}"
|
3024
3058
|
$stderr.puts "#{' Current:'.yellow} #{ (current_value ? current_value.to_s : 'empty').boldwhite }"
|
@@ -3178,7 +3212,7 @@ command %i[archive move] do |c|
|
|
3178
3212
|
c.desc 'Archive entries older than date
|
3179
3213
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
3180
3214
|
c.arg_name 'DATE_STRING'
|
3181
|
-
c.flag [:before]
|
3215
|
+
c.flag [:before], type: DateEndString
|
3182
3216
|
|
3183
3217
|
c.action do |_global_options, options, args|
|
3184
3218
|
options[:fuzzy] = false
|
@@ -3269,11 +3303,11 @@ command :import do |c|
|
|
3269
3303
|
# TODO: Allow time range filtering
|
3270
3304
|
c.desc 'Import entries older than date'
|
3271
3305
|
c.arg_name 'DATE_STRING'
|
3272
|
-
c.flag [:before]
|
3306
|
+
c.flag [:before], type: DateBeginString
|
3273
3307
|
|
3274
3308
|
c.desc 'Import entries newer than date'
|
3275
3309
|
c.arg_name 'DATE_STRING'
|
3276
|
-
c.flag [:after]
|
3310
|
+
c.flag [:after], type: DateEndString
|
3277
3311
|
|
3278
3312
|
c.desc %(
|
3279
3313
|
Date range to import. Date range argument should be quoted. Date specifications can be natural language.
|
@@ -3281,7 +3315,7 @@ command :import do |c|
|
|
3281
3315
|
Has no effect unless the import plugin has implemented date range filtering.
|
3282
3316
|
)
|
3283
3317
|
c.arg_name 'DATE_OR_RANGE'
|
3284
|
-
c.flag %i[f from]
|
3318
|
+
c.flag %i[f from], type: DateRangeString
|
3285
3319
|
|
3286
3320
|
c.desc 'Allow entries that overlap existing times'
|
3287
3321
|
c.switch [:overlap], negatable: true
|
@@ -3299,24 +3333,19 @@ command :import do |c|
|
|
3299
3333
|
end
|
3300
3334
|
|
3301
3335
|
if options[:from]
|
3302
|
-
|
3303
|
-
|
3304
|
-
|
3305
|
-
|
3306
|
-
|
3307
|
-
|
3308
|
-
|
3309
|
-
finish = date_string.chronify(guess: :end)
|
3310
|
-
end
|
3311
|
-
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
3312
|
-
dates = [start, finish]
|
3336
|
+
options[:date_filter] = options[:from]
|
3337
|
+
|
3338
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless options[:date_filter][0]
|
3339
|
+
elsif options[:before] || options[:after]
|
3340
|
+
options[:date_filter] = [nil, nil]
|
3341
|
+
options[:date_filter][1] = options[:before] || Time.now + (1 << 64)
|
3342
|
+
options[:date_filter][0] = options[:after] || Time.now - (1 << 64)
|
3313
3343
|
end
|
3314
3344
|
|
3315
3345
|
options[:case] = options[:case].normalize_case
|
3316
3346
|
|
3317
3347
|
if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
|
3318
3348
|
options[:no_overlap] = !options[:overlap]
|
3319
|
-
options[:date_filter] = dates
|
3320
3349
|
wwid.import(args, options)
|
3321
3350
|
wwid.write(wwid.doing_file)
|
3322
3351
|
else
|
@@ -3633,17 +3662,44 @@ end
|
|
3633
3662
|
|
3634
3663
|
# @@changelog @@changes
|
3635
3664
|
desc 'List recent changes in Doing'
|
3636
|
-
long_desc
|
3637
|
-
|
3665
|
+
long_desc %(Display a formatted list of changes in recent versions.
|
3666
|
+
|
3667
|
+
Without flags, displays only the most recent version.
|
3668
|
+
Use --lookup or --all for history.)
|
3669
|
+
command %i[changes changelog] do |c|
|
3670
|
+
c.desc 'Display all versions'
|
3671
|
+
c.switch %i[a all], default_value: false, negatable: false
|
3672
|
+
|
3673
|
+
c.desc %(Look up a specific version. Specify versions as "MAJ.MIN.PATCH", MIN
|
3674
|
+
and PATCH are optional. Use > or < to see all changes since or prior
|
3675
|
+
to a version.)
|
3676
|
+
c.arg_name 'VERSION'
|
3677
|
+
c.flag %i[l lookup], must_match: /^(?:(?:(?:[<>=]|p(?:rior)|b(?:efore)|o(?:lder)|s(?:ince)|a(?:fter)|n(?:ewer))? *[\d.*?]+ *)+|(?:[\d.]+ *-+ *[\d.]+))$/
|
3678
|
+
|
3679
|
+
c.desc %(Show changelogs matching search terms (uses pattern-based searching).
|
3680
|
+
Add slashes to search with regular expressions, e.g. `--search "/output.*flag/"`)
|
3681
|
+
c.flag %i[s search]
|
3682
|
+
|
3683
|
+
c.example 'doing changes', desc: 'View changes in the current version'
|
3684
|
+
c.example 'doing changes --all', desc: 'See the entire changelog'
|
3685
|
+
c.example 'doing changes --lookup 2.0.21', desc: 'See changes from version 2.0.21'
|
3686
|
+
c.example 'doing changes --lookup "> 2.1"', desc: 'See all changes since 2.1.0'
|
3687
|
+
c.example 'doing changes --search "tags +bool"', desc: 'See all changes containing "tags" and "bool"'
|
3688
|
+
c.example 'doing changes -l "> 2.1" -s "pattern"', desc: 'Lookup and search can be combined'
|
3689
|
+
|
3690
|
+
|
3638
3691
|
c.action do |_global_options, options, args|
|
3639
|
-
|
3640
|
-
|
3641
|
-
|
3642
|
-
|
3643
|
-
|
3644
|
-
|
3645
|
-
|
3646
|
-
|
3692
|
+
cl = Doing::Changes.new(lookup: options[:lookup], search: options[:search])
|
3693
|
+
|
3694
|
+
content = if options[:all] || options[:search] || options[:lookup]
|
3695
|
+
cl.to_s
|
3696
|
+
else
|
3697
|
+
cl.latest
|
3698
|
+
end
|
3699
|
+
|
3700
|
+
parsed = TTY::Markdown.parse(content, width: 80, symbols: {override: {bullet: "•"}})
|
3701
|
+
Doing::Pager.paginate = true
|
3702
|
+
Doing::Pager.page parsed
|
3647
3703
|
end
|
3648
3704
|
end
|
3649
3705
|
|
@@ -3667,9 +3723,9 @@ command :commands_accepting do |c|
|
|
3667
3723
|
end
|
3668
3724
|
|
3669
3725
|
if o[:column]
|
3670
|
-
puts cmds
|
3726
|
+
puts cmds.sort
|
3671
3727
|
else
|
3672
|
-
puts "Commands accepting --#{option}: #{cmds.join(', ')}"
|
3728
|
+
puts "Commands accepting --#{option}: #{cmds.sort.join(', ')}"
|
3673
3729
|
end
|
3674
3730
|
end
|
3675
3731
|
end
|
@@ -3700,7 +3756,7 @@ pre do |global, _command, _options, _args|
|
|
3700
3756
|
Doing::Pager.paginate = global[:pager]
|
3701
3757
|
|
3702
3758
|
$stdout.puts "doing v#{Doing::VERSION}" if global[:version]
|
3703
|
-
unless
|
3759
|
+
unless $stdout.isatty
|
3704
3760
|
Doing::Color.coloring = global[:pager] ? global[:color] : false
|
3705
3761
|
else
|
3706
3762
|
Doing::Color.coloring = global[:color]
|
@@ -3714,7 +3770,9 @@ pre do |global, _command, _options, _args|
|
|
3714
3770
|
end
|
3715
3771
|
|
3716
3772
|
on_error do |exception|
|
3717
|
-
if exception.kind_of?(
|
3773
|
+
if exception.kind_of?(GLI::UnknownCommand)
|
3774
|
+
exit run(['now'].concat(ARGV))
|
3775
|
+
elsif exception.kind_of?(SystemExit)
|
3718
3776
|
false
|
3719
3777
|
else
|
3720
3778
|
# Doing.logger.error('Fatal:', exception)
|
@@ -3748,7 +3806,12 @@ around do |global, command, options, arguments, code|
|
|
3748
3806
|
Doing::Prompt.force_answer = false
|
3749
3807
|
Doing.config.force_answer = false
|
3750
3808
|
else
|
3751
|
-
Doing::Prompt.default_answer =
|
3809
|
+
Doing::Prompt.default_answer = if $stdout.isatty
|
3810
|
+
global[:default]
|
3811
|
+
else
|
3812
|
+
true
|
3813
|
+
end
|
3814
|
+
|
3752
3815
|
Doing.config.force_answer = global[:default] ? true : false
|
3753
3816
|
end
|
3754
3817
|
|