doing 2.1.26 → 2.1.27

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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +14 -19
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +23 -0
  6. data/Dockerfile +5 -5
  7. data/Dockerfile-2.6 +5 -5
  8. data/Dockerfile-2.7 +5 -4
  9. data/Dockerfile-3.0 +5 -4
  10. data/Gemfile.lock +1 -1
  11. data/README.md +1 -1
  12. data/Rakefile +2 -3
  13. data/bin/commands/add_section.rb +2 -0
  14. data/bin/commands/again.rb +23 -65
  15. data/bin/commands/archive.rb +20 -61
  16. data/bin/commands/cancel.rb +27 -69
  17. data/bin/commands/changes.rb +32 -5
  18. data/bin/commands/colors.rb +4 -2
  19. data/bin/commands/commands.rb +4 -2
  20. data/bin/commands/commands_accepting.rb +62 -11
  21. data/bin/commands/completion.rb +10 -7
  22. data/bin/commands/config.rb +1 -1
  23. data/bin/commands/done.rb +3 -17
  24. data/bin/commands/finish.rb +7 -30
  25. data/bin/commands/flag.rb +15 -51
  26. data/bin/commands/grep.rb +12 -28
  27. data/bin/commands/import.rb +3 -33
  28. data/bin/commands/last.rb +3 -36
  29. data/bin/commands/meanwhile.rb +3 -13
  30. data/bin/commands/note.rb +13 -52
  31. data/bin/commands/now.rb +15 -21
  32. data/bin/commands/on.rb +3 -4
  33. data/bin/commands/recent.rb +3 -4
  34. data/bin/commands/redo.rb +6 -2
  35. data/bin/commands/reset.rb +19 -52
  36. data/bin/commands/rotate.rb +5 -36
  37. data/bin/commands/select.rb +23 -41
  38. data/bin/commands/show.rb +28 -74
  39. data/bin/commands/since.rb +3 -4
  40. data/bin/commands/tag.rb +4 -34
  41. data/bin/commands/tags.rb +5 -32
  42. data/bin/commands/today.rb +3 -4
  43. data/bin/commands/view.rb +36 -73
  44. data/bin/commands/yesterday.rb +4 -5
  45. data/bin/doing +150 -13
  46. data/docs/doc/Array.html +3 -502
  47. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  48. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  49. data/docs/doc/BooleanTermParser/Query.html +1 -1
  50. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  51. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  52. data/docs/doc/BooleanTermParser.html +1 -1
  53. data/docs/doc/Doing/Color.html +62 -56
  54. data/docs/doc/Doing/Completion.html +1 -1
  55. data/docs/doc/Doing/Configuration.html +35 -1
  56. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  57. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  58. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  59. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  60. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  61. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  62. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  63. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  64. data/docs/doc/Doing/Errors.html +1 -1
  65. data/docs/doc/Doing/Hooks.html +1 -1
  66. data/docs/doc/Doing/Item.html +1 -1
  67. data/docs/doc/Doing/Items.html +2 -2
  68. data/docs/doc/Doing/LogAdapter.html +1 -1
  69. data/docs/doc/Doing/Note.html +2 -2
  70. data/docs/doc/Doing/Pager.html +1 -1
  71. data/docs/doc/Doing/Plugins.html +1 -1
  72. data/docs/doc/Doing/Prompt.html +1 -1
  73. data/docs/doc/Doing/Section.html +1 -1
  74. data/docs/doc/Doing/TemplateString.html +2 -2
  75. data/docs/doc/Doing/Types.html +41 -1
  76. data/docs/doc/Doing/Util/Backup.html +1 -1
  77. data/docs/doc/Doing/Util.html +1 -1
  78. data/docs/doc/Doing/WWID.html +10 -10
  79. data/docs/doc/Doing.html +3 -3
  80. data/docs/doc/FalseClass.html +35 -1
  81. data/docs/doc/GLI/Commands/Help.html +1 -1
  82. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  83. data/docs/doc/GLI/Commands.html +1 -1
  84. data/docs/doc/GLI.html +1 -1
  85. data/docs/doc/Hash.html +1 -1
  86. data/docs/doc/Object.html +1 -1
  87. data/docs/doc/PhraseParser/Operator.html +1 -1
  88. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  89. data/docs/doc/PhraseParser/Query.html +1 -1
  90. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  91. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  92. data/docs/doc/PhraseParser/TermClause.html +1 -1
  93. data/docs/doc/PhraseParser.html +1 -1
  94. data/docs/doc/Status.html +1 -1
  95. data/docs/doc/String.html +287 -3155
  96. data/docs/doc/Symbol.html +40 -6
  97. data/docs/doc/Time.html +1 -1
  98. data/docs/doc/TrueClass.html +35 -1
  99. data/docs/doc/_index.html +5 -10
  100. data/docs/doc/class_list.html +1 -1
  101. data/docs/doc/file.README.html +2 -2
  102. data/docs/doc/index.html +2 -2
  103. data/docs/doc/method_list.html +278 -678
  104. data/docs/doc/top-level-namespace.html +2 -2
  105. data/doing.rdoc +277 -175
  106. data/lib/completion/_doing.zsh +33 -29
  107. data/lib/completion/doing.bash +30 -19
  108. data/lib/completion/doing.fish +84 -72
  109. data/lib/doing/array/array.rb +4 -0
  110. data/lib/doing/array/nested_hash.rb +17 -0
  111. data/lib/doing/{array.rb → array/tags.rb} +7 -25
  112. data/lib/doing/changelog/change.rb +26 -11
  113. data/lib/doing/changelog/changes.rb +13 -3
  114. data/lib/doing/{array_chronify.rb → chronify/array.rb} +0 -0
  115. data/lib/doing/chronify/chronify.rb +5 -0
  116. data/lib/doing/{numeric_chronify.rb → chronify/numeric.rb} +0 -0
  117. data/lib/doing/{string_chronify.rb → chronify/string.rb} +0 -0
  118. data/lib/doing/colors.rb +115 -54
  119. data/lib/doing/configuration.rb +4 -0
  120. data/lib/doing/good.rb +8 -0
  121. data/lib/doing/help_monkey_patch.rb +6 -5
  122. data/lib/doing/item.rb +5 -5
  123. data/lib/doing/items.rb +2 -2
  124. data/lib/doing/log_adapter.rb +35 -2
  125. data/lib/doing/normalize.rb +188 -0
  126. data/lib/doing/plugins/export/dayone_export.rb +1 -1
  127. data/lib/doing/plugins/export/html_export.rb +1 -1
  128. data/lib/doing/plugins/export/json_export.rb +1 -1
  129. data/lib/doing/plugins/export/markdown_export.rb +1 -1
  130. data/lib/doing/plugins/export/template_export.rb +3 -1
  131. data/lib/doing/prompt.rb +1 -3
  132. data/lib/doing/string/highlight.rb +95 -0
  133. data/lib/doing/string/query.rb +129 -0
  134. data/lib/doing/string/string.rb +12 -0
  135. data/lib/doing/string/tags.rb +164 -0
  136. data/lib/doing/string/transform.rb +168 -0
  137. data/lib/doing/string/truncate.rb +75 -0
  138. data/lib/doing/string/url.rb +82 -0
  139. data/lib/doing/template_string.rb +0 -22
  140. data/lib/doing/types.rb +8 -0
  141. data/lib/doing/util.rb +13 -9
  142. data/lib/doing/version.rb +1 -1
  143. data/lib/doing/wwid.rb +53 -35
  144. data/lib/doing.rb +4 -6
  145. data/lib/examples/plugins/wiki_export/wiki_export.rb +1 -1
  146. data/lib/helpers/threaded_tests.rb +15 -2
  147. data/scripts/deploy.rb +107 -0
  148. data/scripts/runtests.sh +4 -0
  149. metadata +19 -8
  150. data/lib/doing/string.rb +0 -765
  151. data/lib/doing/symbol.rb +0 -28
data/bin/commands/now.rb CHANGED
@@ -20,13 +20,6 @@ command %i[now next] do |c|
20
20
  c.arg_name 'NAME'
21
21
  c.flag %i[s section]
22
22
 
23
- c.desc "Edit entry with #{Doing::Util.default_editor}"
24
- c.switch %i[e editor], negatable: false, default_value: false
25
-
26
- c.desc 'Backdate start time [4pm|20m|2h|"yesterday noon"]'
27
- c.arg_name 'DATE_STRING'
28
- c.flag %i[b back started], type: DateBeginString
29
-
30
23
  c.desc %(
31
24
  Set a start and optionally end time as a date range ("from 1pm to 2:30pm").
32
25
  If an end time is provided, a dated @done tag will be added
@@ -37,19 +30,16 @@ command %i[now next] do |c|
37
30
  c.desc 'Timed entry, marks last entry in section as @done'
38
31
  c.switch %i[f finish_last], negatable: false, default_value: false
39
32
 
40
- c.desc 'Include a note'
41
- c.arg_name 'TEXT'
42
- c.flag %i[n note]
43
-
44
- c.desc 'Prompt for note via multi-line input'
45
- c.switch %i[ask], negatable: false, default_value: false
33
+ add_options(:add_entry, c)
46
34
 
47
35
  # c.desc "Edit entry with specified app"
48
36
  # c.arg_name 'editor_app'
49
37
  # # c.flag [:a, :app]
50
38
 
51
39
  c.action do |_global_options, options, args|
52
- raise InvalidArgument, "--back and --from cannot be used together" if options[:back] && options[:from]
40
+ @wwid.auto_tag = !options[:noauto]
41
+
42
+ raise InvalidArgument, '--back and --from cannot be used together' if options[:back] && options[:from]
53
43
 
54
44
  if options[:back]
55
45
  date = options[:back]
@@ -61,13 +51,17 @@ command %i[now next] do |c|
61
51
  end
62
52
  raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
63
53
 
64
- if options[:section]
65
- section = @wwid.guess_section(options[:section]) || options[:section].cap_first
66
- else
67
- options[:section] = @settings['current_section']
68
- end
69
-
70
- ask_note = options[:ask] && !options[:editor] && args.count.positive? ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
54
+ section = if options[:section]
55
+ @wwid.guess_section(options[:section]) || options[:section].cap_first
56
+ else
57
+ @settings['current_section']
58
+ end
59
+
60
+ ask_note = if options[:ask] && !options[:editor] && args.count.positive?
61
+ Doing::Prompt.read_lines(prompt: 'Add a note')
62
+ else
63
+ ''
64
+ end
71
65
 
72
66
  if options[:editor]
73
67
  raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
data/bin/commands/on.rb CHANGED
@@ -23,10 +23,9 @@ command :on do |c|
23
23
  c.switch [:totals], default_value: false, negatable: false
24
24
 
25
25
  c.desc 'Sort tags by (name|time)'
26
- default = 'time'
27
- default = @settings['tag_sort'] || 'name'
26
+ default = @settings['tag_sort'].normalize_tag_sort || :name
28
27
  c.arg_name 'KEY'
29
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
28
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
30
29
 
31
30
  c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
32
31
  c.arg_name 'FORMAT'
@@ -58,7 +57,7 @@ command :on do |c|
58
57
  Doing.logger.debug('Interpreter:', message)
59
58
 
60
59
  options[:times] = true if options[:totals]
61
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
60
+ options[:sort_tags] = options[:tag_sort]
62
61
 
63
62
  Doing::Pager.page @wwid.list_date([start, finish], options[:section], options[:times], options[:output],
64
63
  { template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
@@ -30,10 +30,9 @@ command :recent do |c|
30
30
  c.switch [:totals], default_value: false, negatable: false
31
31
 
32
32
  c.desc 'Sort tags by (name|time)'
33
- default = 'time'
34
- default = @settings['tag_sort'] || 'name'
33
+ default = @settings['tag_sort'].normalize_tag_sort || :name
35
34
  c.arg_name 'KEY'
36
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
35
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
37
36
 
38
37
  c.desc 'Select from a menu of matching entries to perform additional operations'
39
38
  c.switch %i[i interactive], negatable: false, default_value: false
@@ -55,7 +54,7 @@ command :recent do |c|
55
54
  end
56
55
 
57
56
  options[:times] = true if options[:totals]
58
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
57
+ options[:sort_tags] = options[:tag_sort]
59
58
 
60
59
  template = @settings['templates']['recent'].deep_merge(@settings['templates']['default'])
61
60
  tags_color = template.key?('tags_color') ? template['tags_color'] : nil
data/bin/commands/redo.rb CHANGED
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # @@redo
2
- long_desc 'Shortcut for `doing undo -r`, reverses the last undo command. You cannot undo a redo'
4
+ desc 'Redo an undo command'
5
+ long_desc 'Shortcut for `doing undo -r`, reverses the last undo command. Specify a count to redo multiple undos'
3
6
  arg_name 'COUNT'
4
7
  command :redo do |c|
5
8
  c.desc 'Specify alternate doing file'
@@ -12,7 +15,8 @@ command :redo do |c|
12
15
  c.action do |_global, options, args|
13
16
  file = options[:file] || @wwid.doing_file
14
17
  count = args.empty? ? 1 : args[0].to_i
15
- raise InvalidArgument, "Invalid count specified for redo" unless count&.positive?
18
+ raise InvalidArgument, 'Invalid count specified for redo' unless count&.positive?
19
+
16
20
  if options[:interactive]
17
21
  Doing::Util::Backup.select_redo(file)
18
22
  else
@@ -20,54 +20,24 @@ command %i[reset begin] do |c|
20
20
  c.desc 'Change start date but do not remove @done (shortcut for --no-resume)'
21
21
  c.switch [:n]
22
22
 
23
- c.desc 'Reset last entry matching tag. Wildcards allowed (*, ?)'
24
- c.arg_name 'TAG'
25
- c.flag [:tag]
26
-
27
- c.desc 'Reset last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
28
- c.arg_name 'QUERY'
29
- c.flag [:search]
30
-
31
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
32
- c.arg_name 'QUERY'
33
- c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
34
-
35
- # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
36
- # c.switch [:fuzzy], default_value: false, negatable: false
37
-
38
- c.desc 'Force exact search string matching (case sensitive)'
39
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
40
-
41
- c.desc 'Reset items that *don\'t* match search/tag filters'
42
- c.switch [:not], default_value: false, negatable: false
43
-
44
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
45
- c.arg_name 'TYPE'
46
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
47
-
48
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
49
- c.arg_name 'BOOLEAN'
50
- c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
51
-
52
23
  c.desc 'Select from a menu of matching entries'
53
24
  c.switch %i[i interactive], negatable: false, default_value: false
54
25
 
26
+ add_options(:search, c)
27
+ add_options(:tag_filter, c)
28
+
55
29
  c.action do |global_options, options, args|
56
- if args.count > 0
30
+ if args.count.positive?
57
31
  reset_date = args.join(' ').chronify(guess: :begin)
58
32
  raise InvalidArgument, 'Invalid date string' unless reset_date
33
+
59
34
  else
60
35
  reset_date = Time.now
61
36
  end
62
37
 
63
38
  options[:fuzzy] = false
64
- if options[:section]
65
- options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first
66
- end
67
39
 
68
- options[:bool] = options[:bool].normalize_bool
69
-
70
- options[:case] = options[:case].normalize_case
40
+ options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first if options[:section]
71
41
 
72
42
  if options[:search]
73
43
  search = options[:search]
@@ -75,25 +45,22 @@ command %i[reset begin] do |c|
75
45
  options[:search] = search
76
46
  end
77
47
 
78
-
79
48
  items = @wwid.filter_items([], opt: options)
80
49
 
81
- if options[:interactive]
82
- last_entry = Doing::Prompt.choose_from_items(items, include_section: options[:section].nil?,
83
- menu: true,
84
- header: '',
85
- prompt: 'Select an entry to start/reset > ',
86
- multiple: false,
87
- sort: false,
88
- show_if_single: true)
89
- else
90
- last_entry = items.reverse.last
91
- end
50
+ last_entry = if options[:interactive]
51
+ Doing::Prompt.choose_from_items(items, include_section: options[:section].nil?,
52
+ menu: true,
53
+ header: '',
54
+ prompt: 'Select an entry to start/reset > ',
55
+ multiple: false,
56
+ sort: false,
57
+ show_if_single: true)
58
+ else
59
+ items.reverse.last
60
+ end
92
61
 
93
- unless last_entry
94
- Doing.logger.warn('Not found:', 'No entry matching parameters was found.')
95
- return
96
- end
62
+
63
+ raise NoResults, 'No entry matching parameters was found.' unless last_entry
97
64
 
98
65
  old_item = last_entry.clone
99
66
 
@@ -16,49 +16,18 @@ command :rotate do |c|
16
16
  c.arg_name 'SECTION_NAME'
17
17
  c.flag %i[s section], default_value: 'All'
18
18
 
19
- c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands'
20
- c.arg_name 'TAG'
21
- c.flag [:tag]
22
-
23
- c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
24
- c.arg_name 'BOOLEAN'
25
- c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
26
-
27
- c.desc 'Search filter'
28
- c.arg_name 'QUERY'
29
- c.flag [:search]
30
-
31
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
32
- c.arg_name 'QUERY'
33
- c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
34
-
35
- # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
36
- # c.switch [:fuzzy], default_value: false, negatable: false
37
-
38
- c.desc 'Force exact search string matching (case sensitive)'
39
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
40
-
41
- c.desc 'Rotate items that *don\'t* match search string or tag filter'
42
- c.switch [:not], default_value: false, negatable: false
43
-
44
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
45
- c.arg_name 'TYPE'
46
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
47
-
48
19
  c.desc 'Rotate entries older than date
49
20
  (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
50
21
  c.arg_name 'DATE_STRING'
51
22
  c.flag [:before]
52
23
 
53
- c.action do |_global_options, options, args|
54
- options[:fuzzy] = false
55
- if options[:section] && options[:section] !~ /^all$/i
56
- options[:section] = @wwid.guess_section(options[:section])
57
- end
24
+ add_options(:search, c)
25
+ add_options(:tag_filter, c)
58
26
 
59
- options[:bool] = options[:bool].normalize_bool
27
+ c.action do |_global_options, options, _args|
28
+ options[:fuzzy] = false
60
29
 
61
- options[:case] = options[:case].normalize_case
30
+ options[:section] = @wwid.guess_section(options[:section]) if options[:section] && options[:section] !~ /^all$/i
62
31
 
63
32
  search = nil
64
33
 
@@ -14,9 +14,13 @@ sbtrkt fuzzy-match Items that match s*b*t*r*k*t
14
14
 
15
15
  !fire inverse-exact-match Items that do not include fire'
16
16
  command :select do |c|
17
- c.example 'doing select', desc: 'Select from all entries. A menu of available actions will be presented after confirming the selection.'
18
- c.example 'doing select --editor', desc: 'Select entries from a menu and batch edit them in your default editor'
19
- c.example 'doing select --after "yesterday 12pm" --tag project1', desc: 'Display a menu of entries created after noon yesterday, add @project1 to selected entries'
17
+ c.example 'doing select',
18
+ desc: 'Select from all entries. A menu of actions will be presented after confirming the selection.'
19
+ c.example 'doing select --editor',
20
+ desc: 'Select entries from a menu and batch edit them in your default editor'
21
+ c.example 'doing select --after "yesterday 12pm" --tag project1',
22
+ desc: 'Display a menu of entries created after noon yesterday, add @project1 to selected entries'
23
+
20
24
  c.desc 'Select from a specific section'
21
25
  c.arg_name 'SECTION'
22
26
  c.flag %i[s section]
@@ -38,48 +42,21 @@ command :select do |c|
38
42
  c.arg_name 'SECTION'
39
43
  c.flag %i[m move]
40
44
 
41
- c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote, e.g. `--query "\'search"'
45
+ c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote,
46
+ e.g. `--query "\'search"'
42
47
  c.arg_name 'QUERY'
43
48
  c.flag %i[q query]
44
49
 
45
- c.desc 'Select from entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
46
- c.arg_name 'QUERY'
47
- c.flag [:search]
48
-
49
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
50
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50").
51
+ May be used multiple times, combined with --bool'
50
52
  c.arg_name 'QUERY'
51
53
  c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
52
54
 
53
- c.desc 'Select from entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
54
- c.arg_name 'DATE_STRING'
55
- c.flag [:before], type: DateBeginString
56
-
57
- c.desc 'Select from entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
58
- c.arg_name 'DATE_STRING'
59
- c.flag [:after], type: DateEndString
60
-
61
- c.desc %(
62
- Date range to show, or a single day to filter date on.
63
- Date range argument should be quoted. Date specifications can be natural language.
64
- To specify a range, use "to" or "through": `doing select --from "monday 8am to friday 5pm"`.
65
-
66
- If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
67
- by time of day.
68
- )
69
- c.arg_name 'DATE_OR_RANGE'
70
- c.flag [:from], type: DateRangeString
71
-
72
- c.desc 'Force exact search string matching (case sensitive)'
73
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
74
-
75
55
  c.desc 'Select items that *don\'t* match search/tag filters'
76
56
  c.switch [:not], default_value: false, negatable: false
77
57
 
78
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
79
- c.arg_name 'TYPE'
80
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
81
-
82
- 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'
58
+ c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically.
59
+ Test with `--output doing` to preview matches'
83
60
  c.switch %i[menu], negatable: true, default_value: true
84
61
 
85
62
  c.desc 'Cancel selected items (add @done without timestamp)'
@@ -108,15 +85,20 @@ command :select do |c|
108
85
  c.arg_name 'FORMAT'
109
86
  c.flag %i[o output]
110
87
 
111
- c.desc "Copy selection as a new entry with current time and no @done tag. Only works with single selections. Can be combined with --editor."
88
+ c.desc 'Copy selection as a new entry with current time and no @done tag.
89
+ Only works with single selections. Can be combined with --editor.'
112
90
  c.switch %i[again resume], negatable: false, default_value: false
113
91
 
114
- c.action do |_global_options, options, args|
115
- raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
92
+ add_options(:search, c)
93
+ add_options(:date_filter, c)
116
94
 
117
- raise InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
95
+ c.action do |_global_options, options, _args|
96
+ if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
97
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}")
98
+
99
+ end
118
100
 
119
- options[:case] = options[:case].normalize_case
101
+ raise InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
120
102
 
121
103
  @wwid.interactive(options) # hooked
122
104
  end
data/bin/commands/show.rb CHANGED
@@ -18,69 +18,20 @@ command :show do |c|
18
18
  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.'
19
19
  c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
20
20
 
21
- 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'
22
- c.arg_name 'TAG'
23
- c.flag [:tag], type: TagArray
24
-
25
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
26
- c.arg_name 'QUERY'
27
- c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
28
-
29
- c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans'
30
- c.arg_name 'BOOLEAN'
31
- c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
32
-
33
21
  c.desc 'Max count to show'
34
22
  c.arg_name 'MAX'
35
23
  c.flag %i[c count], default_value: 0, must_match: /^\d+$/, type: Integer
36
24
 
37
25
  c.desc 'Age (oldest|newest)'
38
26
  c.arg_name 'AGE'
39
- c.flag %i[a age], default_value: 'newest'
40
-
41
- c.desc 'Show entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
42
- c.arg_name 'DATE_STRING'
43
- c.flag [:before], type: DateBeginString
44
-
45
- c.desc 'Show entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
46
- c.arg_name 'DATE_STRING'
47
- c.flag [:after], type: DateEndString
48
-
49
- c.desc %(
50
- Date range to show, or a single day to filter date on.
51
- Date range argument should be quoted. Date specifications can be natural language.
52
- To specify a range, use "to" or "through": `doing show --from "monday 8am to friday 5pm"`.
53
-
54
- If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
55
- by time of day.
56
- )
57
-
58
- c.arg_name 'DATE_OR_RANGE'
59
- c.flag [:from], type: DateRangeString
60
-
61
- c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
62
- c.arg_name 'QUERY'
63
- c.flag [:search]
27
+ c.flag %i[a age], default_value: :newest, type: AgeSymbol
64
28
 
65
29
  c.desc "Highlight search matches in output. Only affects command line output"
66
30
  c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
67
31
 
68
- # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
69
- # c.switch [:fuzzy], default_value: false, negatable: false
70
-
71
- c.desc 'Force exact search string matching (case sensitive)'
72
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
73
-
74
- c.desc 'Show items that *don\'t* match search/tag/date filters'
75
- c.switch [:not], default_value: false, negatable: false
76
-
77
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
78
- c.arg_name 'TYPE'
79
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
80
-
81
32
  c.desc 'Sort order (asc/desc)'
82
33
  c.arg_name 'ORDER'
83
- c.flag %i[s sort], must_match: REGEX_SORT_ORDER, default_value: 'asc'
34
+ c.flag %i[s sort], must_match: REGEX_SORT_ORDER, default_value: :asc, type: OrderSymbol
84
35
 
85
36
  c.desc 'Show time intervals on @done tasks'
86
37
  c.switch %i[t times], default_value: true, negatable: true
@@ -92,14 +43,13 @@ command :show do |c|
92
43
  c.switch [:totals], default_value: false, negatable: false
93
44
 
94
45
  c.desc 'Sort tags by (name|time)'
95
- default = 'time'
96
- default = @settings['tag_sort'] || 'name'
46
+ default = @settings['tag_sort'].normalize_tag_sort || :name
97
47
  c.arg_name 'KEY'
98
- c.flag [:tag_sort], must_match: /^(?:name|time)/i, default_value: default
48
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
99
49
 
100
50
  c.desc 'Tag sort direction (asc|desc)'
101
51
  c.arg_name 'DIRECTION'
102
- c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
52
+ c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: :asc, type: OrderSymbol
103
53
 
104
54
  c.desc 'Only show items with recorded time intervals'
105
55
  c.switch [:only_timed], default_value: false, negatable: false
@@ -121,9 +71,17 @@ command :show do |c|
121
71
  c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
122
72
  c.arg_name 'FORMAT'
123
73
  c.flag %i[o output]
74
+
75
+ add_options(:search, c)
76
+ add_options(:tag_filter, c)
77
+ add_options(:date_filter, c)
78
+
124
79
  c.action do |global_options, options, args|
125
80
  options[:fuzzy] = false
126
- raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
81
+ if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
82
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}")
83
+
84
+ end
127
85
 
128
86
  tag_filter = false
129
87
  tags = []
@@ -142,7 +100,7 @@ command :show do |c|
142
100
  else
143
101
  begin
144
102
  section = @wwid.guess_section(args[0])
145
- rescue WrongCommand => exception
103
+ rescue WrongCommand
146
104
  cmd = commands[:view]
147
105
  action = cmd.send(:get_action, nil)
148
106
  return action.call(global_options, options, args)
@@ -155,8 +113,8 @@ command :show do |c|
155
113
  if args.length.positive?
156
114
  args.each do |arg|
157
115
  arg.split(/,/).each do |tag|
158
- tags.push(tag.strip.sub(/^@/, ''))
159
- end
116
+ tags.push(tag.strip.sub(/^@/, ''))
117
+ end
160
118
  end
161
119
  end
162
120
  else
@@ -169,13 +127,11 @@ command :show do |c|
169
127
  options[:times] = true if options[:totals]
170
128
 
171
129
  template = @settings['templates'][options[:config_template]].deep_merge({
172
- 'wrap_width' => @settings['wrap_width'] || 0,
173
- 'date_format' => @settings['default_date_format'],
174
- 'order' => @settings['order'] || 'asc',
175
- 'tags_color' => @settings['tags_color']
176
- })
177
-
178
- options[:case] = options[:case].normalize_case
130
+ 'wrap_width' => @settings['wrap_width'] || 0,
131
+ 'date_format' => @settings['default_date_format'],
132
+ 'order' => @settings['order']&.normalize_order || :asc,
133
+ 'tags_color' => @settings['tags_color']
134
+ })
179
135
 
180
136
  if options[:search]
181
137
  search = options[:search]
@@ -188,7 +144,7 @@ command :show do |c|
188
144
  if tags.good?
189
145
  tag_filter = {
190
146
  'tags' => tags,
191
- 'bool' => options[:bool].normalize_bool
147
+ 'bool' => options[:bool]
192
148
  }
193
149
  end
194
150
 
@@ -198,32 +154,30 @@ command :show do |c|
198
154
  items = @wwid.filter_items([], opt: options)
199
155
 
200
156
  if options[:menu]
157
+ Doing.logger.benchmark(:menu, :start)
201
158
  tag = @wwid.choose_tag(section, items: items, include_all: true)
202
159
  raise UserCancelled unless tag
203
160
 
204
- # options[:bool] = :and unless tags.empty?
205
-
206
161
  tags = tag.split(/ +/).map { |t| t.strip.sub(/^@?/, '') } if tag =~ /^@/
207
162
  if tags.good?
208
163
  tag_filter = {
209
164
  'tags' => tags,
210
- 'bool' => options[:bool].normalize_bool
165
+ 'bool' => options[:bool]
211
166
  }
212
167
  options[:tag_filter] = tag_filter
213
168
  end
169
+ Doing.logger.benchmark(:menu, :finish)
214
170
  end
215
171
 
216
172
  options[:age] ||= :newest
217
173
 
218
174
  opt = options.clone
219
- opt[:age] = options[:age].normalize_age(:newest) if options[:age]
220
- opt[:sort_tags] = options[:tag_sort] =~ /^n/i
175
+ opt[:sort_tags] = options[:tag_sort]
221
176
  opt[:count] = options[:count].to_i
222
177
  opt[:highlight] = true
223
178
  opt[:hilite] = options[:hilite]
224
- opt[:order] = options[:sort].normalize_order
179
+ opt[:order] = options[:sort]
225
180
  opt[:tag] = nil
226
- opt[:tag_order] = options[:tag_order].normalize_order
227
181
  opt[:tags_color] = template['tags_color']
228
182
 
229
183
  Doing::Pager.page @wwid.list_section(opt, items: items)
@@ -21,10 +21,9 @@ command :since do |c|
21
21
  c.switch [:totals], default_value: false, negatable: false
22
22
 
23
23
  c.desc 'Sort tags by (name|time)'
24
- default = 'time'
25
- default = @settings['tag_sort'] || 'name'
24
+ default = @settings['tag_sort'].normalize_tag_sort || :name
26
25
  c.arg_name 'KEY'
27
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
26
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
28
27
 
29
28
  c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
30
29
  c.arg_name 'FORMAT'
@@ -56,7 +55,7 @@ command :since do |c|
56
55
  Doing.logger.debug('Interpreter:', "date interpreted as #{start} through the current time")
57
56
 
58
57
  options[:times] = true if options[:totals]
59
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
58
+ options[:sort_tags] = options[:tag_sort]
60
59
 
61
60
  Doing::Pager.page @wwid.list_date([start, finish], options[:section], options[:times], options[:output],
62
61
  { template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
data/bin/commands/tag.rb CHANGED
@@ -55,39 +55,12 @@ command :tag do |c|
55
55
  c.desc 'Autotag entries based on autotag configuration in ~/.config/doing/config.yml'
56
56
  c.switch %i[a autotag], negatable: false, default_value: false
57
57
 
58
- c.desc 'Tag the last X entries containing TAG.
59
- Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
60
- c.arg_name 'TAG'
61
- c.flag [:tag], type: TagArray
62
-
63
- c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
64
- c.arg_name 'QUERY'
65
- c.flag [:search]
66
-
67
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
68
- c.arg_name 'QUERY'
69
- c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
70
-
71
- # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
72
- # c.switch [:fuzzy], default_value: false, negatable: false
73
-
74
- c.desc 'Force exact search string matching (case sensitive)'
75
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
76
-
77
- c.desc 'Tag items that *don\'t* match search/tag filters'
78
- c.switch [:not], default_value: false, negatable: false
79
-
80
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
81
- c.arg_name 'TYPE'
82
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
83
-
84
- c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
85
- c.arg_name 'BOOLEAN'
86
- c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
87
-
88
58
  c.desc 'Select item(s) to tag from a menu of matching entries'
89
59
  c.switch %i[i interactive], negatable: false, default_value: false
90
60
 
61
+ add_options(:search, c)
62
+ add_options(:tag_filter, c)
63
+
91
64
  c.action do |_global_options, options, args|
92
65
  options[:fuzzy] = false
93
66
  # raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
@@ -130,9 +103,6 @@ command :tag do |c|
130
103
  count = options[:count].to_i
131
104
  end
132
105
 
133
- options[:case] ||= :smart
134
- options[:case] = options[:case].normalize_case
135
-
136
106
  if options[:search]
137
107
  search = options[:search]
138
108
  search.sub!(/^'?/, "'") if options[:exact]
@@ -143,7 +113,7 @@ command :tag do |c|
143
113
  options[:section] = section
144
114
  options[:tag] = search_tags
145
115
  options[:tags] = tags
146
- options[:tag_bool] = options[:bool].normalize_bool
116
+ options[:tag_bool] = options[:bool]
147
117
 
148
118
  if count.zero? && !options[:force]
149
119
  matches = @wwid.filter_items([], opt: options).count