doing 2.1.40 → 2.1.41

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile.lock +1 -1
  5. data/Rakefile +4 -4
  6. data/bin/commands/changes.rb +1 -1
  7. data/bin/commands/tag_dir.rb +49 -15
  8. data/{Dockerfile → docker/Dockerfile} +3 -1
  9. data/{Dockerfile-2.6 → docker/Dockerfile-2.6} +2 -2
  10. data/{Dockerfile-2.7 → docker/Dockerfile-2.7} +2 -2
  11. data/{Dockerfile-3.0 → docker/Dockerfile-3.0} +2 -2
  12. data/{bash_profile → docker/bash_profile} +0 -0
  13. data/{inputrc → docker/inputrc} +0 -0
  14. data/docs/doc/Array.html +84 -2
  15. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  16. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  17. data/docs/doc/BooleanTermParser/Query.html +1 -1
  18. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  19. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  20. data/docs/doc/BooleanTermParser.html +1 -1
  21. data/docs/doc/Doing/ArrayNestedHash.html +198 -0
  22. data/docs/doc/Doing/ArrayTags.html +424 -0
  23. data/docs/doc/Doing/CSVExport.html +266 -0
  24. data/docs/doc/Doing/CalendarImport.html +232 -0
  25. data/docs/doc/Doing/Change.html +617 -0
  26. data/docs/doc/Doing/Changes.html +468 -0
  27. data/docs/doc/Doing/ChronifyArray.html +347 -0
  28. data/docs/doc/Doing/ChronifyNumeric.html +271 -0
  29. data/docs/doc/Doing/ChronifyString.html +682 -0
  30. data/docs/doc/Doing/Color.html +2 -2
  31. data/docs/doc/Doing/Completion/BashCompletions.html +445 -0
  32. data/docs/doc/Doing/Completion/FishCompletions.html +445 -0
  33. data/docs/doc/Doing/Completion/StringUtils.html +229 -0
  34. data/docs/doc/Doing/Completion/ZshCompletions.html +445 -0
  35. data/docs/doc/Doing/Completion.html +17 -3
  36. data/docs/doc/Doing/Configuration.html +1 -1
  37. data/docs/doc/Doing/DayOneRenderer.html +383 -0
  38. data/docs/doc/Doing/DayoneExport.html +290 -0
  39. data/docs/doc/Doing/DoingImport.html +391 -0
  40. data/docs/doc/Doing/Entry.html +381 -0
  41. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  42. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  43. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  44. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  45. data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
  46. data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
  47. data/docs/doc/Doing/Errors/MissingBackupFile.html +1 -1
  48. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  49. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  50. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  51. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  52. data/docs/doc/Doing/Errors.html +1 -1
  53. data/docs/doc/Doing/HTMLExport.html +256 -0
  54. data/docs/doc/Doing/Hooks.html +1 -1
  55. data/docs/doc/Doing/Item.html +47 -3
  56. data/docs/doc/Doing/ItemDates.html +564 -0
  57. data/docs/doc/Doing/ItemQuery.html +614 -0
  58. data/docs/doc/Doing/ItemState.html +387 -0
  59. data/docs/doc/Doing/ItemTags.html +498 -0
  60. data/docs/doc/Doing/Items.html +460 -11
  61. data/docs/doc/Doing/JSONExport.html +222 -0
  62. data/docs/doc/Doing/Logger.html +1 -1
  63. data/docs/doc/Doing/MarkdownExport.html +266 -0
  64. data/docs/doc/Doing/MarkdownRenderer.html +383 -0
  65. data/docs/doc/Doing/Note.html +16 -3
  66. data/docs/doc/Doing/Pager.html +1 -1
  67. data/docs/doc/Doing/Plugins.html +1 -1
  68. data/docs/doc/Doing/Prompt.html +31 -682
  69. data/docs/doc/Doing/PromptChoose.html +484 -0
  70. data/docs/doc/Doing/PromptFZF.html +391 -0
  71. data/docs/doc/Doing/PromptInput.html +572 -0
  72. data/docs/doc/Doing/PromptSTD.html +293 -0
  73. data/docs/doc/Doing/PromptYN.html +237 -0
  74. data/docs/doc/Doing/Section.html +58 -2
  75. data/docs/doc/Doing/StringHighlight.html +533 -0
  76. data/docs/doc/Doing/StringNormalize.html +929 -0
  77. data/docs/doc/Doing/StringQuery.html +725 -0
  78. data/docs/doc/Doing/StringTags.html +884 -0
  79. data/docs/doc/Doing/StringTransform.html +565 -0
  80. data/docs/doc/Doing/StringTruncate.html +448 -0
  81. data/docs/doc/Doing/StringURL.html +409 -0
  82. data/docs/doc/Doing/SymbolNormalize.html +341 -0
  83. data/docs/doc/Doing/TaskPaperExport.html +222 -0
  84. data/docs/doc/Doing/TemplateExport.html +249 -0
  85. data/docs/doc/Doing/TemplateString.html +101 -2
  86. data/docs/doc/Doing/TimingImport.html +285 -0
  87. data/docs/doc/Doing/Types.html +1 -1
  88. data/docs/doc/Doing/Util/Backup.html +9 -7
  89. data/docs/doc/Doing/Util.html +2 -2
  90. data/docs/doc/Doing/Version.html +523 -0
  91. data/docs/doc/Doing/WWID/WWIDUtil.html +510 -0
  92. data/docs/doc/Doing/WWID.html +4377 -217
  93. data/docs/doc/Doing/WWIDDisplay.html +865 -0
  94. data/docs/doc/Doing/WWIDEditor.html +466 -0
  95. data/docs/doc/Doing/WWIDFileTools.html +359 -0
  96. data/docs/doc/Doing/WWIDFilter.html +466 -0
  97. data/docs/doc/Doing/WWIDGuess.html +299 -0
  98. data/docs/doc/Doing/WWIDInteractive.html +752 -0
  99. data/docs/doc/Doing/WWIDModify.html +1078 -0
  100. data/docs/doc/Doing/WWIDTags.html +302 -0
  101. data/docs/doc/Doing/WWIDTimers.html +359 -0
  102. data/docs/doc/Doing/WWIDUtil.html +510 -0
  103. data/docs/doc/Doing.html +9 -6
  104. data/docs/doc/FalseClass.html +1 -1
  105. data/docs/doc/GLI/Commands/Help.html +1 -1
  106. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  107. data/docs/doc/GLI/Commands.html +1 -1
  108. data/docs/doc/GLI.html +1 -1
  109. data/docs/doc/Hash.html +1 -1
  110. data/docs/doc/Numeric.html +23 -78
  111. data/docs/doc/Object.html +1 -1
  112. data/docs/doc/PhraseParser/Operator.html +1 -1
  113. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  114. data/docs/doc/PhraseParser/Query.html +1 -1
  115. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  116. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  117. data/docs/doc/PhraseParser/TermClause.html +1 -1
  118. data/docs/doc/PhraseParser.html +1 -1
  119. data/docs/doc/Status.html +1 -1
  120. data/docs/doc/String.html +58 -633
  121. data/docs/doc/Symbol.html +9 -224
  122. data/docs/doc/Time.html +119 -13
  123. data/docs/doc/TrueClass.html +1 -1
  124. data/docs/doc/_index.html +324 -8
  125. data/docs/doc/class_list.html +1 -1
  126. data/docs/doc/file.README.html +1 -1
  127. data/docs/doc/index.html +1 -1
  128. data/docs/doc/method_list.html +2326 -542
  129. data/docs/doc/top-level-namespace.html +2 -2
  130. data/doing.rdoc +13 -3
  131. data/lib/completion/_doing.zsh +1 -1
  132. data/lib/completion/doing.bash +2 -2
  133. data/lib/completion/doing.fish +3 -1
  134. data/lib/doing/array/array.rb +16 -12
  135. data/lib/doing/array/nested_hash.rb +1 -1
  136. data/lib/doing/array/tags.rb +6 -5
  137. data/lib/doing/changelog/changelog.rb +6 -0
  138. data/lib/doing/chronify/array.rb +1 -3
  139. data/lib/doing/chronify/chronify.rb +12 -0
  140. data/lib/doing/chronify/numeric.rb +3 -2
  141. data/lib/doing/chronify/string.rb +1 -1
  142. data/lib/doing/completion/completion_string.rb +25 -0
  143. data/lib/doing/completion.rb +1 -1
  144. data/lib/doing/good.rb +8 -0
  145. data/lib/doing/item/dates.rb +1 -1
  146. data/lib/doing/{item.rb → item/item.rb} +10 -5
  147. data/lib/doing/item/query.rb +1 -1
  148. data/lib/doing/item/state.rb +1 -1
  149. data/lib/doing/item/tags.rb +1 -1
  150. data/lib/doing/items/filter.rb +67 -0
  151. data/lib/doing/items/items.rb +57 -0
  152. data/lib/doing/items/modify.rb +36 -0
  153. data/lib/doing/items/sections.rb +83 -0
  154. data/lib/doing/items/util.rb +74 -0
  155. data/lib/doing/normalize.rb +10 -2
  156. data/lib/doing/plugins/export/markdown_export.rb +4 -2
  157. data/lib/doing/plugins/import/doing_import.rb +1 -1
  158. data/lib/doing/prompt/choose.rb +118 -0
  159. data/lib/doing/prompt/fzf.rb +84 -0
  160. data/lib/doing/prompt/input.rb +129 -0
  161. data/lib/doing/prompt/prompt.rb +41 -0
  162. data/lib/doing/prompt/std.rb +32 -0
  163. data/lib/doing/prompt/yn.rb +64 -0
  164. data/lib/doing/section.rb +4 -0
  165. data/lib/doing/string/highlight.rb +1 -1
  166. data/lib/doing/string/query.rb +1 -1
  167. data/lib/doing/string/string.rb +18 -7
  168. data/lib/doing/string/tags.rb +14 -3
  169. data/lib/doing/string/transform.rb +1 -1
  170. data/lib/doing/string/truncate.rb +1 -1
  171. data/lib/doing/string/url.rb +1 -1
  172. data/lib/doing/time.rb +19 -1
  173. data/lib/doing/util_backup.rb +2 -2
  174. data/lib/doing/version.rb +1 -1
  175. data/lib/doing/wwid/display.rb +357 -360
  176. data/lib/doing/wwid/editor.rb +173 -176
  177. data/lib/doing/wwid/filetools.rb +156 -159
  178. data/lib/doing/wwid/filter.rb +191 -183
  179. data/lib/doing/wwid/guess.rb +58 -60
  180. data/lib/doing/wwid/interactive.rb +332 -330
  181. data/lib/doing/wwid/modify.rb +509 -512
  182. data/lib/doing/wwid/tags.rb +38 -41
  183. data/lib/doing/wwid/timers.rb +293 -296
  184. data/lib/doing/{wwid.rb → wwid/wwid.rb} +32 -23
  185. data/lib/doing/wwid/wwidutil.rb +79 -82
  186. data/lib/doing.rb +5 -5
  187. data/lib/helpers/threaded_tests.rb +1 -0
  188. metadata +76 -14
  189. data/lib/doing/changelog.rb +0 -6
  190. data/lib/doing/completion/string.rb +0 -17
  191. data/lib/doing/items.rb +0 -221
  192. data/lib/doing/prompt.rb +0 -330
@@ -2,384 +2,386 @@
2
2
 
3
3
  module Doing
4
4
  class WWID
5
- # Interactive methods for WWID class
6
- module Interactive
7
- ##
8
- ## Display an interactive menu of entries
9
- ##
10
- ## @param opt [Hash] Additional options
11
- ##
12
- ## Options hash is shared with #filter_items and #act_on
13
- ##
14
- def interactive(opt)
15
- opt ||= {}
16
- opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
17
-
18
- search = nil
19
-
20
- if opt[:search]
21
- search = opt[:search]
22
- search.sub!(/^'?/, "'") if opt[:exact]
23
- opt[:search] = search
24
- end
5
+ ##
6
+ ## Display an interactive menu of entries
7
+ ##
8
+ ## @param opt [Hash] Additional options
9
+ ##
10
+ ## Options hash is shared with #filter_items and #act_on
11
+ ##
12
+ def interactive(opt)
13
+ opt ||= {}
14
+ opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
15
+
16
+ search = nil
17
+
18
+ if opt[:search]
19
+ search = opt[:search]
20
+ search.sub!(/^'?/, "'") if opt[:exact]
21
+ opt[:search] = search
22
+ end
25
23
 
26
- # opt[:query] = opt[:search] if opt[:search] && !opt[:query]
27
- opt[:query] = "!#{opt[:query]}" if opt[:query] && opt[:not]
28
- opt[:multiple] = true
29
- opt[:show_if_single] = true
30
- filter_options = %i[after before case date_filter from fuzzy not search section val].each_with_object({}) {
31
- |k, hsh| hsh[k] = opt[k]
32
- }
33
- items = filter_items(Items.new, opt: filter_options)
24
+ # opt[:query] = opt[:search] if opt[:search] && !opt[:query]
25
+ opt[:query] = "!#{opt[:query]}" if opt[:query] && opt[:not]
26
+ opt[:multiple] = true
27
+ opt[:show_if_single] = true
28
+ filter_options = %i[after before case date_filter from fuzzy not search section val].each_with_object({}) {
29
+ |k, hsh| hsh[k] = opt[k]
30
+ }
31
+ items = filter_items(Items.new, opt: filter_options)
34
32
 
35
- menu_options = %i[search query exact multiple show_if_single menu sort case].each_with_object({}) {
36
- |k, hsh| hsh[k] = opt[k]
37
- }
33
+ menu_options = %i[search query exact multiple show_if_single menu sort case].each_with_object({}) {
34
+ |k, hsh| hsh[k] = opt[k]
35
+ }
38
36
 
39
- selection = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, **menu_options)
37
+ selection = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, **menu_options)
40
38
 
41
- raise NoResults, 'no items selected' if selection.nil? || selection.empty?
39
+ raise NoResults, 'no items selected' if selection.nil? || selection.empty?
42
40
 
43
- act_on(selection, opt)
44
- end
41
+ act_on(selection, opt)
42
+ end
45
43
 
46
- ##
47
- ## Perform actions on a set of entries. If
48
- ## no valid action is included in the opt
49
- ## hash and the terminal is a TTY, a menu
50
- ## will be presented
51
- ##
52
- ## @param items [Array] Array of Items to affect
53
- ## @param opt [Hash] Options and actions to perform
54
- ##
55
- ## @option opt [Boolean] :editor
56
- ## @option opt [Boolean] :delete
57
- ## @option opt [String] :tag
58
- ## @option opt [Boolean] :flag
59
- ## @option opt [Boolean] :finish
60
- ## @option opt [Boolean] :cancel
61
- ## @option opt [Boolean] :archive
62
- ## @option opt [String] :output
63
- ## @option opt [String] :save_to
64
- ## @option opt [Boolean] :again
65
- ## @option opt [Boolean] :resume
66
- ##
67
- def act_on(items, opt)
68
- opt ||= {}
69
- actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
70
- has_action = false
71
- single = items.count == 1
72
-
73
- actions.each do |a|
74
- if opt[a]
75
- has_action = true
76
- break
77
- end
44
+ ##
45
+ ## Perform actions on a set of entries. If
46
+ ## no valid action is included in the opt
47
+ ## hash and the terminal is a TTY, a menu
48
+ ## will be presented
49
+ ##
50
+ ## @param items [Array] Array of Items to affect
51
+ ## @param opt [Hash] Options and actions to perform
52
+ ##
53
+ ## @option opt [Boolean] :editor
54
+ ## @option opt [Boolean] :delete
55
+ ## @option opt [String] :tag
56
+ ## @option opt [Boolean] :flag
57
+ ## @option opt [Boolean] :finish
58
+ ## @option opt [Boolean] :cancel
59
+ ## @option opt [Boolean] :archive
60
+ ## @option opt [String] :output
61
+ ## @option opt [String] :save_to
62
+ ## @option opt [Boolean] :again
63
+ ## @option opt [Boolean] :resume
64
+ ##
65
+ def act_on(items, opt)
66
+ opt ||= {}
67
+ actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
68
+ has_action = false
69
+ single = items.count == 1
70
+
71
+ actions.each do |a|
72
+ if opt[a]
73
+ has_action = true
74
+ break
78
75
  end
76
+ end
79
77
 
80
- unless has_action
81
- actions = [
82
- 'add tag',
83
- 'remove tag',
84
- 'autotag',
85
- 'cancel',
86
- 'delete',
87
- 'finish',
88
- 'flag',
89
- 'archive',
90
- 'move',
91
- 'edit',
92
- 'output formatted'
93
- ]
94
-
95
- actions.concat(['resume/repeat', 'begin/reset']) if items.count == 1
96
-
97
- choice = Prompt.choose_from(actions,
98
- prompt: 'What do you want to do with the selected items? > ',
99
- multiple: true,
100
- sorted: false,
101
- fzf_args: ["--height=#{actions.count + 3}", '--tac', '--no-sort', '--info=hidden'])
102
- return unless choice
103
-
104
- to_do = choice.strip.split(/\n/)
105
- to_do.each do |action|
106
- case action
107
- when /resume/
108
- opt[:resume] = true
109
- when /reset/
110
- opt[:reset] = true
111
- when /autotag/
112
- opt[:autotag] = true
113
- when /(add|remove) tag/
114
- type = action =~ /^add/ ? 'add' : 'remove'
115
- raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
116
-
117
- tags = type == 'add' ? all_tags(@content) : all_tags(items)
118
-
119
- puts "#{yellow}Separate multiple tags with spaces, hit tab to complete known tags#{type == 'add' ? ', include values with tag(value)' : ''}"
120
- puts "#{boldgreen}Available tags: #{boldwhite}#{tags.sort.map(&:add_at).join(', ')}" if type == 'remove'
121
- tag = Prompt.read_line(prompt: "Tags to #{type}", completions: tags)
122
-
123
- # print "#{yellow("Tag to #{type}: ")}#{reset}"
124
- # tag = $stdin.gets
125
- next if tag =~ /^ *$/
126
-
127
- opt[:tag] = tag.strip.sub(/^@/, '')
128
- opt[:remove] = true if type == 'remove'
129
- when /output formatted/
130
- plugins = Plugins.available_plugins(type: :export).sort
131
- output_format = Prompt.choose_from(plugins,
132
- prompt: 'Which output format? > ',
133
- fzf_args: [
134
- "--height=#{plugins.count + 3}",
135
- '--tac',
136
- '--no-sort',
137
- '--info=hidden'
138
- ])
139
- next if output_format =~ /^ *$/
140
-
141
- raise UserCancelled unless output_format
142
-
143
- opt[:output] = output_format.strip
144
- res = opt[:force] ? false : Prompt.yn('Save to file?', default_response: 'n')
145
- if res
146
- # print "#{yellow('File path/name: ')}#{reset}"
147
- # filename = $stdin.gets.strip
148
- filename = Prompt.read_line(prompt: 'File path/name')
149
- next if filename.empty?
150
-
151
- opt[:save_to] = filename
152
- end
153
- when /archive/
154
- opt[:archive] = true
155
- when /delete/
156
- opt[:delete] = true
157
- when /edit/
158
- opt[:editor] = true
159
- when /finish/
160
- opt[:finish] = true
161
- when /cancel/
162
- opt[:cancel] = true
163
- when /move/
164
- section = choose_section.strip
165
- opt[:move] = section.strip unless section =~ /^ *$/
166
- when /flag/
167
- opt[:flag] = true
78
+ unless has_action
79
+ actions = [
80
+ 'add tag',
81
+ 'remove tag',
82
+ 'autotag',
83
+ 'cancel',
84
+ 'delete',
85
+ 'finish',
86
+ 'flag',
87
+ 'archive',
88
+ 'move',
89
+ 'edit',
90
+ 'output formatted'
91
+ ]
92
+
93
+ actions.concat(['resume/repeat', 'begin/reset']) if items.count == 1
94
+
95
+ choice = Prompt.choose_from(actions,
96
+ prompt: 'What do you want to do with the selected items? > ',
97
+ multiple: true,
98
+ sorted: false,
99
+ fzf_args: ["--height=#{actions.count + 3}", '--tac', '--no-sort', '--info=hidden'])
100
+ return unless choice
101
+
102
+ to_do = choice.strip.split(/\n/)
103
+ to_do.each do |action|
104
+ case action
105
+ when /resume/
106
+ opt[:resume] = true
107
+ when /reset/
108
+ opt[:reset] = true
109
+ when /autotag/
110
+ opt[:autotag] = true
111
+ when /(add|remove) tag/
112
+ type = action =~ /^add/ ? 'add' : 'remove'
113
+ raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
114
+
115
+ tags = type == 'add' ? all_tags(@content) : all_tags(items)
116
+
117
+ add_msg = type == 'add' ? ', include values with tag(value)' : ''
118
+ puts "#{yellow}Separate multiple tags with spaces, hit tab to complete known tags#{add_msg}"
119
+ puts "#{boldgreen}Available tags: #{boldwhite}#{tags.sort.map(&:add_at).join(', ')}" if type == 'remove'
120
+ tag = Prompt.read_line(prompt: "Tags to #{type}", completions: tags)
121
+
122
+ # print "#{yellow("Tag to #{type}: ")}#{reset}"
123
+ # tag = $stdin.gets
124
+ next if tag =~ /^ *$/
125
+
126
+ opt[:tag] = tag.strip.sub(/^@/, '')
127
+ opt[:remove] = true if type == 'remove'
128
+ when /output formatted/
129
+ plugins = Plugins.available_plugins(type: :export).sort
130
+ output_format = Prompt.choose_from(plugins,
131
+ prompt: 'Which output format? > ',
132
+ fzf_args: [
133
+ "--height=#{plugins.count + 3}",
134
+ '--tac',
135
+ '--no-sort',
136
+ '--info=hidden'
137
+ ])
138
+ next if output_format =~ /^ *$/
139
+
140
+ raise UserCancelled unless output_format
141
+
142
+ opt[:output] = output_format.strip
143
+ res = opt[:force] ? false : Prompt.yn('Save to file?', default_response: 'n')
144
+ if res
145
+ # print "#{yellow('File path/name: ')}#{reset}"
146
+ # filename = $stdin.gets.strip
147
+ filename = Prompt.read_line(prompt: 'File path/name')
148
+ next if filename.empty?
149
+
150
+ opt[:save_to] = filename
168
151
  end
152
+ when /archive/
153
+ opt[:archive] = true
154
+ when /delete/
155
+ opt[:delete] = true
156
+ when /edit/
157
+ opt[:editor] = true
158
+ when /finish/
159
+ opt[:finish] = true
160
+ when /cancel/
161
+ opt[:cancel] = true
162
+ when /move/
163
+ section = choose_section.strip
164
+ opt[:move] = section.strip unless section =~ /^ *$/
165
+ when /flag/
166
+ opt[:flag] = true
169
167
  end
170
168
  end
169
+ end
170
+
171
+ if opt[:resume] || opt[:reset]
172
+ raise InvalidArgument, 'resume and restart can only be used on a single entry' if items.count > 1
173
+
174
+ item = items[0]
175
+ if opt[:resume] && !opt[:reset]
176
+ repeat_item(item, { editor: opt[:editor] }) # hooked
177
+ elsif opt[:reset]
178
+ res = Prompt.enter_text('Start date (blank for current time)', default_response: '')
179
+ date = if res =~ /^ *$/
180
+ Time.now
181
+ else
182
+ res.chronify(guess: :begin)
183
+ end
184
+
185
+ res = if item.tags?('done', :and) && !opt[:resume]
186
+ opt[:force] ? true : Prompt.yn('Remove @done tag?', default_response: 'y')
187
+ else
188
+ opt[:resume]
189
+ end
190
+ old_item = item.clone
191
+ new_entry = reset_item(item, date: date, resume: res)
192
+ @content.update_item(item, new_entry)
193
+ Hooks.trigger :post_entry_updated, self, new_entry, old_item
194
+ end
195
+ write(@doing_file)
171
196
 
172
- if opt[:resume] || opt[:reset]
173
- raise InvalidArgument, 'resume and restart can only be used on a single entry' if items.count > 1
174
-
175
- item = items[0]
176
- if opt[:resume] && !opt[:reset]
177
- repeat_item(item, { editor: opt[:editor] }) # hooked
178
- elsif opt[:reset]
179
- res = Prompt.enter_text('Start date (blank for current time)', default_response: '')
180
- if res =~ /^ *$/
181
- date = Time.now
182
- else
183
- date = res.chronify(guess: :begin)
184
- end
197
+ return
198
+ end
185
199
 
186
- res = if item.tags?('done', :and) && !opt[:resume]
187
- opt[:force] ? true : Prompt.yn('Remove @done tag?', default_response: 'y')
188
- else
189
- opt[:resume]
190
- end
191
- old_item = item.clone
192
- new_entry = reset_item(item, date: date, resume: res)
193
- @content.update_item(item, new_entry)
194
- Hooks.trigger :post_entry_updated, self, new_entry, old_item
195
- end
196
- write(@doing_file)
200
+ if opt[:delete]
201
+ delete_items(items, force: opt[:force]) # hooked
202
+ write(@doing_file)
197
203
 
198
- return
204
+ return
205
+ end
206
+
207
+ if opt[:flag]
208
+ tag = Doing.setting('marker_tag', 'flagged')
209
+ items.map! do |i|
210
+ old_item = i.clone
211
+ i.tag(tag, date: false, remove: opt[:remove], single: single)
212
+ Hooks.trigger :post_entry_updated, self, i, old_item
199
213
  end
214
+ end
200
215
 
201
- if opt[:delete]
202
- delete_items(items, force: opt[:force]) # hooked
203
- write(@doing_file)
216
+ if opt[:finish] || opt[:cancel]
217
+ tag = 'done'
218
+ items.map! do |i|
219
+ next unless i.should_finish?
204
220
 
205
- return
221
+ old_item = i.clone
222
+ should_date = !opt[:cancel] && i.should_time?
223
+ i.tag(tag, date: should_date, remove: opt[:remove], single: single)
224
+ Hooks.trigger :post_entry_updated, self, i, old_item
206
225
  end
226
+ end
207
227
 
208
- if opt[:flag]
209
- tag = Doing.setting('marker_tag', 'flagged')
210
- items.map! do |i|
228
+ if opt[:autotag]
229
+ items.map! do |i|
230
+ new_title = autotag(i.title)
231
+ if new_title == i.title
232
+ logger.count(:skipped, level: :debug, message: '%count unchaged %items')
233
+ # logger.debug('Autotag:', 'No changes')
234
+ else
235
+ logger.count(:added_tags)
236
+ logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
211
237
  old_item = i.clone
212
- i.tag(tag, date: false, remove: opt[:remove], single: single)
238
+ i.title = new_title
213
239
  Hooks.trigger :post_entry_updated, self, i, old_item
214
240
  end
215
241
  end
242
+ end
216
243
 
217
- if opt[:finish] || opt[:cancel]
218
- tag = 'done'
219
- items.map! do |i|
220
- if i.should_finish?
221
- old_item = i.clone
222
- should_date = !opt[:cancel] && i.should_time?
223
- i.tag(tag, date: should_date, remove: opt[:remove], single: single)
224
- Hooks.trigger :post_entry_updated, self, i, old_item
225
- end
226
- end
244
+ if opt[:tag]
245
+ tag = opt[:tag]
246
+ items.map! do |i|
247
+ old_item = i.clone
248
+ i.tag(tag, date: false, remove: opt[:remove], single: single)
249
+ i.expand_date_tags(Doing.setting('date_tags'))
250
+ Hooks.trigger :post_entry_updated, self, i, old_item
227
251
  end
252
+ end
228
253
 
229
- if opt[:autotag]
230
- items.map! do |i|
231
- new_title = autotag(i.title)
232
- if new_title == i.title
233
- logger.count(:skipped, level: :debug, message: '%count unchaged %items')
234
- # logger.debug('Autotag:', 'No changes')
235
- else
236
- logger.count(:added_tags)
237
- logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
238
- old_item = i.clone
239
- i.title = new_title
240
- Hooks.trigger :post_entry_updated, self, i, old_item
241
- end
242
- end
254
+ if opt[:archive] || opt[:move]
255
+ section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
256
+ items.map! do |i|
257
+ old_item = i.clone
258
+ i.move_to(section, label: true)
259
+ Hooks.trigger :post_entry_updated, self, i, old_item
243
260
  end
261
+ end
244
262
 
245
- if opt[:tag]
246
- tag = opt[:tag]
247
- items.map! do |i|
248
- old_item = i.clone
249
- i.tag(tag, date: false, remove: opt[:remove], single: single)
250
- i.expand_date_tags(Doing.setting('date_tags'))
251
- Hooks.trigger :post_entry_updated, self, i, old_item
252
- end
253
- end
263
+ write(@doing_file)
254
264
 
255
- if opt[:archive] || opt[:move]
256
- section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
257
- items.map! do |i|
258
- old_item = i.clone
259
- i.move_to(section, label: true)
260
- Hooks.trigger :post_entry_updated, self, i, old_item
261
- end
262
- end
265
+ if opt[:editor]
266
+ edit_items(items) # hooked
263
267
 
264
268
  write(@doing_file)
269
+ end
265
270
 
266
- if opt[:editor]
267
- edit_items(items) # hooked
271
+ return unless opt[:output]
268
272
 
269
- write(@doing_file)
270
- end
273
+ items.each { |i| i.title = "#{i.title} @section(#{i.section})" }
271
274
 
272
- return unless opt[:output]
275
+ export_items = Items.new
276
+ export_items.concat(items)
277
+ export_items.add_section(Section.new('Export'), log: false)
278
+ options = { section: 'All' }
273
279
 
274
- items.each { |i| i.title = "#{i.title} @section(#{i.section})" }
280
+ if opt[:output] =~ /doing/
281
+ options[:output] = 'template'
282
+ options[:template] = '- %date | %title%note'
283
+ else
284
+ options[:output] = opt[:output]
285
+ options[:template] = opt[:template] || nil
286
+ end
275
287
 
276
- export_items = Items.new
277
- export_items.concat(items)
278
- export_items.add_section(Section.new('Export'), log: false)
279
- options = { section: 'All' }
288
+ output = list_section(options, items: export_items) # hooked
280
289
 
281
- if opt[:output] =~ /doing/
282
- options[:output] = 'template'
283
- options[:template] = '- %date | %title%note'
284
- else
285
- options[:output] = opt[:output]
286
- options[:template] = opt[:template] || nil
290
+ if opt[:save_to]
291
+ file = File.expand_path(opt[:save_to])
292
+ if File.exist?(file)
293
+ # Create a backup copy for the undo command
294
+ FileUtils.cp(file, "#{file}~")
287
295
  end
288
296
 
289
- output = list_section(options, items: export_items) # hooked
290
-
291
- if opt[:save_to]
292
- file = File.expand_path(opt[:save_to])
293
- if File.exist?(file)
294
- # Create a backup copy for the undo command
295
- FileUtils.cp(file, "#{file}~")
296
- end
297
-
298
- File.open(file, 'w+') do |f|
299
- f.puts output
300
- end
301
-
302
- logger.warn('File written:', file)
303
- else
304
- Doing::Pager.page output
297
+ File.open(file, 'w+') do |f|
298
+ f.puts output
305
299
  end
306
- end
307
300
 
308
- ##
309
- ## Generate a menu of sections and allow user selection
310
- ##
311
- ## @return [String] The selected section name
312
- ##
313
- def choose_section(include_all: false)
314
- options = @content.section_titles.sort
315
- options.unshift('All') if include_all
316
- choice = Prompt.choose_from(options, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
317
- choice ? choice.strip : choice
301
+ logger.warn('File written:', file)
302
+ else
303
+ Doing::Pager.page output
318
304
  end
305
+ end
319
306
 
320
- ##
321
- ## Generate a menu of tags and allow user selection
322
- ##
323
- ## @return [String] The selected tag name
324
- ##
325
- def choose_tag(section = 'All', items: nil, include_all: false)
326
- items ||= @content.in_section(section)
327
- tags = all_tags(items, counts: true).map { |t, c| "@#{t} (#{c})" }
328
- tags.unshift('No tag filter') if include_all
329
- choice = Prompt.choose_from(tags, sorted: false, multiple: true, prompt: 'Choose tag(s) > ', fzf_args: ['--height=60%'])
330
- choice ? choice.split(/\n/).map { |t| t.strip.sub(/ \(.*?\)$/, '')}.join(' ') : choice
331
- end
307
+ ##
308
+ ## Generate a menu of sections and allow user selection
309
+ ##
310
+ ## @return [String] The selected section name
311
+ ##
312
+ def choose_section(include_all: false)
313
+ options = @content.section_titles.sort
314
+ options.unshift('All') if include_all
315
+ choice = Prompt.choose_from(options, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
316
+ choice ? choice.strip : choice
317
+ end
332
318
 
333
- ##
334
- ## Generate a menu of sections and tags and allow user selection
335
- ##
336
- ## @return [String] The selected section or tag name
337
- ##
338
- def choose_section_tag
339
- options = @content.section_titles.sort
340
- options.concat(@content.all_tags.sort.map { |t| "@#{t}" })
341
- choice = Prompt.choose_from(options, prompt: 'Choose a section or tag > ', fzf_args: ['--height=60%'])
342
- choice ? choice.strip : choice
343
- end
319
+ ##
320
+ ## Generate a menu of tags and allow user selection
321
+ ##
322
+ ## @return [String] The selected tag name
323
+ ##
324
+ def choose_tag(section = 'All', items: nil, include_all: false)
325
+ items ||= @content.in_section(section)
326
+ tags = all_tags(items, counts: true).map { |t, c| "@#{t} (#{c})" }
327
+ tags.unshift('No tag filter') if include_all
328
+ choice = Prompt.choose_from(tags,
329
+ sorted: false,
330
+ multiple: true,
331
+ prompt: 'Choose tag(s) > ',
332
+ fzf_args: ['--height=60%'])
333
+ choice ? choice.split(/\n/).map { |t| t.strip.sub(/ \(.*?\)$/, '') }.join(' ') : choice
334
+ end
344
335
 
345
- ##
346
- ## Generate a menu of views and allow user selection
347
- ##
348
- ## @return [String] The selected view name
349
- ##
350
- def choose_view
351
- choice = Prompt.choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%'])
352
- choice ? choice.strip : choice
353
- end
336
+ ##
337
+ ## Generate a menu of sections and tags and allow user selection
338
+ ##
339
+ ## @return [String] The selected section or tag name
340
+ ##
341
+ def choose_section_tag
342
+ options = @content.section_titles.sort
343
+ options.concat(@content.all_tags.sort.map { |t| "@#{t}" })
344
+ choice = Prompt.choose_from(options, prompt: 'Choose a section or tag > ', fzf_args: ['--height=60%'])
345
+ choice ? choice.strip : choice
346
+ end
354
347
 
355
- ##
356
- ## Interactively verify an item modification if elapsed time is greater than configured threshold
357
- ##
358
- ## @param date [String] Item date
359
- ## @param finish_date [String] The finish date
360
- ## @param title [String] The Item title
361
- ##
362
- def verify_duration(date, finish_date, title: nil)
363
- max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
364
- max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
365
- date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
366
-
367
- elapsed = finish_date - date
368
-
369
- if max_elapsed.positive? && (elapsed > max_elapsed)
370
- puts boldwhite(title) if title
371
- human = elapsed.time_string(format: :natural)
372
- res = Prompt.yn(yellow("Did this entry actually take #{human}"), default_response: true)
373
- unless res
374
- new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
375
- raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
376
-
377
- finish_date = date + new_elapsed if new_elapsed
378
- end
379
- end
348
+ ##
349
+ ## Generate a menu of views and allow user selection
350
+ ##
351
+ ## @return [String] The selected view name
352
+ ##
353
+ def choose_view
354
+ choice = Prompt.choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%'])
355
+ choice ? choice.strip : choice
356
+ end
380
357
 
381
- finish_date
358
+ ##
359
+ ## Interactively verify an item modification if elapsed time is greater than configured threshold
360
+ ##
361
+ ## @param date [String] Item date
362
+ ## @param finish_date [String] The finish date
363
+ ## @param title [String] The Item title
364
+ ##
365
+ def verify_duration(date, finish_date, title: nil)
366
+ max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
367
+ max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
368
+ date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
369
+
370
+ elapsed = finish_date - date
371
+
372
+ if max_elapsed.positive? && (elapsed > max_elapsed)
373
+ puts boldwhite(title) if title
374
+ human = elapsed.time_string(format: :natural)
375
+ res = Prompt.yn(yellow("Did this entry actually take #{human}"), default_response: true)
376
+ unless res
377
+ new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
378
+ raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
379
+
380
+ finish_date = date + new_elapsed if new_elapsed
381
+ end
382
382
  end
383
+
384
+ finish_date
383
385
  end
384
386
  end
385
387
  end