doing 2.1.2pre → 2.1.6pre
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 -15
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +62 -14
- data/Gemfile.lock +25 -1
- data/README.md +5 -1
- data/Rakefile +2 -0
- data/bin/doing +429 -142
- data/docs/_config.yml +1 -0
- data/{doc → docs/doc}/Array.html +63 -1
- data/docs/doc/BooleanTermParser/Clause.html +293 -0
- data/docs/doc/BooleanTermParser/Operator.html +172 -0
- data/docs/doc/BooleanTermParser/Query.html +417 -0
- data/docs/doc/BooleanTermParser/QueryParser.html +135 -0
- data/docs/doc/BooleanTermParser/QueryTransformer.html +124 -0
- data/docs/doc/BooleanTermParser.html +115 -0
- data/docs/doc/Doing/CLIFormat.html +131 -0
- data/{doc → docs/doc}/Doing/Color.html +2 -2
- data/{doc → docs/doc}/Doing/Completion.html +1 -1
- data/{doc → docs/doc}/Doing/Configuration.html +163 -69
- data/{doc → docs/doc}/Doing/Content.html +0 -0
- data/{doc → docs/doc}/Doing/Errors/DoingNoTraceError.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/DoingRuntimeError.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/DoingStandardError.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/EmptyInput.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/NoResults.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/PluginException.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/UserCancelled.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/WrongCommand.html +1 -1
- data/{doc → docs/doc}/Doing/Errors.html +1 -1
- data/{doc → docs/doc}/Doing/Hooks.html +1 -1
- data/{doc → docs/doc}/Doing/Item.html +135 -89
- data/{doc → docs/doc}/Doing/Items.html +36 -2
- data/{doc → docs/doc}/Doing/LogAdapter.html +70 -1
- data/{doc → docs/doc}/Doing/Note.html +5 -134
- data/{doc → docs/doc}/Doing/Pager.html +1 -1
- data/{doc → docs/doc}/Doing/Plugins.html +431 -35
- data/{doc → docs/doc}/Doing/Prompt.html +70 -18
- data/{doc → docs/doc}/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +713 -0
- data/docs/doc/Doing/Util/Backup.html +686 -0
- data/{doc → docs/doc}/Doing/Util.html +16 -4
- data/{doc → docs/doc}/Doing/WWID.html +133 -73
- data/{doc → docs/doc}/Doing/WWIDFile.html +0 -0
- data/{doc → docs/doc}/Doing.html +4 -4
- data/{doc → docs/doc}/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/{doc → docs/doc}/GLI/Commands.html +1 -1
- data/{doc → docs/doc}/GLI.html +1 -1
- data/{doc → docs/doc}/Hash.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +172 -0
- data/docs/doc/PhraseParser/PhraseClause.html +303 -0
- data/docs/doc/PhraseParser/Query.html +495 -0
- data/docs/doc/PhraseParser/QueryParser.html +136 -0
- data/docs/doc/PhraseParser/QueryTransformer.html +124 -0
- data/docs/doc/PhraseParser/TermClause.html +293 -0
- data/docs/doc/PhraseParser.html +115 -0
- data/{doc → docs/doc}/Status.html +1 -1
- data/{doc → docs/doc}/String.html +319 -13
- data/{doc → docs/doc}/Symbol.html +35 -1
- data/{doc → docs/doc}/Time.html +70 -2
- data/{doc → docs/doc}/_index.html +132 -4
- data/docs/doc/class_list.html +51 -0
- data/{doc → docs/doc}/css/common.css +0 -0
- data/{doc → docs/doc}/css/full_list.css +0 -0
- data/{doc → docs/doc}/css/style.css +0 -0
- data/{doc → docs/doc}/file.README.html +6 -2
- data/{doc → docs/doc}/file_list.html +0 -0
- data/{doc → docs/doc}/frames.html +0 -0
- data/{doc → docs/doc}/index.html +6 -2
- data/{doc → docs/doc}/js/app.js +0 -0
- data/{doc → docs/doc}/js/full_list.js +0 -0
- data/{doc → docs/doc}/js/jquery.js +0 -0
- data/{doc → docs/doc}/method_list.html +684 -196
- data/{doc → docs/doc}/top-level-namespace.html +2 -2
- data/docs/index.md +60 -0
- data/doing.gemspec +3 -0
- data/doing.rdoc +222 -74
- data/example_plugin.rb +3 -1
- data/lib/completion/_doing.zsh +53 -41
- data/lib/completion/doing.bash +17 -6
- data/lib/completion/doing.fish +321 -2
- data/lib/doing/array.rb +9 -0
- data/lib/doing/boolean_term_parser.rb +86 -0
- data/lib/doing/completion/fish_completion.rb +46 -3
- data/lib/doing/completion/zsh_completion.rb +1 -1
- data/lib/doing/configuration.rb +48 -21
- data/lib/doing/item.rb +105 -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 +84 -21
- data/lib/doing/plugins/export/dayone_export.rb +209 -0
- data/lib/doing/plugins/export/html_export.rb +2 -2
- data/lib/doing/plugins/export/json_export.rb +1 -0
- data/lib/doing/plugins/export/markdown_export.rb +1 -1
- data/lib/doing/plugins/export/template_export.rb +94 -86
- data/lib/doing/prompt.rb +26 -15
- 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 +105 -41
- data/lib/doing.rb +9 -0
- data/lib/examples/plugins/say_export.rb +1 -1
- data/lib/examples/plugins/wiki_export/wiki_export.rb +3 -3
- data/lib/templates/doing-dayone-entry.erb +6 -0
- data/lib/templates/doing-dayone.erb +5 -0
- metadata +136 -51
- data/doc/class_list.html +0 -51
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'
|
@@ -483,6 +508,13 @@ command :template do |c|
|
|
483
508
|
c.desc 'List in single column for completion'
|
484
509
|
c.switch %i[c column]
|
485
510
|
|
511
|
+
c.desc 'Save template to file instead of STDOUT'
|
512
|
+
c.switch %i[s save], default_value: false, negatable: false
|
513
|
+
|
514
|
+
c.desc 'Save template to alternate location'
|
515
|
+
c.arg_name 'DIRECTORY'
|
516
|
+
c.flag %i[p path], default_value: File.join(Doing::Util.user_home, '.config', 'doing', 'templates')
|
517
|
+
|
486
518
|
c.action do |_global_options, options, args|
|
487
519
|
if options[:list] || options[:column]
|
488
520
|
if options[:column]
|
@@ -495,13 +527,19 @@ command :template do |c|
|
|
495
527
|
|
496
528
|
if args.empty?
|
497
529
|
type = Doing::Prompt.choose_from(Doing::Plugins.plugin_templates, sorted: false, prompt: 'Select template type > ')
|
530
|
+
type.sub!(/ \(.*?\)$/, '').strip!
|
531
|
+
options[:save] = Doing::Prompt.yn("Save to #{options[:path]}? (No outputs to STDOUT)", default_response: false)
|
498
532
|
else
|
499
533
|
type = args[0]
|
500
534
|
end
|
501
535
|
|
502
536
|
raise InvalidPluginType, "No type specified, use `doing template [#{Doing::Plugins.plugin_templates.join('|')}]`" unless type
|
503
537
|
|
504
|
-
|
538
|
+
if options[:save]
|
539
|
+
Doing::Plugins.template_for_trigger(type, save_to: options[:path])
|
540
|
+
else
|
541
|
+
$stdout.puts Doing::Plugins.template_for_trigger(type, save_to: nil)
|
542
|
+
end
|
505
543
|
|
506
544
|
# case args[0]
|
507
545
|
# when /html|haml/i
|
@@ -521,8 +559,19 @@ long_desc 'List all entries and select with typeahead fuzzy matching.
|
|
521
559
|
|
522
560
|
Multiple selections are allowed, hit tab to add the highlighted entry to the
|
523
561
|
selection, and use ctrl-a to select all visible items. Return processes the
|
524
|
-
selected entries.
|
562
|
+
selected entries.
|
563
|
+
|
564
|
+
Search in the menu by typing:
|
565
|
+
|
566
|
+
sbtrkt fuzzy-match Items that match sbtrkt
|
567
|
+
|
568
|
+
\'wild exact-match (quoted) Items that include wild
|
569
|
+
|
570
|
+
!fire inverse-exact-match Items that do not include fire'
|
525
571
|
command :select do |c|
|
572
|
+
c.example 'doing select', desc: 'Select from all entries. A menu of available actions will be presented after confirming the selection.'
|
573
|
+
c.example 'doing select --editor', desc: 'Select entries from a menu and batch edit them in your default editor'
|
574
|
+
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
575
|
c.desc 'Select from a specific section'
|
527
576
|
c.arg_name 'SECTION'
|
528
577
|
c.flag %i[s section]
|
@@ -568,14 +617,14 @@ command :select do |c|
|
|
568
617
|
c.flag [:from]
|
569
618
|
|
570
619
|
c.desc 'Force exact search string matching (case sensitive)'
|
571
|
-
c.switch %i[x exact], default_value:
|
620
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
572
621
|
|
573
622
|
c.desc 'Select items that *don\'t* match search/tag filters'
|
574
623
|
c.switch [:not], default_value: false, negatable: false
|
575
624
|
|
576
625
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
577
626
|
c.arg_name 'TYPE'
|
578
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
627
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
579
628
|
|
580
629
|
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
630
|
c.switch %i[menu], negatable: true, default_value: true
|
@@ -680,6 +729,9 @@ command :later do |c|
|
|
680
729
|
end
|
681
730
|
|
682
731
|
desc 'Add a completed item with @done(date). No argument finishes last entry.'
|
732
|
+
long_desc 'Use this command to add an entry after you\'ve already finished it. It will be immediately marked as @done.
|
733
|
+
You can modify the start and end times of the entry using the --back, --took, and --at flags, making it an easy
|
734
|
+
way to add entries in post and maintain accurate (albeit manual) time tracking.'
|
683
735
|
arg_name 'ENTRY'
|
684
736
|
command %i[done did] do |c|
|
685
737
|
c.example 'doing done', desc: 'Tag the last entry @done'
|
@@ -890,13 +942,13 @@ command :cancel do |c|
|
|
890
942
|
c.arg_name 'NAME'
|
891
943
|
c.flag %i[s section]
|
892
944
|
|
893
|
-
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2)'
|
945
|
+
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2). Wildcards allowed (*, ?).'
|
894
946
|
c.arg_name 'TAG'
|
895
947
|
c.flag [:tag]
|
896
948
|
|
897
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
949
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
898
950
|
c.arg_name 'BOOLEAN'
|
899
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
951
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
900
952
|
|
901
953
|
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
954
|
c.arg_name 'QUERY'
|
@@ -906,14 +958,14 @@ command :cancel do |c|
|
|
906
958
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
907
959
|
|
908
960
|
c.desc 'Force exact search string matching (case sensitive)'
|
909
|
-
c.switch %i[x exact], default_value:
|
961
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
910
962
|
|
911
963
|
c.desc 'Finish items that *don\'t* match search/tag filters'
|
912
964
|
c.switch [:not], default_value: false, negatable: false
|
913
965
|
|
914
966
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
915
967
|
c.arg_name 'TYPE'
|
916
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
968
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
917
969
|
|
918
970
|
c.desc 'Cancel last entry (or entries) not already marked @done'
|
919
971
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
@@ -997,7 +1049,7 @@ command :finish do |c|
|
|
997
1049
|
c.flag [:at]
|
998
1050
|
|
999
1051
|
c.desc 'Finish the last X entries containing TAG.
|
1000
|
-
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
1052
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
1001
1053
|
c.arg_name 'TAG'
|
1002
1054
|
c.flag [:tag]
|
1003
1055
|
|
@@ -1009,18 +1061,18 @@ command :finish do |c|
|
|
1009
1061
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1010
1062
|
|
1011
1063
|
c.desc 'Force exact search string matching (case sensitive)'
|
1012
|
-
c.switch %i[x exact], default_value:
|
1064
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1013
1065
|
|
1014
1066
|
c.desc 'Finish items that *don\'t* match search/tag filters'
|
1015
1067
|
c.switch [:not], default_value: false, negatable: false
|
1016
1068
|
|
1017
1069
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1018
1070
|
c.arg_name 'TYPE'
|
1019
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
1071
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1020
1072
|
|
1021
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
1073
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
1022
1074
|
c.arg_name 'BOOLEAN'
|
1023
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
1075
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
1024
1076
|
|
1025
1077
|
c.desc 'Remove done tag'
|
1026
1078
|
c.switch %i[r remove], negatable: false, default_value: false
|
@@ -1119,7 +1171,14 @@ command :finish do |c|
|
|
1119
1171
|
end
|
1120
1172
|
|
1121
1173
|
desc 'Repeat last entry as new entry'
|
1174
|
+
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
1175
|
command %i[again resume] do |c|
|
1176
|
+
c.example 'doing resume', desc: 'Duplicate the most recent entry with a new start time, removing any @done tag'
|
1177
|
+
c.example 'doing again', desc: 'again is an alias for resume'
|
1178
|
+
c.example 'doing resume --editor', desc: 'Repeat the last entry, opening the new entry in the default editor'
|
1179
|
+
c.example 'doing resume --tag project1 --in Projects', desc: 'Repeat the last entry tagged @project1, creating the new entry in the Projects section'
|
1180
|
+
c.example 'doing resume --interactive', desc: 'Select the entry to repeat from a menu'
|
1181
|
+
|
1123
1182
|
c.desc 'Get last entry from a specific section'
|
1124
1183
|
c.arg_name 'NAME'
|
1125
1184
|
c.flag %i[s section], default_value: 'All'
|
@@ -1128,7 +1187,7 @@ command %i[again resume] do |c|
|
|
1128
1187
|
c.arg_name 'SECTION_NAME'
|
1129
1188
|
c.flag [:in]
|
1130
1189
|
|
1131
|
-
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma.'
|
1190
|
+
c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
1132
1191
|
c.arg_name 'TAG'
|
1133
1192
|
c.flag [:tag]
|
1134
1193
|
|
@@ -1141,18 +1200,18 @@ command %i[again resume] do |c|
|
|
1141
1200
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1142
1201
|
|
1143
1202
|
c.desc 'Force exact search string matching (case sensitive)'
|
1144
|
-
c.switch %i[x exact], default_value:
|
1203
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1145
1204
|
|
1146
1205
|
c.desc 'Resume items that *don\'t* match search/tag filters'
|
1147
1206
|
c.switch [:not], default_value: false, negatable: false
|
1148
1207
|
|
1149
1208
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1150
1209
|
c.arg_name 'TYPE'
|
1151
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
1210
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1152
1211
|
|
1153
|
-
c.desc 'Boolean used to combine multiple tags'
|
1212
|
+
c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans.'
|
1154
1213
|
c.arg_name 'BOOLEAN'
|
1155
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
1214
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
1156
1215
|
|
1157
1216
|
c.desc "Edit duplicated entry with #{Doing::Util.default_editor} before adding"
|
1158
1217
|
c.switch %i[e editor], negatable: false, default_value: false
|
@@ -1186,6 +1245,77 @@ command %i[again resume] do |c|
|
|
1186
1245
|
end
|
1187
1246
|
end
|
1188
1247
|
|
1248
|
+
desc 'List all tags in the current Doing file'
|
1249
|
+
command :tags do |c|
|
1250
|
+
c.desc 'Section'
|
1251
|
+
c.arg_name 'SECTION_NAME'
|
1252
|
+
c.flag %i[s section], default_value: 'All'
|
1253
|
+
|
1254
|
+
c.desc 'Show count of occurrences'
|
1255
|
+
c.switch %i[c counts]
|
1256
|
+
|
1257
|
+
c.desc 'Sort by name or count'
|
1258
|
+
c.arg_name 'SORT_ORDER'
|
1259
|
+
c.flag %i[sort], default_value: 'name', must_match: /^(?:n(?:ame)?|c(?:ount)?)$/
|
1260
|
+
|
1261
|
+
c.desc 'Sort order (asc/desc)'
|
1262
|
+
c.arg_name 'ORDER'
|
1263
|
+
c.flag %i[o order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
1264
|
+
|
1265
|
+
c.desc 'Get tags for entries matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
1266
|
+
c.arg_name 'TAG'
|
1267
|
+
c.flag [:tag]
|
1268
|
+
|
1269
|
+
c.desc 'Get tags for items matching search. Surround with
|
1270
|
+
slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
|
1271
|
+
c.arg_name 'QUERY'
|
1272
|
+
c.flag [:search]
|
1273
|
+
|
1274
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
1275
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
1276
|
+
|
1277
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
1278
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1279
|
+
|
1280
|
+
c.desc 'Get tags from items that *don\'t* match search/tag filters'
|
1281
|
+
c.switch [:not], default_value: false, negatable: false
|
1282
|
+
|
1283
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1284
|
+
c.arg_name 'TYPE'
|
1285
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1286
|
+
|
1287
|
+
c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans.'
|
1288
|
+
c.arg_name 'BOOLEAN'
|
1289
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
1290
|
+
|
1291
|
+
c.desc 'Select items to scan from a menu of matching entries'
|
1292
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
1293
|
+
|
1294
|
+
c.action do |_global, options, args|
|
1295
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
1296
|
+
|
1297
|
+
items = wwid.filter_items([], opt: options)
|
1298
|
+
|
1299
|
+
# items = wwid.content.in_section(section)
|
1300
|
+
tags = wwid.all_tags(items, counts: true)
|
1301
|
+
|
1302
|
+
if options[:sort] =~ /^n/i
|
1303
|
+
tags = tags.sort_by { |tag, count| tag }
|
1304
|
+
else
|
1305
|
+
tags = tags.sort_by { |tag, count| count }
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
tags.reverse! if options[:order].normalize_order == 'desc'
|
1309
|
+
|
1310
|
+
if options[:counts]
|
1311
|
+
tags.each { |t, c| puts "#{t} (#{c})" }
|
1312
|
+
else
|
1313
|
+
tags.each { |t, c| puts "#{t}" }
|
1314
|
+
end
|
1315
|
+
end
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
|
1189
1319
|
desc 'Add tag(s) to last entry'
|
1190
1320
|
long_desc 'Add (or remove) tags from the last entry, or from multiple entries
|
1191
1321
|
(with `--count`), entries matching a search (with `--search`), or entries
|
@@ -1239,7 +1369,7 @@ command :tag do |c|
|
|
1239
1369
|
c.switch %i[a autotag], negatable: false, default_value: false
|
1240
1370
|
|
1241
1371
|
c.desc 'Tag the last X entries containing TAG.
|
1242
|
-
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
1372
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
1243
1373
|
c.arg_name 'TAG'
|
1244
1374
|
c.flag [:tag]
|
1245
1375
|
|
@@ -1251,18 +1381,18 @@ command :tag do |c|
|
|
1251
1381
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1252
1382
|
|
1253
1383
|
c.desc 'Force exact search string matching (case sensitive)'
|
1254
|
-
c.switch %i[x exact], default_value:
|
1384
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1255
1385
|
|
1256
1386
|
c.desc 'Tag items that *don\'t* match search/tag filters'
|
1257
1387
|
c.switch [:not], default_value: false, negatable: false
|
1258
1388
|
|
1259
1389
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1260
1390
|
c.arg_name 'TYPE'
|
1261
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
1391
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1262
1392
|
|
1263
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
1393
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
1264
1394
|
c.arg_name 'BOOLEAN'
|
1265
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
1395
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
1266
1396
|
|
1267
1397
|
c.desc 'Select item(s) to tag from a menu of matching entries'
|
1268
1398
|
c.switch %i[i interactive], negatable: false, default_value: false
|
@@ -1314,44 +1444,49 @@ command :tag do |c|
|
|
1314
1444
|
options[:search] = search
|
1315
1445
|
end
|
1316
1446
|
|
1447
|
+
options[:count] = count
|
1448
|
+
options[:section] = section
|
1449
|
+
options[:tag] = search_tags
|
1450
|
+
options[:tags] = tags
|
1451
|
+
options[:tag_bool] = options[:bool].normalize_bool
|
1452
|
+
|
1317
1453
|
if count.zero? && !options[:force]
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1454
|
+
matches = wwid.filter_items([], opt: options).count
|
1455
|
+
|
1456
|
+
if matches > 5
|
1457
|
+
if options[:search]
|
1458
|
+
section_q = ' matching your search terms'
|
1459
|
+
elsif options[:tag]
|
1460
|
+
section_q = ' matching your tag search'
|
1461
|
+
elsif section == 'All'
|
1462
|
+
section_q = ''
|
1463
|
+
else
|
1464
|
+
section_q = " in section #{section}"
|
1465
|
+
end
|
1327
1466
|
|
1328
1467
|
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1468
|
+
question = if options[:autotag]
|
1469
|
+
"Are you sure you want to autotag #{matches} records#{section_q}"
|
1470
|
+
elsif options[:remove]
|
1471
|
+
"Are you sure you want to remove #{tags.join(' and ')} from #{matches} records#{section_q}"
|
1472
|
+
else
|
1473
|
+
"Are you sure you want to add #{tags.join(' and ')} to #{matches} records#{section_q}"
|
1474
|
+
end
|
1336
1475
|
|
1337
|
-
|
1476
|
+
res = Doing::Prompt.yn(question, default_response: false)
|
1338
1477
|
|
1339
|
-
|
1478
|
+
raise UserCancelled unless res
|
1479
|
+
end
|
1340
1480
|
end
|
1341
1481
|
|
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
1482
|
wwid.tag_last(options)
|
1349
1483
|
end
|
1350
1484
|
end
|
1351
1485
|
|
1352
1486
|
desc 'Mark last entry as flagged'
|
1353
|
-
command [
|
1487
|
+
command %i[mark flag] do |c|
|
1354
1488
|
c.example 'doing flag', desc: 'Add @flagged to the last entry created'
|
1489
|
+
c.example 'doing mark', desc: 'mark is an alias for flag'
|
1355
1490
|
c.example 'doing flag --tag project1 --count 2', desc: 'Add @flagged to the last 2 entries tagged @project1'
|
1356
1491
|
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
1492
|
|
@@ -1376,7 +1511,7 @@ command [:mark, :flag] do |c|
|
|
1376
1511
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
1377
1512
|
|
1378
1513
|
c.desc 'Flag the last entry containing TAG.
|
1379
|
-
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
1514
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
1380
1515
|
c.arg_name 'TAG'
|
1381
1516
|
c.flag [:tag]
|
1382
1517
|
|
@@ -1388,18 +1523,18 @@ command [:mark, :flag] do |c|
|
|
1388
1523
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1389
1524
|
|
1390
1525
|
c.desc 'Force exact search string matching (case sensitive)'
|
1391
|
-
c.switch %i[x exact], default_value:
|
1526
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1392
1527
|
|
1393
1528
|
c.desc 'Flag items that *don\'t* match search/tag/date filters'
|
1394
1529
|
c.switch [:not], default_value: false, negatable: false
|
1395
1530
|
|
1396
1531
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1397
1532
|
c.arg_name 'TYPE'
|
1398
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
1533
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1399
1534
|
|
1400
|
-
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
1535
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans.'
|
1401
1536
|
c.arg_name 'BOOLEAN'
|
1402
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
1537
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
1403
1538
|
|
1404
1539
|
c.desc 'Select item(s) to flag from a menu of matching entries'
|
1405
1540
|
c.switch %i[i interactive], negatable: false, default_value: false
|
@@ -1473,7 +1608,11 @@ end
|
|
1473
1608
|
desc 'List all entries'
|
1474
1609
|
long_desc %(
|
1475
1610
|
The argument can be a section name, @tag(s) or both.
|
1476
|
-
"pick" or "choose" as an argument will offer a section menu.
|
1611
|
+
"pick" or "choose" as an argument will offer a section menu. Run with `--menu` to get a menu of available tags.
|
1612
|
+
|
1613
|
+
Show tags by passing @tagname arguments. Multiple tags can be combined, and you can specify the boolean used to
|
1614
|
+
combine them with `--bool (AND|OR|NOT)`. You can also use @+tagname to require a tag to match, or @-tagname to ignore
|
1615
|
+
entries containing tagname. +/- operators require `--bool PATTERN` (which is the default).
|
1477
1616
|
)
|
1478
1617
|
arg_name '[SECTION|@TAGS]'
|
1479
1618
|
command :show do |c|
|
@@ -1485,17 +1624,17 @@ command :show do |c|
|
|
1485
1624
|
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
1625
|
c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
|
1487
1626
|
|
1488
|
-
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
1627
|
+
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
1628
|
c.arg_name 'TAG'
|
1490
1629
|
c.flag [:tag]
|
1491
1630
|
|
1492
|
-
c.desc 'Tag boolean (AND,OR,NOT)'
|
1631
|
+
c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans.'
|
1493
1632
|
c.arg_name 'BOOLEAN'
|
1494
|
-
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: '
|
1633
|
+
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
1495
1634
|
|
1496
1635
|
c.desc 'Max count to show'
|
1497
1636
|
c.arg_name 'MAX'
|
1498
|
-
c.flag %i[c count], default_value: 0
|
1637
|
+
c.flag %i[c count], default_value: 0, must_match: /^\d+$/, type: Integer
|
1499
1638
|
|
1500
1639
|
c.desc 'Age (oldest|newest)'
|
1501
1640
|
c.arg_name 'AGE'
|
@@ -1529,14 +1668,14 @@ command :show do |c|
|
|
1529
1668
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1530
1669
|
|
1531
1670
|
c.desc 'Force exact search string matching (case sensitive)'
|
1532
|
-
c.switch %i[x exact], default_value:
|
1671
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1533
1672
|
|
1534
1673
|
c.desc 'Show items that *don\'t* match search/tag/date filters'
|
1535
1674
|
c.switch [:not], default_value: false, negatable: false
|
1536
1675
|
|
1537
1676
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1538
1677
|
c.arg_name 'TYPE'
|
1539
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
1678
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1540
1679
|
|
1541
1680
|
c.desc 'Sort order (asc/desc)'
|
1542
1681
|
c.arg_name 'ORDER'
|
@@ -1564,6 +1703,9 @@ command :show do |c|
|
|
1564
1703
|
c.desc 'Only show items with recorded time intervals'
|
1565
1704
|
c.switch [:only_timed], default_value: false, negatable: false
|
1566
1705
|
|
1706
|
+
c.desc 'Select section or tag to display from a menu'
|
1707
|
+
c.switch %i[m menu], negatable: false, default_value: false
|
1708
|
+
|
1567
1709
|
c.desc 'Select from a menu of matching entries to perform additional operations'
|
1568
1710
|
c.switch %i[i interactive], negatable: false, default_value: false
|
1569
1711
|
|
@@ -1576,15 +1718,17 @@ command :show do |c|
|
|
1576
1718
|
|
1577
1719
|
tag_filter = false
|
1578
1720
|
tags = []
|
1721
|
+
|
1579
1722
|
if args.length.positive?
|
1580
1723
|
case args[0]
|
1581
1724
|
when /^all$/i
|
1582
1725
|
section = 'All'
|
1583
1726
|
args.shift
|
1584
1727
|
when /^(choose|pick)$/i
|
1585
|
-
section = wwid.choose_section
|
1728
|
+
section = wwid.choose_section(include_all: true)
|
1729
|
+
|
1586
1730
|
args.shift
|
1587
|
-
when
|
1731
|
+
when /^[@+-]/
|
1588
1732
|
section = 'All'
|
1589
1733
|
else
|
1590
1734
|
begin
|
@@ -1607,18 +1751,12 @@ command :show do |c|
|
|
1607
1751
|
end
|
1608
1752
|
end
|
1609
1753
|
else
|
1610
|
-
section = settings['current_section']
|
1754
|
+
section = options[:menu] ? wwid.choose_section(include_all: true) : settings['current_section']
|
1755
|
+
section ||= 'All'
|
1611
1756
|
end
|
1612
1757
|
|
1613
1758
|
tags.concat(options[:tag].to_tags) if options[:tag]
|
1614
1759
|
|
1615
|
-
unless tags.empty?
|
1616
|
-
tag_filter = {
|
1617
|
-
'tags' => tags,
|
1618
|
-
'bool' => options[:bool].normalize_bool
|
1619
|
-
}
|
1620
|
-
end
|
1621
|
-
|
1622
1760
|
options[:times] = true if options[:totals]
|
1623
1761
|
|
1624
1762
|
template = settings['templates']['default'].deep_merge({
|
@@ -1636,27 +1774,55 @@ command :show do |c|
|
|
1636
1774
|
options[:search] = search
|
1637
1775
|
end
|
1638
1776
|
|
1777
|
+
options[:section] = section
|
1778
|
+
|
1779
|
+
unless tags.empty?
|
1780
|
+
tag_filter = {
|
1781
|
+
'tags' => tags,
|
1782
|
+
'bool' => options[:bool].normalize_bool
|
1783
|
+
}
|
1784
|
+
end
|
1785
|
+
|
1786
|
+
options[:tag_filter] = tag_filter
|
1787
|
+
options[:tag] = nil
|
1788
|
+
|
1789
|
+
items = wwid.filter_items([], opt: options)
|
1790
|
+
|
1791
|
+
if options[:menu]
|
1792
|
+
tag = wwid.choose_tag(section, items: items, include_all: true)
|
1793
|
+
raise UserCancelled unless tag
|
1794
|
+
|
1795
|
+
# options[:bool] = :and unless tags.empty?
|
1796
|
+
|
1797
|
+
tags = tag.split(/ +/).map { |t| t.strip.sub(/^@?/, '') } if tag =~ /^@/
|
1798
|
+
unless tags.empty?
|
1799
|
+
tag_filter = {
|
1800
|
+
'tags' => tags,
|
1801
|
+
'bool' => options[:bool].normalize_bool
|
1802
|
+
}
|
1803
|
+
options[:tag_filter] = tag_filter
|
1804
|
+
end
|
1805
|
+
end
|
1806
|
+
|
1639
1807
|
opt = options.dup
|
1640
1808
|
opt[:sort_tags] = options[:tag_sort] =~ /^n/i
|
1641
1809
|
opt[:count] = options[:count].to_i
|
1642
1810
|
opt[:highlight] = true
|
1643
1811
|
opt[:order] = options[:sort].normalize_order
|
1644
|
-
opt[:section] = section
|
1645
1812
|
opt[:tag] = nil
|
1646
|
-
opt[:tag_filter] = tag_filter
|
1647
1813
|
opt[:tag_order] = options[:tag_order].normalize_order
|
1648
1814
|
opt[:tags_color] = template['tags_color']
|
1649
1815
|
|
1650
|
-
Doing::Pager.page wwid.list_section(opt)
|
1816
|
+
Doing::Pager.page wwid.list_section(opt, items: items)
|
1651
1817
|
end
|
1652
1818
|
end
|
1653
1819
|
|
1654
1820
|
desc 'Search for entries'
|
1655
|
-
long_desc
|
1656
|
-
|
1821
|
+
long_desc %(
|
1822
|
+
Search all sections (or limit to a single section) for entries matching text or regular expression. Normal strings are fuzzy matched.
|
1657
1823
|
|
1658
|
-
|
1659
|
-
|
1824
|
+
To search with regular expressions, single quote the string and surround with slashes: `doing search '/\bm.*?x\b/'`
|
1825
|
+
)
|
1660
1826
|
|
1661
1827
|
arg_name 'SEARCH_PATTERN'
|
1662
1828
|
command %i[grep search] do |c|
|
@@ -1714,14 +1880,14 @@ command %i[grep search] do |c|
|
|
1714
1880
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
1715
1881
|
|
1716
1882
|
c.desc 'Force exact string matching (case sensitive)'
|
1717
|
-
c.switch %i[x exact], default_value:
|
1883
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
1718
1884
|
|
1719
1885
|
c.desc 'Show items that *don\'t* match search string'
|
1720
1886
|
c.switch [:not], default_value: false, negatable: false
|
1721
1887
|
|
1722
1888
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
1723
1889
|
c.arg_name 'TYPE'
|
1724
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
1890
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
1725
1891
|
|
1726
1892
|
c.desc 'Display an interactive menu of results to perform further operations'
|
1727
1893
|
c.switch %i[i interactive], default_value: false, negatable: false
|
@@ -1820,6 +1986,8 @@ command :recent do |c|
|
|
1820
1986
|
end
|
1821
1987
|
|
1822
1988
|
desc 'List entries from today'
|
1989
|
+
long_desc 'List entries from the current day. Use --before, --after, and
|
1990
|
+
--from to specify time ranges.'
|
1823
1991
|
command :today do |c|
|
1824
1992
|
c.example 'doing today', desc: 'List all entries with start dates between 12am and 11:59PM for the current day'
|
1825
1993
|
c.example 'doing today --section Later', desc: 'List today\'s entries in the Later section'
|
@@ -1994,6 +2162,8 @@ command :since do |c|
|
|
1994
2162
|
end
|
1995
2163
|
|
1996
2164
|
desc 'List entries from yesterday'
|
2165
|
+
long_desc 'Show only entries with start times within the previous 24 hour period. Use --before, --after, and --from to limit to
|
2166
|
+
time spans within the day.'
|
1997
2167
|
command :yesterday do |c|
|
1998
2168
|
c.example 'doing yesterday', desc: 'List all entries from the previous day'
|
1999
2169
|
c.example 'doing yesterday --after 8am --before 5pm', desc: 'List entries from the previous day between 8am and 5pm'
|
@@ -2065,6 +2235,8 @@ command :yesterday do |c|
|
|
2065
2235
|
end
|
2066
2236
|
|
2067
2237
|
desc 'Show the last entry, optionally edit'
|
2238
|
+
long_desc 'Shows the last entry. Using --search and --tag filters, you can view/edit the last entry matching a filter,
|
2239
|
+
allowing `doing last` to target historical entries.'
|
2068
2240
|
command :last do |c|
|
2069
2241
|
c.example 'doing last', desc: 'Show the most recent entry in all sections'
|
2070
2242
|
c.example 'doing last -s Later', desc: 'Show the most recent entry in the Later section'
|
@@ -2081,13 +2253,13 @@ command :last do |c|
|
|
2081
2253
|
c.desc "Edit entry with #{Doing::Util.default_editor}"
|
2082
2254
|
c.switch %i[e editor], negatable: false, default_value: false
|
2083
2255
|
|
2084
|
-
c.desc 'Tag filter, combine multiple tags with a comma.'
|
2256
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
2085
2257
|
c.arg_name 'TAG'
|
2086
2258
|
c.flag [:tag]
|
2087
2259
|
|
2088
|
-
c.desc 'Tag boolean'
|
2260
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
|
2089
2261
|
c.arg_name 'BOOLEAN'
|
2090
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
2262
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
2091
2263
|
|
2092
2264
|
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
2093
2265
|
c.arg_name 'QUERY'
|
@@ -2100,14 +2272,14 @@ command :last do |c|
|
|
2100
2272
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2101
2273
|
|
2102
2274
|
c.desc 'Force exact search string matching (case sensitive)'
|
2103
|
-
c.switch %i[x exact], default_value:
|
2275
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
2104
2276
|
|
2105
2277
|
c.desc 'Show items that *don\'t* match search string or tag filter'
|
2106
2278
|
c.switch [:not], default_value: false, negatable: false
|
2107
2279
|
|
2108
2280
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
2109
2281
|
c.arg_name 'TYPE'
|
2110
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
2282
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
2111
2283
|
|
2112
2284
|
c.action do |global_options, options, _args|
|
2113
2285
|
options[:fuzzy] = false
|
@@ -2117,15 +2289,7 @@ command :last do |c|
|
|
2117
2289
|
tags = []
|
2118
2290
|
else
|
2119
2291
|
tags = options[:tag].to_tags
|
2120
|
-
options[:bool] =
|
2121
|
-
when /(any|or)/i
|
2122
|
-
:or
|
2123
|
-
when /(not|none)/i
|
2124
|
-
:not
|
2125
|
-
else
|
2126
|
-
:and
|
2127
|
-
end
|
2128
|
-
|
2292
|
+
options[:bool] = options[:bool].normalize_bool
|
2129
2293
|
end
|
2130
2294
|
|
2131
2295
|
options[:case] = options[:case].normalize_case
|
@@ -2230,6 +2394,8 @@ command :plugins do |c|
|
|
2230
2394
|
end
|
2231
2395
|
|
2232
2396
|
desc 'Generate shell completion scripts'
|
2397
|
+
long_desc 'Generates the necessary scripts to add command line completion to various shells, so typing \'doing\' and hitting
|
2398
|
+
tab will offer completions of subcommands and their options.'
|
2233
2399
|
command :completion do |c|
|
2234
2400
|
c.example 'doing completion', desc: 'Output zsh (default) to STDOUT'
|
2235
2401
|
c.example 'doing completion --type zsh --file ~/.zsh-completions/_doing.zsh', desc: 'Output zsh completions to file'
|
@@ -2252,7 +2418,8 @@ command :completion do |c|
|
|
2252
2418
|
end
|
2253
2419
|
|
2254
2420
|
desc 'Display a user-created view'
|
2255
|
-
long_desc '
|
2421
|
+
long_desc 'Views are defined in your configuration (use `doing config` to edit).
|
2422
|
+
Command line options override view configuration.'
|
2256
2423
|
arg_name 'VIEW_NAME'
|
2257
2424
|
command :view do |c|
|
2258
2425
|
c.example 'doing view color', desc: 'Display entries according to config for view "color"'
|
@@ -2282,13 +2449,13 @@ command :view do |c|
|
|
2282
2449
|
c.desc 'Include colors in output'
|
2283
2450
|
c.switch [:color], default_value: true, negatable: true
|
2284
2451
|
|
2285
|
-
c.desc 'Tag filter, combine multiple tags with a comma.'
|
2452
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?).'
|
2286
2453
|
c.arg_name 'TAG'
|
2287
2454
|
c.flag [:tag]
|
2288
2455
|
|
2289
|
-
c.desc 'Tag boolean (AND,OR,NOT)'
|
2456
|
+
c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans.'
|
2290
2457
|
c.arg_name 'BOOLEAN'
|
2291
|
-
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: '
|
2458
|
+
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
2292
2459
|
|
2293
2460
|
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
2294
2461
|
c.arg_name 'QUERY'
|
@@ -2298,14 +2465,14 @@ command :view do |c|
|
|
2298
2465
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2299
2466
|
|
2300
2467
|
c.desc 'Force exact search string matching (case sensitive)'
|
2301
|
-
c.switch %i[x exact], default_value:
|
2468
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
2302
2469
|
|
2303
2470
|
c.desc 'Show items that *don\'t* match search string'
|
2304
2471
|
c.switch [:not], default_value: false, negatable: false
|
2305
2472
|
|
2306
2473
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
2307
2474
|
c.arg_name 'TYPE'
|
2308
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
2475
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
2309
2476
|
|
2310
2477
|
c.desc 'Sort tags by (name|time)'
|
2311
2478
|
c.arg_name 'KEY'
|
@@ -2367,6 +2534,7 @@ command :view do |c|
|
|
2367
2534
|
end
|
2368
2535
|
|
2369
2536
|
view = wwid.get_view(title)
|
2537
|
+
|
2370
2538
|
if view
|
2371
2539
|
page_title = view.key?('title') ? view['title'] : title.cap_first
|
2372
2540
|
only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
|
@@ -2382,16 +2550,22 @@ command :view do |c|
|
|
2382
2550
|
tag_filter = false
|
2383
2551
|
if options[:tag]
|
2384
2552
|
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
2385
|
-
|
2386
|
-
tag_filter['bool'] =
|
2553
|
+
bool = options[:bool].normalize_bool
|
2554
|
+
tag_filter['bool'] = bool
|
2555
|
+
tag_filter['tags'] = if bool == :pattern
|
2556
|
+
options[:tag]
|
2557
|
+
else
|
2558
|
+
options[:tag].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
2559
|
+
end
|
2387
2560
|
elsif view.key?('tags') && !(view['tags'].nil? || view['tags'].empty?)
|
2388
2561
|
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
2562
|
+
bool = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :pattern
|
2563
|
+
tag_filter['bool'] = bool
|
2389
2564
|
tag_filter['tags'] = if view['tags'].instance_of?(Array)
|
2390
|
-
view['tags'].map(&:strip)
|
2565
|
+
bool == :pattern ? view['tags'].join(' ').strip : view['tags'].map(&:strip)
|
2391
2566
|
else
|
2392
|
-
view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
2567
|
+
bool == :pattern ? view['tags'].strip : view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
2393
2568
|
end
|
2394
|
-
tag_filter['bool'] = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :or
|
2395
2569
|
end
|
2396
2570
|
|
2397
2571
|
# If the -o/--output flag was specified, override any default in the view template
|
@@ -2440,6 +2614,7 @@ command :view do |c|
|
|
2440
2614
|
end
|
2441
2615
|
|
2442
2616
|
opts = options.dup
|
2617
|
+
opts[:view_template] = title
|
2443
2618
|
opts[:count] = count
|
2444
2619
|
opts[:format] = date_format
|
2445
2620
|
opts[:highlight] = options[:color]
|
@@ -2500,13 +2675,13 @@ command %i[archive move] do |c|
|
|
2500
2675
|
c.desc 'Label moved items with @from(SECTION_NAME)'
|
2501
2676
|
c.switch [:label], default_value: true, negatable: true
|
2502
2677
|
|
2503
|
-
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
2678
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands.'
|
2504
2679
|
c.arg_name 'TAG'
|
2505
2680
|
c.flag [:tag]
|
2506
2681
|
|
2507
|
-
c.desc 'Tag boolean (AND|OR|NOT)'
|
2682
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
|
2508
2683
|
c.arg_name 'BOOLEAN'
|
2509
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
2684
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
2510
2685
|
|
2511
2686
|
c.desc 'Search filter'
|
2512
2687
|
c.arg_name 'QUERY'
|
@@ -2516,14 +2691,14 @@ command %i[archive move] do |c|
|
|
2516
2691
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2517
2692
|
|
2518
2693
|
c.desc 'Force exact search string matching (case sensitive)'
|
2519
|
-
c.switch %i[x exact], default_value:
|
2694
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
2520
2695
|
|
2521
2696
|
c.desc 'Show items that *don\'t* match search string'
|
2522
2697
|
c.switch [:not], default_value: false, negatable: false
|
2523
2698
|
|
2524
2699
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
2525
2700
|
c.arg_name 'TYPE'
|
2526
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
2701
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
2527
2702
|
|
2528
2703
|
c.desc 'Archive entries older than date
|
2529
2704
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
@@ -2569,6 +2744,9 @@ command %i[archive move] do |c|
|
|
2569
2744
|
end
|
2570
2745
|
|
2571
2746
|
desc 'Move entries to archive file'
|
2747
|
+
long_desc 'As your doing file grows, commands can get slow. Given that your historical data (and your archive section)
|
2748
|
+
probably aren\'t providing any useful insights a year later, use this command to "rotate" old entries out to an archive
|
2749
|
+
file. You\'ll still have access to all historical data, but it won\'t be slowing down daily operation.'
|
2572
2750
|
command :rotate do |c|
|
2573
2751
|
c.example 'doing rotate', desc: 'Move all entries in doing file to a dated secondary file'
|
2574
2752
|
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'
|
@@ -2582,13 +2760,13 @@ command :rotate do |c|
|
|
2582
2760
|
c.arg_name 'SECTION_NAME'
|
2583
2761
|
c.flag %i[s section], default_value: 'All'
|
2584
2762
|
|
2585
|
-
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
2763
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands.'
|
2586
2764
|
c.arg_name 'TAG'
|
2587
2765
|
c.flag [:tag]
|
2588
2766
|
|
2589
|
-
c.desc 'Tag boolean (AND|OR|NOT)'
|
2767
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans.'
|
2590
2768
|
c.arg_name 'BOOLEAN'
|
2591
|
-
c.flag [:bool], must_match: REGEX_BOOL, default_value: '
|
2769
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
2592
2770
|
|
2593
2771
|
c.desc 'Search filter'
|
2594
2772
|
c.arg_name 'QUERY'
|
@@ -2598,14 +2776,14 @@ command :rotate do |c|
|
|
2598
2776
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2599
2777
|
|
2600
2778
|
c.desc 'Force exact search string matching (case sensitive)'
|
2601
|
-
c.switch %i[x exact], default_value:
|
2779
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
2602
2780
|
|
2603
2781
|
c.desc 'Rotate items that *don\'t* match search string or tag filter'
|
2604
2782
|
c.switch [:not], default_value: false, negatable: false
|
2605
2783
|
|
2606
2784
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
2607
2785
|
c.arg_name 'TYPE'
|
2608
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
2786
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
2609
2787
|
|
2610
2788
|
c.desc 'Rotate entries older than date
|
2611
2789
|
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
@@ -2635,8 +2813,10 @@ command :rotate do |c|
|
|
2635
2813
|
end
|
2636
2814
|
|
2637
2815
|
desc 'Open the "doing" file in an editor'
|
2638
|
-
long_desc "`doing open` defaults to using the
|
2816
|
+
long_desc "`doing open` defaults to using the editors->doing_file setting
|
2817
|
+
in #{config.config_file} (#{Doing::Util.find_default_editor('doing_file')})."
|
2639
2818
|
command :open do |c|
|
2819
|
+
c.example 'doing open', desc: 'Open the doing file in the default editor'
|
2640
2820
|
c.desc 'Open with editor command (e.g. vim, mate)'
|
2641
2821
|
c.arg_name 'COMMAND'
|
2642
2822
|
c.flag %i[e editor]
|
@@ -2810,7 +2990,7 @@ command :config do |c|
|
|
2810
2990
|
c.command :undo do |undo|
|
2811
2991
|
undo.action do |_global, options, args|
|
2812
2992
|
config_file = config.choose_config
|
2813
|
-
|
2993
|
+
Doing::Util::Backup.restore_last_backup(config_file, count: 1)
|
2814
2994
|
end
|
2815
2995
|
end
|
2816
2996
|
|
@@ -2885,6 +3065,7 @@ command :config do |c|
|
|
2885
3065
|
real_path = config.resolve_key_path(keypath, create: true)
|
2886
3066
|
|
2887
3067
|
old_value = settings.dig(*real_path) || nil
|
3068
|
+
old_type = old_value&.class.to_s || nil
|
2888
3069
|
|
2889
3070
|
if old_value.is_a?(Hash) && !options[:remove]
|
2890
3071
|
Doing.logger.log_now(:warn, 'Config:', "Config key must point to a single value, #{real_path.join('->').boldwhite} is a mapping")
|
@@ -2906,13 +3087,11 @@ command :config do |c|
|
|
2906
3087
|
cfg.deep_set(real_path, nil)
|
2907
3088
|
$stderr.puts "#{'Deleting key:'.yellow} #{real_path.join('->').boldwhite}"
|
2908
3089
|
else
|
2909
|
-
|
2910
|
-
p real_path
|
2911
|
-
cfg.deep_set(real_path, value.set_type)
|
3090
|
+
cfg.deep_set(real_path, value.set_type(old_type))
|
2912
3091
|
|
2913
3092
|
$stderr.puts "#{'Key path:'.yellow} #{real_path.join('->').boldwhite}"
|
2914
|
-
$stderr.puts "#{'Previous:'.yellow} #{(old_value ? old_value
|
2915
|
-
$stderr.puts "#{' New:'.yellow} #{value.set_type.to_s.boldwhite}"
|
3093
|
+
$stderr.puts "#{'Previous:'.yellow} #{(old_value ? old_value.to_s : 'empty').boldwhite}"
|
3094
|
+
$stderr.puts "#{' New:'.yellow} #{value.set_type(old_type).to_s.boldwhite}"
|
2916
3095
|
end
|
2917
3096
|
|
2918
3097
|
res = Doing::Prompt.yn('Update selected config', default_response: true)
|
@@ -2925,22 +3104,126 @@ command :config do |c|
|
|
2925
3104
|
end
|
2926
3105
|
end
|
2927
3106
|
|
2928
|
-
desc 'Undo the last
|
3107
|
+
desc 'Undo the last X changes to the Doing file'
|
3108
|
+
long_desc 'Reverts the last X commands that altered the doing file.
|
3109
|
+
All changes performed by a single command are undone at once.
|
3110
|
+
|
3111
|
+
Specify a number to jump back multiple revisions, or use --select for an interactive menu.'
|
3112
|
+
arg_name 'COUNT'
|
2929
3113
|
command :undo do |c|
|
3114
|
+
c.example 'doing undo', desc: 'Undo the most recent change to the doing file'
|
3115
|
+
c.example 'doing undo 5', desc: 'Undo the last 5 changes to the doing file'
|
3116
|
+
c.example 'doing undo --interactive', desc: 'Select from a menu of available revisions'
|
3117
|
+
c.example 'doing undo --redo', desc: 'Undo the last undo command'
|
3118
|
+
|
2930
3119
|
c.desc 'Specify alternate doing file'
|
2931
3120
|
c.arg_name 'PATH'
|
2932
3121
|
c.flag %i[f file], default_value: wwid.doing_file
|
2933
3122
|
|
2934
|
-
c.
|
3123
|
+
c.desc 'Select from recent backups'
|
3124
|
+
c.switch %i[i interactive], negatable: false
|
3125
|
+
|
3126
|
+
c.desc 'Remove old backups, retaining X files'
|
3127
|
+
c.arg_name 'COUNT'
|
3128
|
+
c.flag %i[p prune], type: Integer
|
3129
|
+
|
3130
|
+
c.desc 'Redo last undo. Note: you cannot undo a redo.'
|
3131
|
+
c.switch %i[r redo]
|
3132
|
+
|
3133
|
+
c.action do |_global_options, options, args|
|
3134
|
+
file = options[:file] || wwid.doing_file
|
3135
|
+
count = args.empty? ? 1 : args[0].to_i
|
3136
|
+
raise InvalidArgument, "Invalid count specified for undo" unless count&.positive?
|
3137
|
+
|
3138
|
+
if options[:prune]
|
3139
|
+
Doing::Util::Backup.prune_backups(file, options[:prune])
|
3140
|
+
elsif options[:redo]
|
3141
|
+
if options[:interactive]
|
3142
|
+
Doing::Util::Backup.select_redo(file)
|
3143
|
+
else
|
3144
|
+
Doing::Util::Backup.redo_backup(file, count: count)
|
3145
|
+
end
|
3146
|
+
else
|
3147
|
+
if options[:interactive]
|
3148
|
+
Doing::Util::Backup.select_backup(file)
|
3149
|
+
else
|
3150
|
+
Doing::Util::Backup.restore_last_backup(file, count: count)
|
3151
|
+
end
|
3152
|
+
end
|
3153
|
+
end
|
3154
|
+
end
|
3155
|
+
|
3156
|
+
long_desc 'Shortcut for `doing undo -r`, reverses the last undo command. You cannot undo a redo.'
|
3157
|
+
arg_name 'COUNT'
|
3158
|
+
command :redo do |c|
|
3159
|
+
c.desc 'Specify alternate doing file'
|
3160
|
+
c.arg_name 'PATH'
|
3161
|
+
c.flag %i[f file], default_value: wwid.doing_file
|
3162
|
+
|
3163
|
+
c.desc 'Select from an interactive menu'
|
3164
|
+
c.switch %i[i interactive]
|
3165
|
+
|
3166
|
+
c.action do |_global, options, args|
|
2935
3167
|
file = options[:file] || wwid.doing_file
|
2936
|
-
|
3168
|
+
count = args.empty? ? 1 : args[0].to_i
|
3169
|
+
raise InvalidArgument, "Invalid count specified for redo" unless count&.positive?
|
3170
|
+
if options[:interactive]
|
3171
|
+
Doing::Util::Backup.select_redo(file)
|
3172
|
+
else
|
3173
|
+
Doing::Util::Backup.redo_backup(file, count: count)
|
3174
|
+
end
|
2937
3175
|
end
|
2938
3176
|
end
|
2939
3177
|
|
3178
|
+
desc 'List recent changes in Doing'
|
3179
|
+
long_desc 'Display a formatted list of changes in recent versions, latest at the top'
|
3180
|
+
command %i[changelog changes] do |c|
|
3181
|
+
c.action do |_global_options, options, args|
|
3182
|
+
changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md'))
|
3183
|
+
if File.exist?(changelog)
|
3184
|
+
parsed = TTY::Markdown.parse(IO.read(changelog), width: 80, symbols: {override: {bullet: "•"}})
|
3185
|
+
Doing::Pager.paginate = true
|
3186
|
+
Doing::Pager.page parsed
|
3187
|
+
else
|
3188
|
+
raise "Error locating changelog"
|
3189
|
+
end
|
3190
|
+
end
|
3191
|
+
end
|
3192
|
+
|
3193
|
+
arg_name 'OPTION'
|
3194
|
+
command :commands_accepting do |c|
|
3195
|
+
c.desc 'Output in single column for completion'
|
3196
|
+
c.switch %i[c column]
|
3197
|
+
|
3198
|
+
c.action do |g, o, a|
|
3199
|
+
a.each do |option|
|
3200
|
+
cmds = []
|
3201
|
+
commands.each do |cmd, v|
|
3202
|
+
v.flags.merge(v.switches).each do |n, flag|
|
3203
|
+
if flag.name == option.to_sym || flag.aliases&.include?(option.to_sym)
|
3204
|
+
cmds.push(cmd)
|
3205
|
+
end
|
3206
|
+
end
|
3207
|
+
end
|
3208
|
+
|
3209
|
+
if o[:column]
|
3210
|
+
puts cmds
|
3211
|
+
else
|
3212
|
+
puts "Commands accepting --#{option}: #{cmds.join(', ')}"
|
3213
|
+
end
|
3214
|
+
end
|
3215
|
+
end
|
3216
|
+
end
|
3217
|
+
|
3218
|
+
|
2940
3219
|
desc 'Import entries from an external source'
|
2941
3220
|
long_desc "Imports entries from other sources. Available plugins: #{Doing::Plugins.plugin_names(type: :import, separator: ', ')}"
|
2942
3221
|
arg_name 'PATH'
|
2943
3222
|
command :import do |c|
|
3223
|
+
c.example 'doing import --type timing "~/Desktop/All Activities.json"', desc: 'Import a Timing.app JSON report'
|
3224
|
+
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'
|
3225
|
+
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'
|
3226
|
+
|
2944
3227
|
c.desc "Import type (#{Doing::Plugins.plugin_names(type: :import)})"
|
2945
3228
|
c.arg_name 'TYPE'
|
2946
3229
|
c.flag :type, default_value: 'doing'
|
@@ -2953,14 +3236,14 @@ command :import do |c|
|
|
2953
3236
|
# c.switch [:fuzzy], default_value: false, negatable: false
|
2954
3237
|
|
2955
3238
|
c.desc 'Force exact search string matching (case sensitive)'
|
2956
|
-
c.switch %i[x exact], default_value:
|
3239
|
+
c.switch %i[x exact], default_value: config.exact_match?, negatable: config.exact_match?
|
2957
3240
|
|
2958
3241
|
c.desc 'Import items that *don\'t* match search/tag/date filters'
|
2959
3242
|
c.switch [:not], default_value: false, negatable: false
|
2960
3243
|
|
2961
3244
|
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
2962
3245
|
c.arg_name 'TYPE'
|
2963
|
-
c.flag [:case], must_match: /^[csi]/, default_value: '
|
3246
|
+
c.flag [:case], must_match: /^[csi]/, default_value: settings.dig('search', 'case')
|
2964
3247
|
|
2965
3248
|
c.desc 'Only import items with recorded time intervals'
|
2966
3249
|
c.switch [:only_timed], default_value: false, negatable: false
|
@@ -3035,7 +3318,6 @@ end
|
|
3035
3318
|
|
3036
3319
|
pre do |global, _command, _options, _args|
|
3037
3320
|
# global[:pager] ||= settings['paginate']
|
3038
|
-
|
3039
3321
|
Doing::Pager.paginate = global[:pager]
|
3040
3322
|
|
3041
3323
|
$stdout.puts "doing v#{Doing::VERSION}" if global[:version]
|
@@ -3066,6 +3348,8 @@ post do |global, _command, _options, _args|
|
|
3066
3348
|
# Use skips_post before a command to skip this
|
3067
3349
|
# block on that command only
|
3068
3350
|
Doing.logger.output_results
|
3351
|
+
Doing.logger.benchmark(:total, :finish)
|
3352
|
+
Doing.logger.log_benchmarks
|
3069
3353
|
end
|
3070
3354
|
|
3071
3355
|
around do |global, command, options, arguments, code|
|
@@ -3080,10 +3364,13 @@ around do |global, command, options, arguments, code|
|
|
3080
3364
|
|
3081
3365
|
if global[:yes]
|
3082
3366
|
Doing::Prompt.force_answer = true
|
3367
|
+
Doing.config.force_answer = true
|
3083
3368
|
elsif global[:no]
|
3084
3369
|
Doing::Prompt.force_answer = false
|
3370
|
+
Doing.config.force_answer = false
|
3085
3371
|
else
|
3086
3372
|
Doing::Prompt.default_answer = global[:default]
|
3373
|
+
Doing.config.force_answer = global[:default] ? true : false
|
3087
3374
|
end
|
3088
3375
|
|
3089
3376
|
if global[:config_file] && global[:config_file] != config.config_file
|
@@ -3096,13 +3383,13 @@ around do |global, command, options, arguments, code|
|
|
3096
3383
|
config.config_file = cf
|
3097
3384
|
settings = config.configure({ ignore_local: true })
|
3098
3385
|
end
|
3099
|
-
|
3386
|
+
Doing.logger.benchmark(:init, :start)
|
3100
3387
|
if global[:doing_file]
|
3101
3388
|
wwid.init_doing_file(global[:doing_file])
|
3102
3389
|
else
|
3103
3390
|
wwid.init_doing_file
|
3104
3391
|
end
|
3105
|
-
|
3392
|
+
Doing.logger.benchmark(:init, :finish)
|
3106
3393
|
wwid.auto_tag = !global[:noauto]
|
3107
3394
|
|
3108
3395
|
settings[:include_notes] = false unless global[:notes]
|