doing 2.1.2pre → 2.1.6pre
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/.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]
|