doing 2.1.1pre → 2.1.5pre
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 +19 -15
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +58 -8
- data/Gemfile.lock +25 -1
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/bin/doing +447 -149
- data/doc/Array.html +63 -1
- data/doc/BooleanTermParser/Clause.html +293 -0
- data/doc/BooleanTermParser/Operator.html +172 -0
- data/doc/BooleanTermParser/Query.html +417 -0
- data/doc/BooleanTermParser/QueryParser.html +135 -0
- data/doc/BooleanTermParser/QueryTransformer.html +124 -0
- data/doc/BooleanTermParser.html +115 -0
- data/doc/Doing/CLIFormat.html +131 -0
- data/doc/Doing/Color.html +2 -2
- data/doc/Doing/Completion.html +1 -1
- data/doc/Doing/Configuration.html +168 -73
- data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/doc/Doing/Errors/EmptyInput.html +1 -1
- data/doc/Doing/Errors/NoResults.html +1 -1
- data/doc/Doing/Errors/PluginException.html +1 -1
- data/doc/Doing/Errors/UserCancelled.html +1 -1
- data/doc/Doing/Errors/WrongCommand.html +1 -1
- data/doc/Doing/Errors.html +1 -1
- data/doc/Doing/Hooks.html +1 -1
- data/doc/Doing/Item.html +177 -86
- data/doc/Doing/Items.html +36 -2
- data/doc/Doing/LogAdapter.html +70 -1
- data/doc/Doing/Note.html +5 -134
- data/doc/Doing/Pager.html +1 -1
- data/doc/Doing/Plugins.html +380 -40
- data/doc/Doing/Prompt.html +70 -18
- data/doc/Doing/Section.html +1 -1
- data/doc/Doing/TemplateString.html +713 -0
- data/doc/Doing/Util/Backup.html +686 -0
- data/doc/Doing/Util.html +16 -4
- data/doc/Doing/WWID.html +133 -73
- data/doc/Doing.html +4 -4
- data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/doc/GLI/Commands.html +1 -1
- data/doc/GLI.html +1 -1
- data/doc/Hash.html +1 -1
- data/doc/PhraseParser/Operator.html +172 -0
- data/doc/PhraseParser/PhraseClause.html +303 -0
- data/doc/PhraseParser/Query.html +495 -0
- data/doc/PhraseParser/QueryParser.html +136 -0
- data/doc/PhraseParser/QueryTransformer.html +124 -0
- data/doc/PhraseParser/TermClause.html +293 -0
- data/doc/PhraseParser.html +115 -0
- data/doc/Status.html +1 -1
- data/doc/String.html +319 -13
- data/doc/Symbol.html +35 -1
- data/doc/Time.html +70 -2
- data/doc/_index.html +132 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +2 -2
- data/doc/index.html +2 -2
- data/doc/method_list.html +648 -160
- data/doc/top-level-namespace.html +2 -2
- data/doing.gemspec +3 -0
- data/doing.rdoc +263 -82
- data/lib/completion/doing.bash +18 -18
- data/lib/doing/array.rb +9 -0
- data/lib/doing/boolean_term_parser.rb +86 -0
- data/lib/doing/configuration.rb +63 -24
- data/lib/doing/item.rb +112 -10
- data/lib/doing/items.rb +6 -0
- data/lib/doing/log_adapter.rb +28 -0
- data/lib/doing/note.rb +31 -30
- data/lib/doing/phrase_parser.rb +124 -0
- data/lib/doing/plugin_manager.rb +57 -13
- data/lib/doing/plugins/export/dayone_export.rb +209 -0
- data/lib/doing/plugins/export/template_export.rb +113 -81
- data/lib/doing/prompt.rb +26 -13
- data/lib/doing/string.rb +114 -29
- data/lib/doing/string_chronify.rb +5 -1
- data/lib/doing/symbol.rb +4 -0
- data/lib/doing/template_string.rb +197 -0
- data/lib/doing/time.rb +32 -0
- data/lib/doing/util.rb +6 -7
- data/lib/doing/util_backup.rb +287 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +152 -55
- data/lib/doing.rb +9 -0
- data/lib/templates/doing-dayone-entry.erb +6 -0
- data/lib/templates/doing-dayone.erb +5 -0
- metadata +85 -2
data/bin/doing
CHANGED
|
@@ -25,7 +25,7 @@ version Doing::VERSION
|
|
|
25
25
|
hide_commands_without_desc true
|
|
26
26
|
autocomplete_commands true
|
|
27
27
|
|
|
28
|
-
REGEX_BOOL = /^(?:and|all|any|or|not|none)$/i
|
|
28
|
+
REGEX_BOOL = /^(?:and|all|any|or|not|none|p(?:at(?:tern)?)?)$/i
|
|
29
29
|
REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i
|
|
30
30
|
|
|
31
31
|
InvalidExportType = Class.new(RuntimeError)
|
|
@@ -51,11 +51,16 @@ if ENV['DOING_LOG_LEVEL'] || ENV['DOING_DEBUG'] || ENV['DOING_QUIET'] || ENV['DO
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
Doing.logger.benchmark(:total, :start)
|
|
55
|
+
|
|
54
56
|
if ENV['DOING_CONFIG']
|
|
55
57
|
Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
Doing.logger.benchmark(:configure, :start)
|
|
58
61
|
config = Doing.config
|
|
62
|
+
Doing.logger.benchmark(:configure, :finish)
|
|
63
|
+
|
|
59
64
|
settings = config.settings
|
|
60
65
|
wwid.config = settings
|
|
61
66
|
|
|
@@ -195,7 +200,16 @@ command %i[now next] do |c|
|
|
|
195
200
|
end
|
|
196
201
|
|
|
197
202
|
desc 'Reset the start time of an entry'
|
|
203
|
+
long_desc 'Update the start time of the last entry or the last entry matching a tag/search filter.
|
|
204
|
+
If no argument is provided, the start time will be reset to the current time.
|
|
205
|
+
If a date string is provided as an argument, the start time will be set to the parsed result.'
|
|
206
|
+
arg_name 'DATE_STRING'
|
|
198
207
|
command %i[reset begin] do |c|
|
|
208
|
+
c.example 'doing reset', desc: 'Reset the start time of the last entry to the current time'
|
|
209
|
+
c.example 'doing reset --tag project1', desc: 'Reset the start time of the most recent entry tagged @project1 to the current time'
|
|
210
|
+
c.example 'doing reset 3pm', desc: 'Reset the start time of the last entry to 3pm of the current day'
|
|
211
|
+
c.example 'doing begin --tag todo --resume', desc: 'alias for reset. Updates the last @todo entry to the current time, removing @done tag.'
|
|
212
|
+
|
|
199
213
|
c.desc 'Limit search to section'
|
|
200
214
|
c.arg_name 'NAME'
|
|
201
215
|
c.flag %i[s section], default_value: 'All'
|
|
@@ -203,7 +217,7 @@ command %i[reset begin] do |c|
|
|
|
203
217
|
c.desc 'Resume entry (remove @done)'
|
|
204
218
|
c.switch %i[r resume], default_value: true
|
|
205
219
|
|
|
206
|
-
c.desc 'Reset last entry matching tag'
|
|
220
|
+
c.desc 'Reset last entry matching tag. Wildcards allowed (*, ?).'
|
|
207
221
|
c.arg_name 'TAG'
|
|
208
222
|
c.flag [:tag]
|
|
209
223
|
|
|
@@ -215,23 +229,30 @@ command %i[reset begin] do |c|
|
|
|
215
229
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
216
230
|
|
|
217
231
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
218
|
-
c.switch %i[x exact], default_value:
|
|
232
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
219
233
|
|
|
220
234
|
c.desc 'Reset items that *don\'t* match search/tag filters'
|
|
221
235
|
c.switch [:not], default_value: false, negatable: false
|
|
222
236
|
|
|
223
237
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
224
238
|
c.arg_name 'TYPE'
|
|
225
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
239
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
226
240
|
|
|
227
241
|
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
|
228
242
|
c.arg_name 'BOOLEAN'
|
|
229
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
243
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
230
244
|
|
|
231
245
|
c.desc 'Select from a menu of matching entries'
|
|
232
246
|
c.switch %i[i interactive], negatable: false, default_value: false
|
|
233
247
|
|
|
234
248
|
c.action do |global_options, options, args|
|
|
249
|
+
if args.count > 0
|
|
250
|
+
reset_date = args.join(' ').chronify(guess: :begin)
|
|
251
|
+
raise InvalidArgument, 'Invalid date string' unless reset_date
|
|
252
|
+
else
|
|
253
|
+
reset_date = Time.now
|
|
254
|
+
end
|
|
255
|
+
|
|
235
256
|
options[:fuzzy] = false
|
|
236
257
|
if options[:section]
|
|
237
258
|
options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
@@ -259,7 +280,7 @@ command %i[reset begin] do |c|
|
|
|
259
280
|
sort: false,
|
|
260
281
|
show_if_single: true)
|
|
261
282
|
else
|
|
262
|
-
last_entry = items.last
|
|
283
|
+
last_entry = items.reverse.last
|
|
263
284
|
end
|
|
264
285
|
|
|
265
286
|
unless last_entry
|
|
@@ -267,7 +288,7 @@ command %i[reset begin] do |c|
|
|
|
267
288
|
return
|
|
268
289
|
end
|
|
269
290
|
|
|
270
|
-
wwid.reset_item(last_entry, resume: options[:resume])
|
|
291
|
+
wwid.reset_item(last_entry, date: reset_date, resume: options[:resume])
|
|
271
292
|
|
|
272
293
|
# new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
|
|
273
294
|
|
|
@@ -301,7 +322,7 @@ command :note do |c|
|
|
|
301
322
|
c.desc "Replace/Remove last entry's note (default append)"
|
|
302
323
|
c.switch %i[r remove], negatable: false, default_value: false
|
|
303
324
|
|
|
304
|
-
c.desc 'Add/remove note from last entry matching tag'
|
|
325
|
+
c.desc 'Add/remove note from last entry matching tag. Wildcards allowed (*, ?).'
|
|
305
326
|
c.arg_name 'TAG'
|
|
306
327
|
c.flag [:tag]
|
|
307
328
|
|
|
@@ -313,18 +334,18 @@ command :note do |c|
|
|
|
313
334
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
314
335
|
|
|
315
336
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
316
|
-
c.switch %i[x exact], default_value:
|
|
337
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
317
338
|
|
|
318
339
|
c.desc 'Add note to item that *doesn\'t* match search/tag filters'
|
|
319
340
|
c.switch [:not], default_value: false, negatable: false
|
|
320
341
|
|
|
321
342
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
322
343
|
c.arg_name 'TYPE'
|
|
323
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
344
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
324
345
|
|
|
325
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
|
346
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
|
326
347
|
c.arg_name 'BOOLEAN'
|
|
327
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
348
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
328
349
|
|
|
329
350
|
c.desc 'Select item for new note from a menu of matching entries'
|
|
330
351
|
c.switch %i[i interactive], negatable: false, default_value: false
|
|
@@ -394,6 +415,10 @@ command :note do |c|
|
|
|
394
415
|
end
|
|
395
416
|
|
|
396
417
|
desc 'Finish any running @meanwhile tasks and optionally create a new one'
|
|
418
|
+
long_desc 'The @meanwhile tag allows you to have long-running entries that encompass smaller entries.
|
|
419
|
+
This command makes it easy to start and stop these overarching entries. Just run `doing meanwhile Starting work on this
|
|
420
|
+
big project` to start a @meanwhile entry, add other entries as you work on the project, then use `doing meanwhile` by
|
|
421
|
+
itself to mark the entry as @done.'
|
|
397
422
|
arg_name 'ENTRY'
|
|
398
423
|
command :meanwhile do |c|
|
|
399
424
|
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'
|
|
@@ -521,8 +546,19 @@ long_desc 'List all entries and select with typeahead fuzzy matching.
|
|
|
521
546
|
|
|
522
547
|
Multiple selections are allowed, hit tab to add the highlighted entry to the
|
|
523
548
|
selection, and use ctrl-a to select all visible items. Return processes the
|
|
524
|
-
selected entries.
|
|
549
|
+
selected entries.
|
|
550
|
+
|
|
551
|
+
Search in the menu by typing:
|
|
552
|
+
|
|
553
|
+
sbtrkt fuzzy-match Items that match sbtrkt
|
|
554
|
+
|
|
555
|
+
\'wild exact-match (quoted) Items that include wild
|
|
556
|
+
|
|
557
|
+
!fire inverse-exact-match Items that do not include fire'
|
|
525
558
|
command :select do |c|
|
|
559
|
+
c.example 'doing select', desc: 'Select from all entries. A menu of available actions will be presented after confirming the selection.'
|
|
560
|
+
c.example 'doing select --editor', desc: 'Select entries from a menu and batch edit them in your default editor'
|
|
561
|
+
c.example 'doing select --after "yesterday 12pm" --tag project1', desc: 'Display a menu of entries created after noon yesterday, add @project1 to selected entries'
|
|
526
562
|
c.desc 'Select from a specific section'
|
|
527
563
|
c.arg_name 'SECTION'
|
|
528
564
|
c.flag %i[s section]
|
|
@@ -568,14 +604,14 @@ command :select do |c|
|
|
|
568
604
|
c.flag [:from]
|
|
569
605
|
|
|
570
606
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
571
|
-
c.switch %i[x exact], default_value:
|
|
607
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
572
608
|
|
|
573
609
|
c.desc 'Select items that *don\'t* match search/tag filters'
|
|
574
610
|
c.switch [:not], default_value: false, negatable: false
|
|
575
611
|
|
|
576
612
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
577
613
|
c.arg_name 'TYPE'
|
|
578
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
614
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
579
615
|
|
|
580
616
|
c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches.'
|
|
581
617
|
c.switch %i[menu], negatable: true, default_value: true
|
|
@@ -680,6 +716,9 @@ command :later do |c|
|
|
|
680
716
|
end
|
|
681
717
|
|
|
682
718
|
desc 'Add a completed item with @done(date). No argument finishes last entry.'
|
|
719
|
+
desc 'Use this command to add an entry after you\'ve already finished it. It will be immediately marked as @done.
|
|
720
|
+
You can modify the start and end times of the entry using the --back, --took, and --at flags, making it an easy
|
|
721
|
+
way to add entries in post and maintain accurate (albeit manual) time tracking.'
|
|
683
722
|
arg_name 'ENTRY'
|
|
684
723
|
command %i[done did] do |c|
|
|
685
724
|
c.example 'doing done', desc: 'Tag the last entry @done'
|
|
@@ -890,13 +929,13 @@ command :cancel do |c|
|
|
|
890
929
|
c.arg_name 'NAME'
|
|
891
930
|
c.flag %i[s section]
|
|
892
931
|
|
|
893
|
-
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2)'
|
|
932
|
+
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2). Wildcards allowed (*, ?).'
|
|
894
933
|
c.arg_name 'TAG'
|
|
895
934
|
c.flag [:tag]
|
|
896
935
|
|
|
897
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
|
936
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
|
898
937
|
c.arg_name 'BOOLEAN'
|
|
899
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
938
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
900
939
|
|
|
901
940
|
c.desc 'Cancel the last X entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
|
|
902
941
|
c.arg_name 'QUERY'
|
|
@@ -906,14 +945,14 @@ command :cancel do |c|
|
|
|
906
945
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
907
946
|
|
|
908
947
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
909
|
-
c.switch %i[x exact], default_value:
|
|
948
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
910
949
|
|
|
911
950
|
c.desc 'Finish items that *don\'t* match search/tag filters'
|
|
912
951
|
c.switch [:not], default_value: false, negatable: false
|
|
913
952
|
|
|
914
953
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
915
954
|
c.arg_name 'TYPE'
|
|
916
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
955
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
917
956
|
|
|
918
957
|
c.desc 'Cancel last entry (or entries) not already marked @done'
|
|
919
958
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
|
@@ -997,7 +1036,7 @@ command :finish do |c|
|
|
|
997
1036
|
c.flag [:at]
|
|
998
1037
|
|
|
999
1038
|
c.desc 'Finish the last X entries containing TAG.
|
|
1000
|
-
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
|
1039
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
|
1001
1040
|
c.arg_name 'TAG'
|
|
1002
1041
|
c.flag [:tag]
|
|
1003
1042
|
|
|
@@ -1009,18 +1048,18 @@ command :finish do |c|
|
|
|
1009
1048
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1010
1049
|
|
|
1011
1050
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
1012
|
-
c.switch %i[x exact], default_value:
|
|
1051
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1013
1052
|
|
|
1014
1053
|
c.desc 'Finish items that *don\'t* match search/tag filters'
|
|
1015
1054
|
c.switch [:not], default_value: false, negatable: false
|
|
1016
1055
|
|
|
1017
1056
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1018
1057
|
c.arg_name 'TYPE'
|
|
1019
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
1058
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1020
1059
|
|
|
1021
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
|
1060
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
|
1022
1061
|
c.arg_name 'BOOLEAN'
|
|
1023
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
1062
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
1024
1063
|
|
|
1025
1064
|
c.desc 'Remove done tag'
|
|
1026
1065
|
c.switch %i[r remove], negatable: false, default_value: false
|
|
@@ -1119,7 +1158,14 @@ command :finish do |c|
|
|
|
1119
1158
|
end
|
|
1120
1159
|
|
|
1121
1160
|
desc 'Repeat last entry as new entry'
|
|
1161
|
+
long_desc 'This command is designed to allow multiple time intervals to be created for an entry by duplicating it with a new start (and end, eventually) time.'
|
|
1122
1162
|
command %i[again resume] do |c|
|
|
1163
|
+
c.example 'doing resume', desc: 'Duplicate the most recent entry with a new start time, removing any @done tag'
|
|
1164
|
+
c.example 'doing again', desc: 'again is an alias for resume'
|
|
1165
|
+
c.example 'doing resume --editor', desc: 'Repeat the last entry, opening the new entry in the default editor'
|
|
1166
|
+
c.example 'doing resume --tag project1 --in Projects', desc: 'Repeat the last entry tagged @project1, creating the new entry in the Projects section'
|
|
1167
|
+
c.example 'doing resume --interactive', desc: 'Select the entry to repeat from a menu'
|
|
1168
|
+
|
|
1123
1169
|
c.desc 'Get last entry from a specific section'
|
|
1124
1170
|
c.arg_name 'NAME'
|
|
1125
1171
|
c.flag %i[s section], default_value: 'All'
|
|
@@ -1128,7 +1174,7 @@ command %i[again resume] do |c|
|
|
|
1128
1174
|
c.arg_name 'SECTION_NAME'
|
|
1129
1175
|
c.flag [:in]
|
|
1130
1176
|
|
|
1131
|
-
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma.'
|
|
1177
|
+
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
|
1132
1178
|
c.arg_name 'TAG'
|
|
1133
1179
|
c.flag [:tag]
|
|
1134
1180
|
|
|
@@ -1141,18 +1187,18 @@ command %i[again resume] do |c|
|
|
|
1141
1187
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1142
1188
|
|
|
1143
1189
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
1144
|
-
c.switch %i[x exact], default_value:
|
|
1190
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1145
1191
|
|
|
1146
1192
|
c.desc 'Resume items that *don\'t* match search/tag filters'
|
|
1147
1193
|
c.switch [:not], default_value: false, negatable: false
|
|
1148
1194
|
|
|
1149
1195
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1150
1196
|
c.arg_name 'TYPE'
|
|
1151
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
1197
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1152
1198
|
|
|
1153
|
-
c.desc 'Boolean used to combine multiple tags'
|
|
1199
|
+
c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans.'
|
|
1154
1200
|
c.arg_name 'BOOLEAN'
|
|
1155
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
1201
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
1156
1202
|
|
|
1157
1203
|
c.desc "Edit duplicated entry with #{Doing::Util.default_editor} before adding"
|
|
1158
1204
|
c.switch %i[e editor], negatable: false, default_value: false
|
|
@@ -1186,6 +1232,77 @@ command %i[again resume] do |c|
|
|
|
1186
1232
|
end
|
|
1187
1233
|
end
|
|
1188
1234
|
|
|
1235
|
+
desc 'List all tags in the current Doing file'
|
|
1236
|
+
command :tags do |c|
|
|
1237
|
+
c.desc 'Section'
|
|
1238
|
+
c.arg_name 'SECTION_NAME'
|
|
1239
|
+
c.flag %i[s section], default_value: 'All'
|
|
1240
|
+
|
|
1241
|
+
c.desc 'Show count of occurrences'
|
|
1242
|
+
c.switch %i[c counts]
|
|
1243
|
+
|
|
1244
|
+
c.desc 'Sort by name or count'
|
|
1245
|
+
c.arg_name 'SORT_ORDER'
|
|
1246
|
+
c.flag %i[sort], default_value: 'name', must_match: /^(?:n(?:ame)?|c(?:ount)?)$/
|
|
1247
|
+
|
|
1248
|
+
c.desc 'Sort order (asc/desc)'
|
|
1249
|
+
c.arg_name 'ORDER'
|
|
1250
|
+
c.flag %i[o order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
|
1251
|
+
|
|
1252
|
+
c.desc 'Get tags for entries matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
|
1253
|
+
c.arg_name 'TAG'
|
|
1254
|
+
c.flag [:tag]
|
|
1255
|
+
|
|
1256
|
+
c.desc 'Get tags for items matching search. Surround with
|
|
1257
|
+
slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
|
|
1258
|
+
c.arg_name 'QUERY'
|
|
1259
|
+
c.flag [:search]
|
|
1260
|
+
|
|
1261
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
1262
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1263
|
+
|
|
1264
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
1265
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1266
|
+
|
|
1267
|
+
c.desc 'Get tags from items that *don\'t* match search/tag filters'
|
|
1268
|
+
c.switch [:not], default_value: false, negatable: false
|
|
1269
|
+
|
|
1270
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1271
|
+
c.arg_name 'TYPE'
|
|
1272
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1273
|
+
|
|
1274
|
+
c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans.'
|
|
1275
|
+
c.arg_name 'BOOLEAN'
|
|
1276
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
1277
|
+
|
|
1278
|
+
c.desc 'Select items to scan from a menu of matching entries'
|
|
1279
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
1280
|
+
|
|
1281
|
+
c.action do |_global, options, args|
|
|
1282
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
1283
|
+
|
|
1284
|
+
items = wwid.filter_items([], opt: options)
|
|
1285
|
+
|
|
1286
|
+
# items = wwid.content.in_section(section)
|
|
1287
|
+
tags = wwid.all_tags(items, counts: true)
|
|
1288
|
+
|
|
1289
|
+
if options[:sort] =~ /^n/i
|
|
1290
|
+
tags = tags.sort_by { |tag, count| tag }
|
|
1291
|
+
else
|
|
1292
|
+
tags = tags.sort_by { |tag, count| count }
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
tags.reverse! if options[:order].normalize_order == 'desc'
|
|
1296
|
+
|
|
1297
|
+
if options[:counts]
|
|
1298
|
+
tags.each { |t, c| puts "#{t} (#{c})" }
|
|
1299
|
+
else
|
|
1300
|
+
tags.each { |t, c| puts "#{t}" }
|
|
1301
|
+
end
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
|
|
1189
1306
|
desc 'Add tag(s) to last entry'
|
|
1190
1307
|
long_desc 'Add (or remove) tags from the last entry, or from multiple entries
|
|
1191
1308
|
(with `--count`), entries matching a search (with `--search`), or entries
|
|
@@ -1239,7 +1356,7 @@ command :tag do |c|
|
|
|
1239
1356
|
c.switch %i[a autotag], negatable: false, default_value: false
|
|
1240
1357
|
|
|
1241
1358
|
c.desc 'Tag the last X entries containing TAG.
|
|
1242
|
-
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
|
1359
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
|
1243
1360
|
c.arg_name 'TAG'
|
|
1244
1361
|
c.flag [:tag]
|
|
1245
1362
|
|
|
@@ -1251,18 +1368,18 @@ command :tag do |c|
|
|
|
1251
1368
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1252
1369
|
|
|
1253
1370
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
1254
|
-
c.switch %i[x exact], default_value:
|
|
1371
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1255
1372
|
|
|
1256
1373
|
c.desc 'Tag items that *don\'t* match search/tag filters'
|
|
1257
1374
|
c.switch [:not], default_value: false, negatable: false
|
|
1258
1375
|
|
|
1259
1376
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1260
1377
|
c.arg_name 'TYPE'
|
|
1261
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
1378
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1262
1379
|
|
|
1263
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
|
1380
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
|
1264
1381
|
c.arg_name 'BOOLEAN'
|
|
1265
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
1382
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
1266
1383
|
|
|
1267
1384
|
c.desc 'Select item(s) to tag from a menu of matching entries'
|
|
1268
1385
|
c.switch %i[i interactive], negatable: false, default_value: false
|
|
@@ -1314,44 +1431,49 @@ command :tag do |c|
|
|
|
1314
1431
|
options[:search] = search
|
|
1315
1432
|
end
|
|
1316
1433
|
|
|
1434
|
+
options[:count] = count
|
|
1435
|
+
options[:section] = section
|
|
1436
|
+
options[:tag] = search_tags
|
|
1437
|
+
options[:tags] = tags
|
|
1438
|
+
options[:tag_bool] = options[:bool].normalize_bool
|
|
1439
|
+
|
|
1317
1440
|
if count.zero? && !options[:force]
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1441
|
+
matches = wwid.filter_items([], opt: options).count
|
|
1442
|
+
|
|
1443
|
+
if matches > 5
|
|
1444
|
+
if options[:search]
|
|
1445
|
+
section_q = ' matching your search terms'
|
|
1446
|
+
elsif options[:tag]
|
|
1447
|
+
section_q = ' matching your tag search'
|
|
1448
|
+
elsif section == 'All'
|
|
1449
|
+
section_q = ''
|
|
1450
|
+
else
|
|
1451
|
+
section_q = " in section #{section}"
|
|
1452
|
+
end
|
|
1327
1453
|
|
|
1328
1454
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1455
|
+
question = if options[:autotag]
|
|
1456
|
+
"Are you sure you want to autotag #{matches} records#{section_q}"
|
|
1457
|
+
elsif options[:remove]
|
|
1458
|
+
"Are you sure you want to remove #{tags.join(' and ')} from #{matches} records#{section_q}"
|
|
1459
|
+
else
|
|
1460
|
+
"Are you sure you want to add #{tags.join(' and ')} to #{matches} records#{section_q}"
|
|
1461
|
+
end
|
|
1336
1462
|
|
|
1337
|
-
|
|
1463
|
+
res = Doing::Prompt.yn(question, default_response: false)
|
|
1338
1464
|
|
|
1339
|
-
|
|
1465
|
+
raise UserCancelled unless res
|
|
1466
|
+
end
|
|
1340
1467
|
end
|
|
1341
1468
|
|
|
1342
|
-
options[:count] = count
|
|
1343
|
-
options[:section] = section
|
|
1344
|
-
options[:tag] = search_tags
|
|
1345
|
-
options[:tags] = tags
|
|
1346
|
-
options[:tag_bool] = options[:bool].normalize_bool
|
|
1347
|
-
|
|
1348
1469
|
wwid.tag_last(options)
|
|
1349
1470
|
end
|
|
1350
1471
|
end
|
|
1351
1472
|
|
|
1352
1473
|
desc 'Mark last entry as flagged'
|
|
1353
|
-
command [
|
|
1474
|
+
command %i[mark flag] do |c|
|
|
1354
1475
|
c.example 'doing flag', desc: 'Add @flagged to the last entry created'
|
|
1476
|
+
c.example 'doing mark', desc: 'mark is an alias for flag'
|
|
1355
1477
|
c.example 'doing flag --tag project1 --count 2', desc: 'Add @flagged to the last 2 entries tagged @project1'
|
|
1356
1478
|
c.example 'doing flag --interactive --search "/(develop|cod)ing/"', desc: 'Find entries matching regular expression and create a menu allowing multiple selections, selected items will be @flagged'
|
|
1357
1479
|
|
|
@@ -1376,7 +1498,7 @@ command [:mark, :flag] do |c|
|
|
|
1376
1498
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
|
1377
1499
|
|
|
1378
1500
|
c.desc 'Flag the last entry containing TAG.
|
|
1379
|
-
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
|
1501
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
|
1380
1502
|
c.arg_name 'TAG'
|
|
1381
1503
|
c.flag [:tag]
|
|
1382
1504
|
|
|
@@ -1388,18 +1510,18 @@ command [:mark, :flag] do |c|
|
|
|
1388
1510
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1389
1511
|
|
|
1390
1512
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
1391
|
-
c.switch %i[x exact], default_value:
|
|
1513
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1392
1514
|
|
|
1393
1515
|
c.desc 'Flag items that *don\'t* match search/tag/date filters'
|
|
1394
1516
|
c.switch [:not], default_value: false, negatable: false
|
|
1395
1517
|
|
|
1396
1518
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1397
1519
|
c.arg_name 'TYPE'
|
|
1398
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
1520
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1399
1521
|
|
|
1400
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
|
1522
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
|
1401
1523
|
c.arg_name 'BOOLEAN'
|
|
1402
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
1524
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
1403
1525
|
|
|
1404
1526
|
c.desc 'Select item(s) to flag from a menu of matching entries'
|
|
1405
1527
|
c.switch %i[i interactive], negatable: false, default_value: false
|
|
@@ -1473,7 +1595,11 @@ end
|
|
|
1473
1595
|
desc 'List all entries'
|
|
1474
1596
|
long_desc %(
|
|
1475
1597
|
The argument can be a section name, @tag(s) or both.
|
|
1476
|
-
"pick" or "choose" as an argument will offer a section menu.
|
|
1598
|
+
"pick" or "choose" as an argument will offer a section menu. Run with `--menu` to get a menu of available tags.
|
|
1599
|
+
|
|
1600
|
+
Show tags by passing @tagname arguments. Multiple tags can be combined, and you can specify the boolean used to
|
|
1601
|
+
combine them with `--bool (AND|OR|NOT)`. You can also use @+tagname to require a tag to match, or @-tagname to ignore
|
|
1602
|
+
entries containing tagname. +/- operators require `--bool PATTERN` (which is the default).
|
|
1477
1603
|
)
|
|
1478
1604
|
arg_name '[SECTION|@TAGS]'
|
|
1479
1605
|
command :show do |c|
|
|
@@ -1485,17 +1611,17 @@ command :show do |c|
|
|
|
1485
1611
|
c.example 'doing show Ideas @doing --from "mon to fri"', desc: 'Show entries tagged @doing from the Ideas section added between monday and friday of the current week.'
|
|
1486
1612
|
c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
|
|
1487
1613
|
|
|
1488
|
-
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
|
1614
|
+
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.'
|
|
1489
1615
|
c.arg_name 'TAG'
|
|
1490
1616
|
c.flag [:tag]
|
|
1491
1617
|
|
|
1492
|
-
c.desc 'Tag boolean (AND,OR,NOT)'
|
|
1618
|
+
c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans.'
|
|
1493
1619
|
c.arg_name 'BOOLEAN'
|
|
1494
|
-
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: '
|
|
1620
|
+
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
1495
1621
|
|
|
1496
1622
|
c.desc 'Max count to show'
|
|
1497
1623
|
c.arg_name 'MAX'
|
|
1498
|
-
c.flag %i[c count], default_value: 0
|
|
1624
|
+
c.flag %i[c count], default_value: 0, must_match: /^\d+$/, type: Integer
|
|
1499
1625
|
|
|
1500
1626
|
c.desc 'Age (oldest|newest)'
|
|
1501
1627
|
c.arg_name 'AGE'
|
|
@@ -1529,14 +1655,14 @@ command :show do |c|
|
|
|
1529
1655
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1530
1656
|
|
|
1531
1657
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
1532
|
-
c.switch %i[x exact], default_value:
|
|
1658
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1533
1659
|
|
|
1534
1660
|
c.desc 'Show items that *don\'t* match search/tag/date filters'
|
|
1535
1661
|
c.switch [:not], default_value: false, negatable: false
|
|
1536
1662
|
|
|
1537
1663
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1538
1664
|
c.arg_name 'TYPE'
|
|
1539
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
1665
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1540
1666
|
|
|
1541
1667
|
c.desc 'Sort order (asc/desc)'
|
|
1542
1668
|
c.arg_name 'ORDER'
|
|
@@ -1545,6 +1671,9 @@ command :show do |c|
|
|
|
1545
1671
|
c.desc 'Show time intervals on @done tasks'
|
|
1546
1672
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1547
1673
|
|
|
1674
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
1675
|
+
c.switch [:duration]
|
|
1676
|
+
|
|
1548
1677
|
c.desc 'Show intervals with totals at the end of output'
|
|
1549
1678
|
c.switch [:totals], default_value: false, negatable: false
|
|
1550
1679
|
|
|
@@ -1561,6 +1690,9 @@ command :show do |c|
|
|
|
1561
1690
|
c.desc 'Only show items with recorded time intervals'
|
|
1562
1691
|
c.switch [:only_timed], default_value: false, negatable: false
|
|
1563
1692
|
|
|
1693
|
+
c.desc 'Select section or tag to display from a menu'
|
|
1694
|
+
c.switch %i[m menu], negatable: false, default_value: false
|
|
1695
|
+
|
|
1564
1696
|
c.desc 'Select from a menu of matching entries to perform additional operations'
|
|
1565
1697
|
c.switch %i[i interactive], negatable: false, default_value: false
|
|
1566
1698
|
|
|
@@ -1573,15 +1705,17 @@ command :show do |c|
|
|
|
1573
1705
|
|
|
1574
1706
|
tag_filter = false
|
|
1575
1707
|
tags = []
|
|
1708
|
+
|
|
1576
1709
|
if args.length.positive?
|
|
1577
1710
|
case args[0]
|
|
1578
1711
|
when /^all$/i
|
|
1579
1712
|
section = 'All'
|
|
1580
1713
|
args.shift
|
|
1581
1714
|
when /^(choose|pick)$/i
|
|
1582
|
-
section = wwid.choose_section
|
|
1715
|
+
section = wwid.choose_section(include_all: true)
|
|
1716
|
+
|
|
1583
1717
|
args.shift
|
|
1584
|
-
when
|
|
1718
|
+
when /^[@+-]/
|
|
1585
1719
|
section = 'All'
|
|
1586
1720
|
else
|
|
1587
1721
|
begin
|
|
@@ -1604,18 +1738,12 @@ command :show do |c|
|
|
|
1604
1738
|
end
|
|
1605
1739
|
end
|
|
1606
1740
|
else
|
|
1607
|
-
section = settings['current_section']
|
|
1741
|
+
section = options[:menu] ? wwid.choose_section(include_all: true) : settings['current_section']
|
|
1742
|
+
section ||= 'All'
|
|
1608
1743
|
end
|
|
1609
1744
|
|
|
1610
1745
|
tags.concat(options[:tag].to_tags) if options[:tag]
|
|
1611
1746
|
|
|
1612
|
-
unless tags.empty?
|
|
1613
|
-
tag_filter = {
|
|
1614
|
-
'tags' => tags,
|
|
1615
|
-
'bool' => options[:bool].normalize_bool
|
|
1616
|
-
}
|
|
1617
|
-
end
|
|
1618
|
-
|
|
1619
1747
|
options[:times] = true if options[:totals]
|
|
1620
1748
|
|
|
1621
1749
|
template = settings['templates']['default'].deep_merge({
|
|
@@ -1633,27 +1761,55 @@ command :show do |c|
|
|
|
1633
1761
|
options[:search] = search
|
|
1634
1762
|
end
|
|
1635
1763
|
|
|
1764
|
+
options[:section] = section
|
|
1765
|
+
|
|
1766
|
+
unless tags.empty?
|
|
1767
|
+
tag_filter = {
|
|
1768
|
+
'tags' => tags,
|
|
1769
|
+
'bool' => options[:bool].normalize_bool
|
|
1770
|
+
}
|
|
1771
|
+
end
|
|
1772
|
+
|
|
1773
|
+
options[:tag_filter] = tag_filter
|
|
1774
|
+
options[:tag] = nil
|
|
1775
|
+
|
|
1776
|
+
items = wwid.filter_items([], opt: options)
|
|
1777
|
+
|
|
1778
|
+
if options[:menu]
|
|
1779
|
+
tag = wwid.choose_tag(section, items: items, include_all: true)
|
|
1780
|
+
raise UserCancelled unless tag
|
|
1781
|
+
|
|
1782
|
+
# options[:bool] = :and unless tags.empty?
|
|
1783
|
+
|
|
1784
|
+
tags = tag.split(/ +/).map { |t| t.strip.sub(/^@?/, '') } if tag =~ /^@/
|
|
1785
|
+
unless tags.empty?
|
|
1786
|
+
tag_filter = {
|
|
1787
|
+
'tags' => tags,
|
|
1788
|
+
'bool' => options[:bool].normalize_bool
|
|
1789
|
+
}
|
|
1790
|
+
options[:tag_filter] = tag_filter
|
|
1791
|
+
end
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1636
1794
|
opt = options.dup
|
|
1637
1795
|
opt[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
1638
1796
|
opt[:count] = options[:count].to_i
|
|
1639
1797
|
opt[:highlight] = true
|
|
1640
1798
|
opt[:order] = options[:sort].normalize_order
|
|
1641
|
-
opt[:section] = section
|
|
1642
1799
|
opt[:tag] = nil
|
|
1643
|
-
opt[:tag_filter] = tag_filter
|
|
1644
1800
|
opt[:tag_order] = options[:tag_order].normalize_order
|
|
1645
1801
|
opt[:tags_color] = template['tags_color']
|
|
1646
1802
|
|
|
1647
|
-
Doing::Pager.page wwid.list_section(opt)
|
|
1803
|
+
Doing::Pager.page wwid.list_section(opt, items: items)
|
|
1648
1804
|
end
|
|
1649
1805
|
end
|
|
1650
1806
|
|
|
1651
1807
|
desc 'Search for entries'
|
|
1652
|
-
long_desc
|
|
1653
|
-
|
|
1808
|
+
long_desc %(
|
|
1809
|
+
Search all sections (or limit to a single section) for entries matching text or regular expression. Normal strings are fuzzy matched.
|
|
1654
1810
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1811
|
+
To search with regular expressions, single quote the string and surround with slashes: `doing search '/\bm.*?x\b/'`
|
|
1812
|
+
)
|
|
1657
1813
|
|
|
1658
1814
|
arg_name 'SEARCH_PATTERN'
|
|
1659
1815
|
command %i[grep search] do |c|
|
|
@@ -1692,6 +1848,9 @@ command %i[grep search] do |c|
|
|
|
1692
1848
|
c.desc 'Show time intervals on @done tasks'
|
|
1693
1849
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1694
1850
|
|
|
1851
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
1852
|
+
c.switch [:duration]
|
|
1853
|
+
|
|
1695
1854
|
c.desc 'Show intervals with totals at the end of output'
|
|
1696
1855
|
c.switch [:totals], default_value: false, negatable: false
|
|
1697
1856
|
|
|
@@ -1708,14 +1867,14 @@ command %i[grep search] do |c|
|
|
|
1708
1867
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
1709
1868
|
|
|
1710
1869
|
c.desc 'Force exact string matching (case sensitive)'
|
|
1711
|
-
c.switch %i[x exact], default_value:
|
|
1870
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
1712
1871
|
|
|
1713
1872
|
c.desc 'Show items that *don\'t* match search string'
|
|
1714
1873
|
c.switch [:not], default_value: false, negatable: false
|
|
1715
1874
|
|
|
1716
1875
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
1717
1876
|
c.arg_name 'TYPE'
|
|
1718
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
1877
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
1719
1878
|
|
|
1720
1879
|
c.desc 'Display an interactive menu of results to perform further operations'
|
|
1721
1880
|
c.switch %i[i interactive], default_value: false, negatable: false
|
|
@@ -1761,6 +1920,9 @@ command :recent do |c|
|
|
|
1761
1920
|
c.desc 'Show time intervals on @done tasks'
|
|
1762
1921
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1763
1922
|
|
|
1923
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
1924
|
+
c.switch [:duration]
|
|
1925
|
+
|
|
1764
1926
|
c.desc 'Show intervals with totals at the end of output'
|
|
1765
1927
|
c.switch [:totals], default_value: false, negatable: false
|
|
1766
1928
|
|
|
@@ -1800,7 +1962,8 @@ command :recent do |c|
|
|
|
1800
1962
|
tags_color: tags_color,
|
|
1801
1963
|
times: options[:times],
|
|
1802
1964
|
totals: options[:totals],
|
|
1803
|
-
interactive: options[:interactive]
|
|
1965
|
+
interactive: options[:interactive],
|
|
1966
|
+
duration: options[:duration]
|
|
1804
1967
|
}
|
|
1805
1968
|
|
|
1806
1969
|
Doing::Pager::page wwid.recent(count, section.cap_first, opts)
|
|
@@ -1810,6 +1973,8 @@ command :recent do |c|
|
|
|
1810
1973
|
end
|
|
1811
1974
|
|
|
1812
1975
|
desc 'List entries from today'
|
|
1976
|
+
long_desc 'List entries from the current day. Use --before, --after, and
|
|
1977
|
+
--from to specify time ranges.'
|
|
1813
1978
|
command :today do |c|
|
|
1814
1979
|
c.example 'doing today', desc: 'List all entries with start dates between 12am and 11:59PM for the current day'
|
|
1815
1980
|
c.example 'doing today --section Later', desc: 'List today\'s entries in the Later section'
|
|
@@ -1823,6 +1988,9 @@ command :today do |c|
|
|
|
1823
1988
|
c.desc 'Show time intervals on @done tasks'
|
|
1824
1989
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1825
1990
|
|
|
1991
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
1992
|
+
c.switch [:duration]
|
|
1993
|
+
|
|
1826
1994
|
c.desc 'Show time totals at the end of output'
|
|
1827
1995
|
c.switch [:totals], default_value: false, negatable: false
|
|
1828
1996
|
|
|
@@ -1855,7 +2023,7 @@ command :today do |c|
|
|
|
1855
2023
|
|
|
1856
2024
|
options[:times] = true if options[:totals]
|
|
1857
2025
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
1858
|
-
filter_options = %i[after before from section sort_tags totals].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
|
|
2026
|
+
filter_options = %i[after before duration from section sort_tags totals].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
|
|
1859
2027
|
|
|
1860
2028
|
Doing::Pager.page wwid.today(options[:times], options[:output], filter_options).chomp
|
|
1861
2029
|
end
|
|
@@ -1878,6 +2046,9 @@ command :on do |c|
|
|
|
1878
2046
|
c.desc 'Show time intervals on @done tasks'
|
|
1879
2047
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1880
2048
|
|
|
2049
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
2050
|
+
c.switch [:duration]
|
|
2051
|
+
|
|
1881
2052
|
c.desc 'Show time totals at the end of output'
|
|
1882
2053
|
c.switch [:totals], default_value: false, negatable: false
|
|
1883
2054
|
|
|
@@ -1917,7 +2088,7 @@ command :on do |c|
|
|
|
1917
2088
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
1918
2089
|
|
|
1919
2090
|
Doing::Pager.page wwid.list_date([start, finish], options[:section], options[:times], options[:output],
|
|
1920
|
-
{ totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
|
2091
|
+
{ duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
|
1921
2092
|
end
|
|
1922
2093
|
end
|
|
1923
2094
|
|
|
@@ -1936,6 +2107,9 @@ command :since do |c|
|
|
|
1936
2107
|
c.desc 'Show time intervals on @done tasks'
|
|
1937
2108
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1938
2109
|
|
|
2110
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
2111
|
+
c.switch [:duration]
|
|
2112
|
+
|
|
1939
2113
|
c.desc 'Show time totals at the end of output'
|
|
1940
2114
|
c.switch [:totals], default_value: false, negatable: false
|
|
1941
2115
|
|
|
@@ -1970,11 +2144,13 @@ command :since do |c|
|
|
|
1970
2144
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
1971
2145
|
|
|
1972
2146
|
Doing::Pager.page wwid.list_date([start, finish], options[:section], options[:times], options[:output],
|
|
1973
|
-
{ totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
|
2147
|
+
{ duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
|
1974
2148
|
end
|
|
1975
2149
|
end
|
|
1976
2150
|
|
|
1977
2151
|
desc 'List entries from yesterday'
|
|
2152
|
+
desc 'Show only entries with start times within the previous 24 hour period. Use --before, --after, and --from to limit to
|
|
2153
|
+
time spans within the day.'
|
|
1978
2154
|
command :yesterday do |c|
|
|
1979
2155
|
c.example 'doing yesterday', desc: 'List all entries from the previous day'
|
|
1980
2156
|
c.example 'doing yesterday --after 8am --before 5pm', desc: 'List entries from the previous day between 8am and 5pm'
|
|
@@ -1991,6 +2167,9 @@ command :yesterday do |c|
|
|
|
1991
2167
|
c.desc 'Show time intervals on @done tasks'
|
|
1992
2168
|
c.switch %i[t times], default_value: true, negatable: true
|
|
1993
2169
|
|
|
2170
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
2171
|
+
c.switch [:duration]
|
|
2172
|
+
|
|
1994
2173
|
c.desc 'Show time totals at the end of output'
|
|
1995
2174
|
c.switch [:totals], default_value: false, negatable: false
|
|
1996
2175
|
|
|
@@ -2031,6 +2210,7 @@ command :yesterday do |c|
|
|
|
2031
2210
|
opt = {
|
|
2032
2211
|
after: options[:after],
|
|
2033
2212
|
before: options[:before],
|
|
2213
|
+
duration: options[:duration],
|
|
2034
2214
|
from: options[:from],
|
|
2035
2215
|
sort_tags: options[:sort_tags],
|
|
2036
2216
|
tag_order: options[:tag_order].normalize_order,
|
|
@@ -2042,6 +2222,8 @@ command :yesterday do |c|
|
|
|
2042
2222
|
end
|
|
2043
2223
|
|
|
2044
2224
|
desc 'Show the last entry, optionally edit'
|
|
2225
|
+
long_desc 'Shows the last entry. Using --search and --tag filters, you can view/edit the last entry matching a filter,
|
|
2226
|
+
allowing `doing last` to target historical entries.'
|
|
2045
2227
|
command :last do |c|
|
|
2046
2228
|
c.example 'doing last', desc: 'Show the most recent entry in all sections'
|
|
2047
2229
|
c.example 'doing last -s Later', desc: 'Show the most recent entry in the Later section'
|
|
@@ -2058,30 +2240,33 @@ command :last do |c|
|
|
|
2058
2240
|
c.desc "Edit entry with #{Doing::Util.default_editor}"
|
|
2059
2241
|
c.switch %i[e editor], negatable: false, default_value: false
|
|
2060
2242
|
|
|
2061
|
-
c.desc 'Tag filter, combine multiple tags with a comma.'
|
|
2243
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
|
2062
2244
|
c.arg_name 'TAG'
|
|
2063
2245
|
c.flag [:tag]
|
|
2064
2246
|
|
|
2065
|
-
c.desc 'Tag boolean'
|
|
2247
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
|
|
2066
2248
|
c.arg_name 'BOOLEAN'
|
|
2067
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
2249
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
2068
2250
|
|
|
2069
2251
|
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
|
2070
2252
|
c.arg_name 'QUERY'
|
|
2071
2253
|
c.flag [:search]
|
|
2072
2254
|
|
|
2255
|
+
c.desc 'Show elapsed time if entry is not tagged @done'
|
|
2256
|
+
c.switch [:duration]
|
|
2257
|
+
|
|
2073
2258
|
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
2074
2259
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
2075
2260
|
|
|
2076
2261
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
2077
|
-
c.switch %i[x exact], default_value:
|
|
2262
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
2078
2263
|
|
|
2079
2264
|
c.desc 'Show items that *don\'t* match search string or tag filter'
|
|
2080
2265
|
c.switch [:not], default_value: false, negatable: false
|
|
2081
2266
|
|
|
2082
2267
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
2083
2268
|
c.arg_name 'TYPE'
|
|
2084
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
2269
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
2085
2270
|
|
|
2086
2271
|
c.action do |global_options, options, _args|
|
|
2087
2272
|
options[:fuzzy] = false
|
|
@@ -2091,15 +2276,7 @@ command :last do |c|
|
|
|
2091
2276
|
tags = []
|
|
2092
2277
|
else
|
|
2093
2278
|
tags = options[:tag].to_tags
|
|
2094
|
-
options[:bool] =
|
|
2095
|
-
when /(any|or)/i
|
|
2096
|
-
:or
|
|
2097
|
-
when /(not|none)/i
|
|
2098
|
-
:not
|
|
2099
|
-
else
|
|
2100
|
-
:and
|
|
2101
|
-
end
|
|
2102
|
-
|
|
2279
|
+
options[:bool] = options[:bool].normalize_bool
|
|
2103
2280
|
end
|
|
2104
2281
|
|
|
2105
2282
|
options[:case] = options[:case].normalize_case
|
|
@@ -2115,7 +2292,15 @@ command :last do |c|
|
|
|
2115
2292
|
wwid.edit_last(section: options[:section], options: { search: search, fuzzy: options[:fuzzy], case: options[:case], tag: tags, tag_bool: options[:bool], not: options[:not] })
|
|
2116
2293
|
else
|
|
2117
2294
|
Doing::Pager::page wwid.last(times: true, section: options[:section],
|
|
2118
|
-
options: {
|
|
2295
|
+
options: {
|
|
2296
|
+
duration: options[:duration],
|
|
2297
|
+
search: search,
|
|
2298
|
+
fuzzy: options[:fuzzy],
|
|
2299
|
+
case: options[:case],
|
|
2300
|
+
negate: options[:not],
|
|
2301
|
+
tag: tags,
|
|
2302
|
+
tag_bool: options[:bool]
|
|
2303
|
+
}).strip
|
|
2119
2304
|
end
|
|
2120
2305
|
end
|
|
2121
2306
|
end
|
|
@@ -2196,6 +2381,8 @@ command :plugins do |c|
|
|
|
2196
2381
|
end
|
|
2197
2382
|
|
|
2198
2383
|
desc 'Generate shell completion scripts'
|
|
2384
|
+
desc 'Generates the necessary scripts to add command line completion to various shells, so typing \'doing\' and hitting
|
|
2385
|
+
tab will offer completions of subcommands and their options.'
|
|
2199
2386
|
command :completion do |c|
|
|
2200
2387
|
c.example 'doing completion', desc: 'Output zsh (default) to STDOUT'
|
|
2201
2388
|
c.example 'doing completion --type zsh --file ~/.zsh-completions/_doing.zsh', desc: 'Output zsh completions to file'
|
|
@@ -2218,7 +2405,8 @@ command :completion do |c|
|
|
|
2218
2405
|
end
|
|
2219
2406
|
|
|
2220
2407
|
desc 'Display a user-created view'
|
|
2221
|
-
long_desc '
|
|
2408
|
+
long_desc 'Views are defined in your configuration (use `doing config` to edit).
|
|
2409
|
+
Command line options override view configuration.'
|
|
2222
2410
|
arg_name 'VIEW_NAME'
|
|
2223
2411
|
command :view do |c|
|
|
2224
2412
|
c.example 'doing view color', desc: 'Display entries according to config for view "color"'
|
|
@@ -2239,19 +2427,22 @@ command :view do |c|
|
|
|
2239
2427
|
c.desc 'Show time intervals on @done tasks'
|
|
2240
2428
|
c.switch %i[t times], default_value: true, negatable: true
|
|
2241
2429
|
|
|
2430
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
2431
|
+
c.switch [:duration]
|
|
2432
|
+
|
|
2242
2433
|
c.desc 'Show intervals with totals at the end of output'
|
|
2243
2434
|
c.switch [:totals], default_value: false, negatable: false
|
|
2244
2435
|
|
|
2245
2436
|
c.desc 'Include colors in output'
|
|
2246
2437
|
c.switch [:color], default_value: true, negatable: true
|
|
2247
2438
|
|
|
2248
|
-
c.desc 'Tag filter, combine multiple tags with a comma.'
|
|
2439
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
|
2249
2440
|
c.arg_name 'TAG'
|
|
2250
2441
|
c.flag [:tag]
|
|
2251
2442
|
|
|
2252
|
-
c.desc 'Tag boolean (AND,OR,NOT)'
|
|
2443
|
+
c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans.'
|
|
2253
2444
|
c.arg_name 'BOOLEAN'
|
|
2254
|
-
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: '
|
|
2445
|
+
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
2255
2446
|
|
|
2256
2447
|
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
|
2257
2448
|
c.arg_name 'QUERY'
|
|
@@ -2261,14 +2452,14 @@ command :view do |c|
|
|
|
2261
2452
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
2262
2453
|
|
|
2263
2454
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
2264
|
-
c.switch %i[x exact], default_value:
|
|
2455
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
2265
2456
|
|
|
2266
2457
|
c.desc 'Show items that *don\'t* match search string'
|
|
2267
2458
|
c.switch [:not], default_value: false, negatable: false
|
|
2268
2459
|
|
|
2269
2460
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
2270
2461
|
c.arg_name 'TYPE'
|
|
2271
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
2462
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
2272
2463
|
|
|
2273
2464
|
c.desc 'Sort tags by (name|time)'
|
|
2274
2465
|
c.arg_name 'KEY'
|
|
@@ -2330,6 +2521,7 @@ command :view do |c|
|
|
|
2330
2521
|
end
|
|
2331
2522
|
|
|
2332
2523
|
view = wwid.get_view(title)
|
|
2524
|
+
|
|
2333
2525
|
if view
|
|
2334
2526
|
page_title = view.key?('title') ? view['title'] : title.cap_first
|
|
2335
2527
|
only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
|
|
@@ -2345,16 +2537,22 @@ command :view do |c|
|
|
|
2345
2537
|
tag_filter = false
|
|
2346
2538
|
if options[:tag]
|
|
2347
2539
|
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
|
2348
|
-
|
|
2349
|
-
tag_filter['bool'] =
|
|
2540
|
+
bool = options[:bool].normalize_bool
|
|
2541
|
+
tag_filter['bool'] = bool
|
|
2542
|
+
tag_filter['tags'] = if bool == :pattern
|
|
2543
|
+
options[:tag]
|
|
2544
|
+
else
|
|
2545
|
+
options[:tag].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
|
2546
|
+
end
|
|
2350
2547
|
elsif view.key?('tags') && !(view['tags'].nil? || view['tags'].empty?)
|
|
2351
2548
|
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
|
2549
|
+
bool = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :pattern
|
|
2550
|
+
tag_filter['bool'] = bool
|
|
2352
2551
|
tag_filter['tags'] = if view['tags'].instance_of?(Array)
|
|
2353
|
-
view['tags'].map(&:strip)
|
|
2552
|
+
bool == :pattern ? view['tags'].join(' ').strip : view['tags'].map(&:strip)
|
|
2354
2553
|
else
|
|
2355
|
-
view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
|
2554
|
+
bool == :pattern ? view['tags'].strip : view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
|
2356
2555
|
end
|
|
2357
|
-
tag_filter['bool'] = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :or
|
|
2358
2556
|
end
|
|
2359
2557
|
|
|
2360
2558
|
# If the -o/--output flag was specified, override any default in the view template
|
|
@@ -2391,7 +2589,7 @@ command :view do |c|
|
|
|
2391
2589
|
false
|
|
2392
2590
|
end
|
|
2393
2591
|
|
|
2394
|
-
%w[before after from].each { |k| options[k.to_sym] = view[k] if view.key?(k) && !options[k.to_sym] }
|
|
2592
|
+
%w[before after from duration].each { |k| options[k.to_sym] = view[k] if view.key?(k) && !options[k.to_sym] }
|
|
2395
2593
|
|
|
2396
2594
|
options[:case] = options[:case].normalize_case
|
|
2397
2595
|
|
|
@@ -2403,6 +2601,7 @@ command :view do |c|
|
|
|
2403
2601
|
end
|
|
2404
2602
|
|
|
2405
2603
|
opts = options.dup
|
|
2604
|
+
opts[:view_template] = title
|
|
2406
2605
|
opts[:count] = count
|
|
2407
2606
|
opts[:format] = date_format
|
|
2408
2607
|
opts[:highlight] = options[:color]
|
|
@@ -2463,13 +2662,13 @@ command %i[archive move] do |c|
|
|
|
2463
2662
|
c.desc 'Label moved items with @from(SECTION_NAME)'
|
|
2464
2663
|
c.switch [:label], default_value: true, negatable: true
|
|
2465
2664
|
|
|
2466
|
-
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
|
2665
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands.'
|
|
2467
2666
|
c.arg_name 'TAG'
|
|
2468
2667
|
c.flag [:tag]
|
|
2469
2668
|
|
|
2470
|
-
c.desc 'Tag boolean (AND|OR|NOT)'
|
|
2669
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
|
|
2471
2670
|
c.arg_name 'BOOLEAN'
|
|
2472
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
2671
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
2473
2672
|
|
|
2474
2673
|
c.desc 'Search filter'
|
|
2475
2674
|
c.arg_name 'QUERY'
|
|
@@ -2479,14 +2678,14 @@ command %i[archive move] do |c|
|
|
|
2479
2678
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
2480
2679
|
|
|
2481
2680
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
2482
|
-
c.switch %i[x exact], default_value:
|
|
2681
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
2483
2682
|
|
|
2484
2683
|
c.desc 'Show items that *don\'t* match search string'
|
|
2485
2684
|
c.switch [:not], default_value: false, negatable: false
|
|
2486
2685
|
|
|
2487
2686
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
2488
2687
|
c.arg_name 'TYPE'
|
|
2489
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
2688
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
2490
2689
|
|
|
2491
2690
|
c.desc 'Archive entries older than date
|
|
2492
2691
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
|
@@ -2532,6 +2731,9 @@ command %i[archive move] do |c|
|
|
|
2532
2731
|
end
|
|
2533
2732
|
|
|
2534
2733
|
desc 'Move entries to archive file'
|
|
2734
|
+
long_desc 'As your doing file grows, commands can get slow. Given that your historical data (and your archive section)
|
|
2735
|
+
probably aren\'t providing any useful insights a year later, use this command to "rotate" old entries out to an archive
|
|
2736
|
+
file. You\'ll still have access to all historical data, but it won\'t be slowing down daily operation.'
|
|
2535
2737
|
command :rotate do |c|
|
|
2536
2738
|
c.example 'doing rotate', desc: 'Move all entries in doing file to a dated secondary file'
|
|
2537
2739
|
c.example 'doing rotate --section Archive --keep 10', desc: 'Move entries in the Archive section to a secondary file, keeping the most recent 10 entries'
|
|
@@ -2545,13 +2747,13 @@ command :rotate do |c|
|
|
|
2545
2747
|
c.arg_name 'SECTION_NAME'
|
|
2546
2748
|
c.flag %i[s section], default_value: 'All'
|
|
2547
2749
|
|
|
2548
|
-
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
|
2750
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands.'
|
|
2549
2751
|
c.arg_name 'TAG'
|
|
2550
2752
|
c.flag [:tag]
|
|
2551
2753
|
|
|
2552
|
-
c.desc 'Tag boolean (AND|OR|NOT)'
|
|
2754
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
|
|
2553
2755
|
c.arg_name 'BOOLEAN'
|
|
2554
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
|
2756
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
2555
2757
|
|
|
2556
2758
|
c.desc 'Search filter'
|
|
2557
2759
|
c.arg_name 'QUERY'
|
|
@@ -2561,14 +2763,14 @@ command :rotate do |c|
|
|
|
2561
2763
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
2562
2764
|
|
|
2563
2765
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
2564
|
-
c.switch %i[x exact], default_value:
|
|
2766
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
2565
2767
|
|
|
2566
2768
|
c.desc 'Rotate items that *don\'t* match search string or tag filter'
|
|
2567
2769
|
c.switch [:not], default_value: false, negatable: false
|
|
2568
2770
|
|
|
2569
2771
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
2570
2772
|
c.arg_name 'TYPE'
|
|
2571
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
2773
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
2572
2774
|
|
|
2573
2775
|
c.desc 'Rotate entries older than date
|
|
2574
2776
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
|
@@ -2598,8 +2800,10 @@ command :rotate do |c|
|
|
|
2598
2800
|
end
|
|
2599
2801
|
|
|
2600
2802
|
desc 'Open the "doing" file in an editor'
|
|
2601
|
-
long_desc "`doing open` defaults to using the
|
|
2803
|
+
long_desc "`doing open` defaults to using the editors->doing_file setting
|
|
2804
|
+
in #{config.config_file} (#{Doing::Util.find_default_editor('doing_file')})."
|
|
2602
2805
|
command :open do |c|
|
|
2806
|
+
c.example 'doing open', desc: 'Open the doing file in the default editor'
|
|
2603
2807
|
c.desc 'Open with editor command (e.g. vim, mate)'
|
|
2604
2808
|
c.arg_name 'COMMAND'
|
|
2605
2809
|
c.flag %i[e editor]
|
|
@@ -2773,7 +2977,7 @@ command :config do |c|
|
|
|
2773
2977
|
c.command :undo do |undo|
|
|
2774
2978
|
undo.action do |_global, options, args|
|
|
2775
2979
|
config_file = config.choose_config
|
|
2776
|
-
|
|
2980
|
+
Doing::Util::Backup.restore_last_backup(config_file, count: 1)
|
|
2777
2981
|
end
|
|
2778
2982
|
end
|
|
2779
2983
|
|
|
@@ -2845,9 +3049,21 @@ command :config do |c|
|
|
|
2845
3049
|
|
|
2846
3050
|
value = options[:remove] ? nil : args.pop
|
|
2847
3051
|
keypath = args.join('.')
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3052
|
+
real_path = config.resolve_key_path(keypath, create: true)
|
|
3053
|
+
|
|
3054
|
+
old_value = settings.dig(*real_path) || nil
|
|
3055
|
+
old_type = old_value&.class.to_s || nil
|
|
3056
|
+
|
|
3057
|
+
if old_value.is_a?(Hash) && !options[:remove]
|
|
3058
|
+
Doing.logger.log_now(:warn, 'Config:', "Config key must point to a single value, #{real_path.join('->').boldwhite} is a mapping")
|
|
3059
|
+
didyou = 'Did you mean:'
|
|
3060
|
+
old_value.keys.each do |k|
|
|
3061
|
+
Doing.logger.log_now(:warn, "#{didyou}", "#{keypath}.#{k}?")
|
|
3062
|
+
didyou = '..........or:'
|
|
3063
|
+
end
|
|
3064
|
+
raise InvalidArgument, 'Config value is a mapping, can not be set to a single value'
|
|
3065
|
+
|
|
3066
|
+
end
|
|
2851
3067
|
|
|
2852
3068
|
config_file = config.choose_config
|
|
2853
3069
|
cfg = YAML.safe_load_file(config_file) || {}
|
|
@@ -2858,11 +3074,11 @@ command :config do |c|
|
|
|
2858
3074
|
cfg.deep_set(real_path, nil)
|
|
2859
3075
|
$stderr.puts "#{'Deleting key:'.yellow} #{real_path.join('->').boldwhite}"
|
|
2860
3076
|
else
|
|
2861
|
-
|
|
2862
|
-
|
|
3077
|
+
cfg.deep_set(real_path, value.set_type(old_type))
|
|
3078
|
+
|
|
2863
3079
|
$stderr.puts "#{'Key path:'.yellow} #{real_path.join('->').boldwhite}"
|
|
2864
|
-
$stderr.puts "#{'Previous:'.yellow} #{old_value.to_s.boldwhite}"
|
|
2865
|
-
$stderr.puts "#{' New:'.yellow} #{value.set_type.to_s.boldwhite}"
|
|
3080
|
+
$stderr.puts "#{'Previous:'.yellow} #{(old_value ? old_value.to_s : 'empty').boldwhite}"
|
|
3081
|
+
$stderr.puts "#{' New:'.yellow} #{value.set_type(old_type).to_s.boldwhite}"
|
|
2866
3082
|
end
|
|
2867
3083
|
|
|
2868
3084
|
res = Doing::Prompt.yn('Update selected config', default_response: true)
|
|
@@ -2875,15 +3091,89 @@ command :config do |c|
|
|
|
2875
3091
|
end
|
|
2876
3092
|
end
|
|
2877
3093
|
|
|
2878
|
-
desc 'Undo the last
|
|
3094
|
+
desc 'Undo the last X changes to the Doing file'
|
|
3095
|
+
long_desc 'Reverts the last X commands that altered the doing file.
|
|
3096
|
+
All changes performed by a single command are undone at once.
|
|
3097
|
+
|
|
3098
|
+
Specify a number to jump back multiple revisions, or use --select for an interactive menu.'
|
|
3099
|
+
arg_name 'COUNT'
|
|
2879
3100
|
command :undo do |c|
|
|
3101
|
+
c.example 'doing undo', desc: 'Undo the most recent change to the doing file'
|
|
3102
|
+
c.example 'doing undo 5', desc: 'Undo the last 5 changes to the doing file'
|
|
3103
|
+
c.example 'doing undo --interactive', desc: 'Select from a menu of available revisions'
|
|
3104
|
+
c.example 'doing undo --redo', desc: 'Undo the last undo command'
|
|
3105
|
+
|
|
2880
3106
|
c.desc 'Specify alternate doing file'
|
|
2881
3107
|
c.arg_name 'PATH'
|
|
2882
3108
|
c.flag %i[f file], default_value: wwid.doing_file
|
|
2883
3109
|
|
|
2884
|
-
c.
|
|
3110
|
+
c.desc 'Select from recent backups'
|
|
3111
|
+
c.switch %i[i interactive], negatable: false
|
|
3112
|
+
|
|
3113
|
+
c.desc 'Remove old backups, retaining X files'
|
|
3114
|
+
c.arg_name 'COUNT'
|
|
3115
|
+
c.flag %i[p prune], type: Integer
|
|
3116
|
+
|
|
3117
|
+
c.desc 'Redo last undo. Note: you cannot undo a redo.'
|
|
3118
|
+
c.switch %i[r redo]
|
|
3119
|
+
|
|
3120
|
+
c.action do |_global_options, options, args|
|
|
3121
|
+
file = options[:file] || wwid.doing_file
|
|
3122
|
+
count = args.empty? ? 1 : args[0].to_i
|
|
3123
|
+
raise InvalidArgument, "Invalid count specified for undo" unless count&.positive?
|
|
3124
|
+
|
|
3125
|
+
if options[:prune]
|
|
3126
|
+
Doing::Util::Backup.prune_backups(file, options[:prune])
|
|
3127
|
+
elsif options[:redo]
|
|
3128
|
+
if options[:interactive]
|
|
3129
|
+
Doing::Util::Backup.select_redo(file)
|
|
3130
|
+
else
|
|
3131
|
+
Doing::Util::Backup.redo_backup(file, count: count)
|
|
3132
|
+
end
|
|
3133
|
+
else
|
|
3134
|
+
if options[:interactive]
|
|
3135
|
+
Doing::Util::Backup.select_backup(file)
|
|
3136
|
+
else
|
|
3137
|
+
Doing::Util::Backup.restore_last_backup(file, count: count)
|
|
3138
|
+
end
|
|
3139
|
+
end
|
|
3140
|
+
end
|
|
3141
|
+
end
|
|
3142
|
+
|
|
3143
|
+
long_desc 'Shortcut for `doing undo -r`, reverses the last undo command. You cannot undo a redo.'
|
|
3144
|
+
arg_name 'COUNT'
|
|
3145
|
+
command :redo do |c|
|
|
3146
|
+
c.desc 'Specify alternate doing file'
|
|
3147
|
+
c.arg_name 'PATH'
|
|
3148
|
+
c.flag %i[f file], default_value: wwid.doing_file
|
|
3149
|
+
|
|
3150
|
+
c.desc 'Select from an interactive menu'
|
|
3151
|
+
c.switch %i[i interactive]
|
|
3152
|
+
|
|
3153
|
+
c.action do |_global, options, args|
|
|
2885
3154
|
file = options[:file] || wwid.doing_file
|
|
2886
|
-
|
|
3155
|
+
count = args.empty? ? 1 : args[0].to_i
|
|
3156
|
+
raise InvalidArgument, "Invalid count specified for redo" unless count&.positive?
|
|
3157
|
+
if options[:interactive]
|
|
3158
|
+
Doing::Util::Backup.select_redo(file)
|
|
3159
|
+
else
|
|
3160
|
+
Doing::Util::Backup.redo_backup(file, count: count)
|
|
3161
|
+
end
|
|
3162
|
+
end
|
|
3163
|
+
end
|
|
3164
|
+
|
|
3165
|
+
desc 'List recent changes in Doing'
|
|
3166
|
+
long_desc 'Display a formatted list of changes in recent versions, latest at the top'
|
|
3167
|
+
command %i[changelog changes] do |c|
|
|
3168
|
+
c.action do |_global_options, options, args|
|
|
3169
|
+
changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md'))
|
|
3170
|
+
if File.exist?(changelog)
|
|
3171
|
+
parsed = TTY::Markdown.parse(IO.read(changelog), width: 80, symbols: {override: {bullet: "•"}})
|
|
3172
|
+
Doing::Pager.paginate = true
|
|
3173
|
+
Doing::Pager.page parsed
|
|
3174
|
+
else
|
|
3175
|
+
raise "Error locating changelog"
|
|
3176
|
+
end
|
|
2887
3177
|
end
|
|
2888
3178
|
end
|
|
2889
3179
|
|
|
@@ -2891,6 +3181,10 @@ desc 'Import entries from an external source'
|
|
|
2891
3181
|
long_desc "Imports entries from other sources. Available plugins: #{Doing::Plugins.plugin_names(type: :import, separator: ', ')}"
|
|
2892
3182
|
arg_name 'PATH'
|
|
2893
3183
|
command :import do |c|
|
|
3184
|
+
c.example 'doing import --type timing "~/Desktop/All Activities.json"', desc: 'Import a Timing.app JSON report'
|
|
3185
|
+
c.example 'doing import --type doing --tag imported --no-autotag ~/doing_backup.md', desc: 'Import an Doing archive, tag all entries with @imported, skip autotagging'
|
|
3186
|
+
c.example 'doing import --type doing --from "10/1 to 10/15" ~/doing_backup.md', desc: 'Import a Doing archive, only importing entries between two dates'
|
|
3187
|
+
|
|
2894
3188
|
c.desc "Import type (#{Doing::Plugins.plugin_names(type: :import)})"
|
|
2895
3189
|
c.arg_name 'TYPE'
|
|
2896
3190
|
c.flag :type, default_value: 'doing'
|
|
@@ -2903,14 +3197,14 @@ command :import do |c|
|
|
|
2903
3197
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
2904
3198
|
|
|
2905
3199
|
c.desc 'Force exact search string matching (case sensitive)'
|
|
2906
|
-
c.switch %i[x exact], default_value:
|
|
3200
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
|
2907
3201
|
|
|
2908
3202
|
c.desc 'Import items that *don\'t* match search/tag/date filters'
|
|
2909
3203
|
c.switch [:not], default_value: false, negatable: false
|
|
2910
3204
|
|
|
2911
3205
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
2912
3206
|
c.arg_name 'TYPE'
|
|
2913
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
|
3207
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
|
2914
3208
|
|
|
2915
3209
|
c.desc 'Only import items with recorded time intervals'
|
|
2916
3210
|
c.switch [:only_timed], default_value: false, negatable: false
|
|
@@ -2985,7 +3279,6 @@ end
|
|
|
2985
3279
|
|
|
2986
3280
|
pre do |global, _command, _options, _args|
|
|
2987
3281
|
# global[:pager] ||= settings['paginate']
|
|
2988
|
-
|
|
2989
3282
|
Doing::Pager.paginate = global[:pager]
|
|
2990
3283
|
|
|
2991
3284
|
$stdout.puts "doing v#{Doing::VERSION}" if global[:version]
|
|
@@ -3016,6 +3309,8 @@ post do |global, _command, _options, _args|
|
|
|
3016
3309
|
# Use skips_post before a command to skip this
|
|
3017
3310
|
# block on that command only
|
|
3018
3311
|
Doing.logger.output_results
|
|
3312
|
+
Doing.logger.benchmark(:total, :finish)
|
|
3313
|
+
Doing.logger.log_benchmarks
|
|
3019
3314
|
end
|
|
3020
3315
|
|
|
3021
3316
|
around do |global, command, options, arguments, code|
|
|
@@ -3030,10 +3325,13 @@ around do |global, command, options, arguments, code|
|
|
|
3030
3325
|
|
|
3031
3326
|
if global[:yes]
|
|
3032
3327
|
Doing::Prompt.force_answer = true
|
|
3328
|
+
Doing.config.force_answer = true
|
|
3033
3329
|
elsif global[:no]
|
|
3034
3330
|
Doing::Prompt.force_answer = false
|
|
3331
|
+
Doing.config.force_answer = false
|
|
3035
3332
|
else
|
|
3036
3333
|
Doing::Prompt.default_answer = global[:default]
|
|
3334
|
+
Doing.config.force_answer = global[:default] ? true : false
|
|
3037
3335
|
end
|
|
3038
3336
|
|
|
3039
3337
|
if global[:config_file] && global[:config_file] != config.config_file
|
|
@@ -3046,13 +3344,13 @@ around do |global, command, options, arguments, code|
|
|
|
3046
3344
|
config.config_file = cf
|
|
3047
3345
|
settings = config.configure({ ignore_local: true })
|
|
3048
3346
|
end
|
|
3049
|
-
|
|
3347
|
+
Doing.logger.benchmark(:init, :start)
|
|
3050
3348
|
if global[:doing_file]
|
|
3051
3349
|
wwid.init_doing_file(global[:doing_file])
|
|
3052
3350
|
else
|
|
3053
3351
|
wwid.init_doing_file
|
|
3054
3352
|
end
|
|
3055
|
-
|
|
3353
|
+
Doing.logger.benchmark(:init, :finish)
|
|
3056
3354
|
wwid.auto_tag = !global[:noauto]
|
|
3057
3355
|
|
|
3058
3356
|
settings[:include_notes] = false unless global[:notes]
|