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/tags.rb CHANGED
@@ -18,41 +18,14 @@ command :tags do |c|
18
18
 
19
19
  c.desc 'Sort order (asc/desc)'
20
20
  c.arg_name 'ORDER'
21
- c.flag %i[o order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
22
-
23
- c.desc 'Get tags for entries matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
24
- c.arg_name 'TAG'
25
- c.flag [:tag]
26
-
27
- c.desc 'Get tags for items matching search. Surround with
28
- slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
29
- c.arg_name 'QUERY'
30
- c.flag [:search]
31
-
32
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
33
- c.arg_name 'QUERY'
34
- c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
35
-
36
- # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
37
- # c.switch [:fuzzy], default_value: false, negatable: false
38
-
39
- c.desc 'Force exact search string matching (case sensitive)'
40
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
41
-
42
- c.desc 'Get tags from items that *don\'t* match search/tag filters'
43
- c.switch [:not], default_value: false, negatable: false
44
-
45
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
46
- c.arg_name 'TYPE'
47
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
48
-
49
- c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans'
50
- c.arg_name 'BOOLEAN'
51
- c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
21
+ c.flag %i[o order], must_match: REGEX_SORT_ORDER, default_value: :asc, type: OrderSymbol
52
22
 
53
23
  c.desc 'Select items to scan from a menu of matching entries'
54
24
  c.switch %i[i interactive], negatable: false, default_value: false
55
25
 
26
+ add_options(:search, c)
27
+ add_options(:tag_filter, c)
28
+
56
29
  c.action do |_global, options, args|
57
30
  section = @wwid.guess_section(options[:section]) || options[:section].cap_first
58
31
  options[:count] = args.count.positive? ? args[0].to_i : 0
@@ -78,7 +51,7 @@ command :tags do |c|
78
51
  tags = tags.sort_by { |tag, count| count }
79
52
  end
80
53
 
81
- tags.reverse! if options[:order].normalize_order == 'desc'
54
+ tags.reverse! if options[:order] == :desc
82
55
 
83
56
  if options[:counts]
84
57
  tags.each { |t, c| puts "#{t} (#{c})" }
@@ -22,10 +22,9 @@ command :today do |c|
22
22
  c.switch [:totals], default_value: false, negatable: false
23
23
 
24
24
  c.desc 'Sort tags by (name|time)'
25
- default = 'time'
26
- default = @settings['tag_sort'] || 'name'
25
+ default = @settings['tag_sort'].normalize_tag_sort || :name
27
26
  c.arg_name 'KEY'
28
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
27
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
29
28
 
30
29
  c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
31
30
  c.arg_name 'FORMAT'
@@ -57,7 +56,7 @@ command :today do |c|
57
56
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
58
57
 
59
58
  options[:times] = true if options[:totals]
60
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
59
+ options[:sort_tags] = options[:tag_sort]
61
60
  filter_options = %i[after before duration from section sort_tags totals template config_template].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
62
61
 
63
62
  Doing::Pager.page @wwid.today(options[:times], options[:output], filter_options).chomp
data/bin/commands/view.rb CHANGED
@@ -21,7 +21,7 @@ command :view do |c|
21
21
 
22
22
  c.desc 'Age (oldest|newest)'
23
23
  c.arg_name 'AGE'
24
- c.flag %i[age], default_value: 'newest'
24
+ c.flag %i[age], default_value: :newest, type: AgeSymbol
25
25
 
26
26
  c.desc 'Show time intervals on @done tasks'
27
27
  c.switch %i[t times], default_value: true, negatable: true
@@ -35,64 +35,16 @@ command :view do |c|
35
35
  c.desc 'Include colors in output'
36
36
  c.switch [:color], default_value: true, negatable: true
37
37
 
38
- c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?)'
39
- c.arg_name 'TAG'
40
- c.flag [:tag]
41
-
42
- c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
43
- c.arg_name 'QUERY'
44
- c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
45
-
46
- c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans'
47
- c.arg_name 'BOOLEAN'
48
- c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
49
-
50
- c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
51
- c.arg_name 'QUERY'
52
- c.flag [:search]
53
-
54
38
  c.desc "Highlight search matches in output. Only affects command line output"
55
39
  c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
56
40
 
57
- # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
58
- # c.switch [:fuzzy], default_value: false, negatable: false
59
-
60
- c.desc 'Force exact search string matching (case sensitive)'
61
- c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
62
-
63
- c.desc 'Show items that *don\'t* match search string'
64
- c.switch [:not], default_value: false, negatable: false
65
-
66
- c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
67
- c.arg_name 'TYPE'
68
- c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
69
-
70
41
  c.desc 'Sort tags by (name|time)'
71
42
  c.arg_name 'KEY'
72
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i
43
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, type: TagSortSymbol
73
44
 
74
45
  c.desc 'Tag sort direction (asc|desc)'
75
46
  c.arg_name 'DIRECTION'
76
- c.flag [:tag_order], must_match: REGEX_SORT_ORDER
77
-
78
- c.desc 'View entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
79
- c.arg_name 'DATE_STRING'
80
- c.flag [:before], type: DateBeginString
81
-
82
- c.desc 'View entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
83
- c.arg_name 'DATE_STRING'
84
- c.flag [:after], type: DateEndString
85
-
86
- c.desc %(
87
- Date range to show, or a single day to filter date on.
88
- Date range argument should be quoted. Date specifications can be natural language.
89
- To specify a range, use "to" or "through": `doing view --from "monday 8am to friday 5pm" view_name`.
90
-
91
- If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
92
- by time of day.
93
- )
94
- c.arg_name 'DATE_OR_RANGE'
95
- c.flag [:from], type: DateRangeString
47
+ c.flag [:tag_order], must_match: REGEX_SORT_ORDER, type: OrderSymbol
96
48
 
97
49
  c.desc 'Only show items with recorded time intervals (override view settings)'
98
50
  c.switch [:only_timed], default_value: false, negatable: false
@@ -100,9 +52,16 @@ command :view do |c|
100
52
  c.desc 'Select from a menu of matching entries to perform additional operations'
101
53
  c.switch %i[i interactive], negatable: false, default_value: false
102
54
 
55
+ add_options(:search, c)
56
+ add_options(:tag_filter, c)
57
+ add_options(:date_filter, c)
58
+
103
59
  c.action do |global_options, options, args|
104
60
  options[:fuzzy] = false
105
- raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
61
+ if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
62
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}")
63
+
64
+ end
106
65
 
107
66
  raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
108
67
 
@@ -111,20 +70,20 @@ command :view do |c|
111
70
  else
112
71
  begin
113
72
  @wwid.guess_view(args[0])
114
- rescue WrongCommand => exception
73
+ rescue WrongCommand
115
74
  cmd = commands[:show]
116
- options[:sort] = 'asc'
117
- options[:tag_order] = 'asc'
75
+ options[:sort] = :asc
76
+ options[:tag_order] = :asc
118
77
  action = cmd.send(:get_action, nil)
119
78
  return action.call(global_options, options, args)
120
79
  end
121
80
  end
122
81
 
123
- if options[:section]
124
- section = @wwid.guess_section(options[:section]) || options[:section].cap_first
125
- else
126
- section = @settings['current_section']
127
- end
82
+ section = if options[:section]
83
+ @wwid.guess_section(options[:section]) || options[:section].cap_first
84
+ else
85
+ @settings['current_section']
86
+ end
128
87
 
129
88
  view = @wwid.get_view(title)
130
89
 
@@ -143,7 +102,7 @@ command :view do |c|
143
102
  tag_filter = false
144
103
  if options[:tag]
145
104
  tag_filter = { 'tags' => [], 'bool' => 'OR' }
146
- bool = options[:bool].normalize_bool
105
+ bool = options[:bool]
147
106
  tag_filter['bool'] = bool
148
107
  tag_filter['tags'] = if bool == :pattern
149
108
  options[:tag]
@@ -164,41 +123,45 @@ command :view do |c|
164
123
  # If the -o/--output flag was specified, override any default in the view template
165
124
  options[:output] ||= view.key?('output_format') ? view['output_format'] : 'template'
166
125
 
167
- count = options[:count] ? options[:count] : view.key?('count') ? view['count'] : 10
126
+ count = if options[:count]
127
+ options[:count]
128
+ elsif view.key?('count')
129
+ view['count']
130
+ else
131
+ 10
132
+ end
168
133
 
169
134
  section = if options[:section]
170
135
  section
171
136
  else
172
137
  view['section'] || @settings['current_section']
173
138
  end
174
- order = view['order']&.normalize_order || 'asc'
139
+ order = if view.key?('order')
140
+ view['order'].normalize_order
141
+ else
142
+ :asc
143
+ end
175
144
 
176
145
  totals = if options[:totals]
177
146
  true
178
147
  else
179
148
  view['totals'] || false
180
149
  end
181
- tag_order = if options[:tag_order]
182
- options[:tag_order].normalize_order
183
- else
184
- view['tag_order']&.normalize_order || 'asc'
185
- end
150
+ tag_order = options[:tag_order] || view['tag_order']&.normalize_order || :asc
186
151
 
187
152
  options[:times] = true if totals
188
153
  output_format = options[:output]&.downcase || 'template'
189
154
 
190
155
  options[:sort_tags] = if options[:tag_sort]
191
- options[:tag_sort] =~ /^n/i ? true : false
156
+ options[:tag_sort]
192
157
  elsif view.key?('tag_sort')
193
- view['tag_sort'] =~ /^n/i ? true : false
158
+ view['tag_sort'].normalize_tag_sort
194
159
  else
195
160
  false
196
161
  end
197
162
 
198
163
  %w[before after from duration].each { |k| options[k.to_sym] = view[k] if view.key?(k) && !options[k.to_sym] }
199
164
 
200
- options[:case] = options[:case].normalize_case
201
-
202
165
  search = nil
203
166
 
204
167
  if options[:search]
@@ -209,7 +172,7 @@ command :view do |c|
209
172
  options[:age] ||= :newest
210
173
 
211
174
  opts = options.clone
212
- opts[:age] = options[:age].normalize_age(:newest)
175
+ opts[:age] = options[:age]
213
176
  opts[:count] = count
214
177
  opts[:format] = date_format
215
178
  opts[:highlight] = options[:color]
@@ -33,9 +33,9 @@ command :yesterday do |c|
33
33
  c.switch [:totals], default_value: false, negatable: false
34
34
 
35
35
  c.desc 'Sort tags by (name|time)'
36
- default = @settings['tag_sort'] || 'name'
36
+ default = @settings['tag_sort'].normalize_tag_sort || :name
37
37
  c.arg_name 'KEY'
38
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
38
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
39
39
 
40
40
  c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
41
41
  c.arg_name 'TIME_STRING'
@@ -51,12 +51,12 @@ command :yesterday do |c|
51
51
 
52
52
  c.desc 'Tag sort direction (asc|desc)'
53
53
  c.arg_name 'DIRECTION'
54
- c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
54
+ c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: :asc, type: OrderSymbol
55
55
 
56
56
  c.action do |_global_options, options, _args|
57
57
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
58
58
 
59
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
59
+ options[:sort_tags] = options[:tag_sort]
60
60
 
61
61
  if options[:from]
62
62
  options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
@@ -65,7 +65,6 @@ command :yesterday do |c|
65
65
  end
66
66
 
67
67
  opt = options.clone
68
- opt[:tag_order] = options[:tag_order].normalize_order
69
68
  opt[:order] = @settings.dig('templates', options[:config_template], 'order')
70
69
 
71
70
  Doing::Pager.page @wwid.yesterday(options[:section], options[:times], options[:output], opt).chomp
data/bin/doing CHANGED
@@ -38,11 +38,11 @@ env_log_level = nil
38
38
  if ENV['DOING_LOG_LEVEL'] || ENV['DOING_DEBUG'] || ENV['DOING_QUIET'] || ENV['DOING_VERBOSE'] || ENV['DOING_PLUGIN_DEBUG']
39
39
  env_log_level = true
40
40
  # Quiet always wins
41
- if ENV['DOING_QUIET'] && ENV['DOING_QUIET'].truthy?
41
+ if ENV['DOING_QUIET']&.truthy?
42
42
  Doing.logger.log_level = :error
43
- elsif (ENV['DOING_PLUGIN_DEBUG'] && ENV['DOING_PLUGIN_DEBUG'].truthy?)
43
+ elsif ENV['DOING_PLUGIN_DEBUG']&.truthy?
44
44
  Doing.logger.log_level = :debug
45
- elsif (ENV['DOING_DEBUG'] && ENV['DOING_DEBUG'].truthy?)
45
+ elsif ENV['DOING_DEBUG']&.truthy?
46
46
  Doing.logger.log_level = :debug
47
47
  elsif ENV['DOING_LOG_LEVEL']
48
48
  Doing.logger.log_level = ENV['DOING_LOG_LEVEL']
@@ -51,11 +51,8 @@ end
51
51
 
52
52
  Doing.logger.benchmark(:total, :start)
53
53
 
54
- if ENV['DOING_CONFIG']
55
- Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
56
- end
57
-
58
54
  Doing.logger.benchmark(:configure, :start)
55
+ Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true }) if ENV['DOING_CONFIG']
59
56
  @config = Doing.config
60
57
  Doing.logger.benchmark(:configure, :finish)
61
58
 
@@ -63,8 +60,30 @@ Doing.logger.benchmark(:configure, :finish)
63
60
  @settings = @config.settings
64
61
  @wwid.config = @settings
65
62
 
66
- if @settings.dig('plugins', 'command_path')
67
- commands_from File.expand_path(@settings.dig('plugins', 'command_path'))
63
+ commands_from File.expand_path(@settings.dig('plugins', 'command_path')) if @settings.dig('plugins', 'command_path')
64
+
65
+ accept BooleanSymbol do |value|
66
+ value.normalize_bool(:pattern)
67
+ end
68
+
69
+ accept CaseSymbol do |value|
70
+ value.normalize_case(@config.fetch('search', 'case', :smart))
71
+ end
72
+
73
+ accept AgeSymbol do |value|
74
+ value.normalize_age(:newest)
75
+ end
76
+
77
+ accept OrderSymbol do |value|
78
+ value.normalize_order(:asc)
79
+ end
80
+
81
+ accept MatchingSymbol do |value|
82
+ value.normalize_matching(:pattern)
83
+ end
84
+
85
+ accept TagSortSymbol do |value|
86
+ value.normalize_tag_sort(@config.fetch('tag_sort', :name))
68
87
  end
69
88
 
70
89
  accept TemplateName do |value|
@@ -119,7 +138,123 @@ accept DateIntervalString do |value|
119
138
  end
120
139
 
121
140
  accept TagArray do |value|
122
- value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '')}.map(&:strip)
141
+ value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '') }.map(&:strip)
142
+ end
143
+
144
+ ##
145
+ ## Add presets of flags and switches to a command.
146
+ ##
147
+ ## :add_entry => --noauto, --note, --ask, --editor, --back
148
+ ##
149
+ ## :search => --search, --case, --exact
150
+ ##
151
+ ## :tag_filter => --tag, --bool, --not, --val
152
+ ##
153
+ ## :date_filter => --before, --after, --from
154
+ ##
155
+ ## @param type [Symbol] The type
156
+ ## @param cmd The GLI command to which the options will be added
157
+ ##
158
+ def add_options(type, cmd)
159
+ cmd_name = cmd.name.to_s
160
+ action = case cmd_name
161
+ when /again/
162
+ 'Repeat'
163
+ when /grep/
164
+ 'Search'
165
+ when /mark/
166
+ 'Flag'
167
+ when /(last|tags|view)/
168
+ 'Show'
169
+ else
170
+ cmd_name.capitalize
171
+ end
172
+
173
+ case type
174
+ when :add_entry
175
+ cmd.desc 'Exclude auto tags and default tags'
176
+ cmd.switch %i[X noauto], default_value: false, negatable: false
177
+
178
+ cmd.desc 'Include a note'
179
+ cmd.arg_name 'TEXT'
180
+ cmd.flag %i[n note]
181
+
182
+ cmd.desc 'Prompt for note via multi-line input'
183
+ cmd.switch %i[ask], negatable: false, default_value: false
184
+
185
+ cmd.desc "Edit entry with #{Doing::Util.default_editor}"
186
+ cmd.switch %i[e editor], negatable: false, default_value: false
187
+
188
+ cmd.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
189
+ cmd.arg_name 'DATE_STRING'
190
+ cmd.flag %i[b back started], type: DateBeginString
191
+ when :search
192
+ cmd.desc 'Filter entries using a search query, surround with slashes for regex (e.g. "/query.*/"),
193
+ start with single quote for exact match ("\'query")'
194
+ cmd.arg_name 'QUERY'
195
+ cmd.flag [:search]
196
+
197
+ cmd.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
198
+ cmd.arg_name 'TYPE'
199
+ cmd.flag [:case], must_match: REGEX_CASE,
200
+ default_value: @settings.dig('search', 'case').normalize_case,
201
+ type: CaseSymbol
202
+
203
+ cmd.desc 'Force exact search string matching (case sensitive)'
204
+ cmd.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
205
+ when :tag_filter
206
+ cmd.desc 'Filter entries by tag. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
207
+ cmd.arg_name 'TAG'
208
+ cmd.flag [:tag], type: TagArray
209
+
210
+ cmd.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50").
211
+ May be used multiple times, combined with --bool'
212
+ cmd.arg_name 'QUERY'
213
+ cmd.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
214
+
215
+ cmd.desc "#{action} items that *don't* match search/tag filters"
216
+ cmd.switch [:not], default_value: false, negatable: false
217
+
218
+ cmd.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans'
219
+ cmd.arg_name 'BOOLEAN'
220
+ cmd.flag [:bool], must_match: REGEX_BOOL,
221
+ default_value: :pattern,
222
+ type: BooleanSymbol
223
+ when :date_filter
224
+ if action =~ /Archive/
225
+ cmd.desc 'Archive entries older than date (natural language).'
226
+ else
227
+ cmd.desc "#{action} entries older than date (natural language). If this is only a time (8am, 1:30pm, 15:00), all
228
+ dates will be included, but entries will be filtered by time of day"
229
+ end
230
+ cmd.arg_name 'DATE_STRING'
231
+ cmd.flag [:before], type: DateBeginString
232
+
233
+ if action =~ /Archive/
234
+ cmd.desc 'Archive entries newer than date (natural language).'
235
+ else
236
+ cmd.desc "#{action} entries newer than date (natural language). If this is only a time (8am, 1:30pm, 15:00), all
237
+ dates will be included, but entries will be filtered by time of day"
238
+ end
239
+ cmd.arg_name 'DATE_STRING'
240
+ cmd.flag [:after], type: DateEndString
241
+
242
+ if action =~ /Archive/
243
+ cmd.desc %(
244
+ Date range (natural language) to archive: `doing archive --from "1/1/21 to 12/31/21"`.
245
+ )
246
+ else
247
+ cmd.desc %(
248
+ Date range (natural language) to #{action.downcase}, or a single day to filter on.
249
+ To specify a range, use "to": `doing #{cmd_name} --from "monday 8am to friday 5pm"`.
250
+
251
+ If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
252
+ by time of day.
253
+ )
254
+ end
255
+ cmd.arg_name 'DATE_OR_RANGE'
256
+ cmd.flag [:from], type: DateRangeString
257
+ end
123
258
  end
124
259
 
125
260
  program_desc 'A CLI for a What Was I Doing system'
@@ -192,7 +327,6 @@ add_commands(%w[commands_accepting install_fzf])
192
327
  ## Optional commands
193
328
  add_commands(%w[again cancel flag meanwhile reset tags today yesterday since add_section tag_dir colors completion plugins sections template views changes])
194
329
 
195
-
196
330
  pre do |global, _command, _options, _args|
197
331
  # global[:pager] ||= @settings['paginate']
198
332
  Doing::Pager.paginate = global[:pager]
@@ -232,6 +366,7 @@ post do |global, _command, _options, _args|
232
366
  end
233
367
 
234
368
  around do |global, command, options, arguments, code|
369
+ Doing.logger.benchmark("command_#{command.name.to_s}".to_sym, :start)
235
370
  # Doing.logger.debug('Pager:', "Global: #{global[:pager]}, Config: #{@settings['paginate']}, Pager: #{Doing::Pager.paginate}")
236
371
  if env_log_level.nil?
237
372
  Doing.logger.adjust_verbosity(global)
@@ -242,10 +377,10 @@ around do |global, command, options, arguments, code|
242
377
  end
243
378
 
244
379
  if global[:yes]
245
- Doing::Prompt.force_answer = true
380
+ Doing::Prompt.force_answer = :yes
246
381
  Doing.config.force_answer = true
247
382
  elsif global[:no]
248
- Doing::Prompt.force_answer = false
383
+ Doing::Prompt.force_answer = :no
249
384
  Doing.config.force_answer = false
250
385
  else
251
386
  Doing::Prompt.default_answer = if $stdout.isatty
@@ -281,6 +416,8 @@ around do |global, command, options, arguments, code|
281
416
  global[:wwid] = @wwid
282
417
 
283
418
  code.call
419
+
420
+ Doing.logger.benchmark("command_#{command.name.to_s}".to_sym, :finish)
284
421
  end
285
422
 
286
423
  exit run(ARGV)