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,616 +2,613 @@
2
2
 
3
3
  module Doing
4
4
  class WWID
5
- # Item modification methods for WWID class
6
- module Modify
7
- ##
8
- ## Adds an entry
9
- ##
10
- ## @param title [String] The entry title
11
- ## @param section [String] The section to add to
12
- ## @param opt [Hash] Additional Options
13
- ##
14
- ## @option opt :date [Date] item start date
15
- ## @option opt :note [Array] item note (will be converted if value is String)
16
- ## @option opt :back [Date] backdate
17
- ## @option opt :timed [Boolean] new item is timed entry, marks previous entry as @done
18
- ## @option opt :done [Date] If set, adds a @done tag to new entry
19
- ##
20
- def add_item(title, section = nil, opt)
21
- opt ||= {}
22
- section ||= Doing.setting('current_section')
23
- @content.add_section(section, log: false)
24
- opt[:back] ||= opt[:date] ? opt[:date] : Time.now
25
- opt[:date] ||= Time.now
26
- note = Note.new
27
- opt[:timed] ||= false
28
-
29
- note.add(opt[:note]) if opt[:note]
30
-
31
- title = [title.strip.cap_first]
32
- title = title.join(' ')
33
-
34
- if Doing.auto_tag
35
- title = autotag(title)
36
- title.add_tags!(Doing.setting('default_tags')) if Doing.setting('default_tags').good?
37
- end
5
+ ##
6
+ ## Adds an entry
7
+ ##
8
+ ## @param title [String] The entry title
9
+ ## @param section [String] The section to add to
10
+ ## @param opt [Hash] Additional Options
11
+ ##
12
+ ## @option opt :date [Date] item start date
13
+ ## @option opt :note [Note] item note (will be converted if value is String)
14
+ ## @option opt :back [Date] backdate
15
+ ## @option opt :timed [Boolean] new item is timed entry, marks previous entry as @done
16
+ ## @option opt :done [Date] If set, adds a @done tag to new entry
17
+ ##
18
+ def add_item(title, section = nil, opt)
19
+ opt ||= {}
20
+ section ||= Doing.setting('current_section')
21
+ @content.add_section(section, log: false)
22
+ opt[:back] ||= opt[:date] ? opt[:date] : Time.now
23
+ opt[:date] ||= Time.now
24
+ note = Note.new
25
+ opt[:timed] ||= false
26
+
27
+ note.add(opt[:note]) if opt[:note]
28
+
29
+ title = [title.strip.cap_first]
30
+ title = title.join(' ')
31
+
32
+ if Doing.auto_tag
33
+ title = autotag(title)
34
+ title.add_tags!(Doing.setting('default_tags')) if Doing.setting('default_tags').good?
35
+ end
38
36
 
39
- title.compress!
40
- entry = Item.new(opt[:back], title.strip, section)
37
+ title.compress!
38
+ entry = Item.new(opt[:back], title.strip, section)
41
39
 
42
- if opt[:done] && entry.should_finish?
43
- if entry.should_time?
44
- entry.tag('done', value: opt[:done])
45
- else
46
- entry.tag('done')
47
- end
40
+ if opt[:done] && entry.should_finish?
41
+ if entry.should_time?
42
+ entry.tag('done', value: opt[:done])
43
+ else
44
+ entry.tag('done')
48
45
  end
46
+ end
49
47
 
50
- entry.note = note
48
+ entry.note = note
51
49
 
52
- items = @content.clone
53
- if opt[:timed]
54
- items.reverse!
55
- items.each_with_index do |i, x|
56
- next if i.title =~ / @done/
50
+ items = @content.clone
51
+ if opt[:timed]
52
+ items.reverse!
53
+ items.each_with_index do |i, x|
54
+ next if i.title =~ / @done/
57
55
 
58
- finish_date = verify_duration(i.date, opt[:back], title: i.title)
59
- items[x].tag('done', value: finish_date.strftime('%F %R'))
60
- break
61
- end
56
+ finish_date = verify_duration(i.date, opt[:back], title: i.title)
57
+ items[x].tag('done', value: finish_date.strftime('%F %R'))
58
+ break
62
59
  end
60
+ end
61
+
62
+ Hooks.trigger :pre_entry_add, self, entry
63
63
 
64
- Hooks.trigger :pre_entry_add, self, entry
64
+ @content.push(entry)
65
+ # logger.count(:added, level: :debug)
66
+ logger.info('New entry:', %(added "#{entry.date.relative_date}: #{entry.title}" to #{section}))
65
67
 
66
- @content.push(entry)
67
- # logger.count(:added, level: :debug)
68
- logger.info('New entry:', %(added "#{entry.date.relative_date}: #{entry.title}" to #{section}))
68
+ Hooks.trigger :post_entry_added, self, entry
69
+ entry
70
+ end
69
71
 
70
- Hooks.trigger :post_entry_added, self, entry
71
- entry
72
+ # Reset start date to current time, optionally remove
73
+ # done tag (resume)
74
+ #
75
+ # @param item [Item] the item to reset/resume
76
+ # @param resume [Boolean] removing @done tag if true
77
+ #
78
+ def reset_item(item, date: nil, finish_date: nil, resume: false)
79
+ date ||= Time.now
80
+ item.date = date
81
+ if finish_date
82
+ item.tag('done', remove: true)
83
+ item.tag('done', value: finish_date.strftime('%F %R'))
84
+ else
85
+ item.tag('done', remove: true) if resume
72
86
  end
87
+ logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
88
+ item
89
+ end
73
90
 
74
- # Reset start date to current time, optionally remove
75
- # done tag (resume)
76
- #
77
- # @param item [Item] the item to reset/resume
78
- # @param resume [Boolean] removing @done tag if true
79
- #
80
- def reset_item(item, date: nil, finish_date: nil, resume: false)
81
- date ||= Time.now
82
- item.date = date
83
- if finish_date
84
- item.tag('done', remove: true)
85
- item.tag('done', value: finish_date.strftime('%F %R'))
91
+ # Duplicate an item and add it as a new item
92
+ #
93
+ # @param item [Item] the item to duplicate
94
+ # @param opt [Hash] additional options
95
+ #
96
+ # @option opt :editor [Boolean] open new item in editor
97
+ # @option opt :date [String] set start date
98
+ # @option opt :in [String] add new item to section :in
99
+ # @option opt :note [Note] add note to new item
100
+ #
101
+ # @return nothing
102
+ #
103
+ def repeat_item(item, opt)
104
+ opt ||= {}
105
+ old_item = item.clone
106
+ if item.should_finish?
107
+ if item.should_time?
108
+ finish_date = verify_duration(item.date, Time.now, title: item.title)
109
+ item.title.tag!('done', value: finish_date.strftime('%F %R'))
86
110
  else
87
- item.tag('done', remove: true) if resume
111
+ item.title.tag!('done')
88
112
  end
89
- logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
90
- item
113
+ Hooks.trigger :post_entry_updated, self, item, old_item
91
114
  end
92
115
 
93
- # Duplicate an item and add it as a new item
94
- #
95
- # @param item [Item] the item to duplicate
96
- # @param opt [Hash] additional options
97
- #
98
- # @option opt :editor [Boolean] open new item in editor
99
- # @option opt :date [String] set start date
100
- # @option opt :in [String] add new item to section :in
101
- # @option opt :note [Note] add note to new item
102
- #
103
- # @return nothing
104
- #
105
- def repeat_item(item, opt)
106
- opt ||= {}
107
- old_item = item.clone
108
- if item.should_finish?
109
- if item.should_time?
110
- finish_date = verify_duration(item.date, Time.now, title: item.title)
111
- item.title.tag!('done', value: finish_date.strftime('%F %R'))
112
- else
113
- item.title.tag!('done')
114
- end
115
- Hooks.trigger :post_entry_updated, self, item, old_item
116
- end
117
-
118
- # Remove @done tag
119
- title = item.title.sub(/\s*@done(\(.*?\))?/, '').chomp
120
- section = opt[:in].nil? ? item.section : guess_section(opt[:in])
121
- Doing.auto_tag = false
116
+ # Remove @done tag
117
+ title = item.title.sub(/\s*@done(\(.*?\))?/, '').chomp
118
+ section = opt[:in].nil? ? item.section : guess_section(opt[:in])
119
+ Doing.auto_tag = false
122
120
 
123
- note = opt[:note] || Note.new
121
+ note = opt[:note] || Note.new
124
122
 
125
- if opt[:editor]
126
- start = opt[:date] ? opt[:date] : Time.now
127
- to_edit = "#{start.strftime('%F %R')} | #{title}"
128
- to_edit += "\n#{note.strip_lines.join("\n")}" unless note.empty?
129
- new_item = fork_editor(to_edit)
130
- date, title, note = format_input(new_item)
123
+ if opt[:editor]
124
+ start = opt[:date] ? opt[:date] : Time.now
125
+ to_edit = "#{start.strftime('%F %R')} | #{title}"
126
+ to_edit += "\n#{note.strip_lines.join("\n")}" unless note.empty?
127
+ new_item = fork_editor(to_edit)
128
+ date, title, note = format_input(new_item)
131
129
 
132
- opt[:date] = date unless date.nil?
130
+ opt[:date] = date unless date.nil?
133
131
 
134
- if title.nil? || title.empty?
135
- logger.warn('Skipped:', 'No content provided')
136
- return
137
- end
132
+ if title.nil? || title.empty?
133
+ logger.warn('Skipped:', 'No content provided')
134
+ return
138
135
  end
139
-
140
- # @content.update_item(original, item)
141
- add_item(title, section, { note: note, back: opt[:date], timed: false })
142
136
  end
143
137
 
144
- ##
145
- ## Restart the last entry
146
- ##
147
- ## @param opt [Hash] Additional Options
148
- ##
149
- def repeat_last(opt)
150
- opt ||= {}
151
- opt[:section] ||= 'all'
152
- opt[:section] = guess_section(opt[:section])
153
- opt[:note] ||= []
154
- opt[:tag] ||= []
155
- opt[:tag_bool] ||= :and
156
-
157
- last = last_entry(opt)
158
- if last.nil?
159
- logger.warn('Skipped:', 'No previous entry found')
160
- return
161
- end
138
+ # @content.update_item(original, item)
139
+ add_item(title, section, { note: note, back: opt[:date], timed: false })
140
+ end
162
141
 
163
- repeat_item(last, opt)
164
- write(@doing_file)
142
+ ##
143
+ ## Restart the last entry
144
+ ##
145
+ ## @param opt [Hash] Additional Options
146
+ ##
147
+ def repeat_last(opt)
148
+ opt ||= {}
149
+ opt[:section] ||= 'all'
150
+ opt[:section] = guess_section(opt[:section])
151
+ opt[:note] ||= []
152
+ opt[:tag] ||= []
153
+ opt[:tag_bool] ||= :and
154
+
155
+ last = last_entry(opt)
156
+ if last.nil?
157
+ logger.warn('Skipped:', 'No previous entry found')
158
+ return
165
159
  end
166
160
 
161
+ repeat_item(last, opt)
162
+ write(@doing_file)
163
+ end
167
164
 
168
- ##
169
- ## Tag the last entry or X entries
170
- ##
171
- ## @param opt [Hash] Additional Options (see
172
- ## #filter_items for filtering
173
- ## options)
174
- ##
175
- ## @see #filter_items
176
- ##
177
- def tag_last(opt) # hooked
178
- opt ||= {}
179
- opt[:count] ||= 1
180
- opt[:archive] ||= false
181
- opt[:tags] ||= ['done']
182
- opt[:sequential] ||= false
183
- opt[:date] ||= false
184
- opt[:remove] ||= false
185
- opt[:update] ||= false
186
- opt[:autotag] ||= false
187
- opt[:back] ||= false
188
- opt[:unfinished] ||= false
189
- opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
190
-
191
- items = filter_items(Items.new, opt: opt)
192
-
193
- if opt[:interactive]
194
- items = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, menu: true,
195
- header: '',
196
- prompt: 'Select entries to tag > ',
197
- multiple: true,
198
- sort: true,
199
- show_if_single: true)
200
-
201
- raise NoResults, 'no items selected' if items.empty?
202
165
 
203
- end
166
+ ##
167
+ ## Tag the last entry or X entries
168
+ ##
169
+ ## @param opt [Hash] Additional Options (see
170
+ ## #filter_items for filtering
171
+ ## options)
172
+ ##
173
+ ## @see #filter_items
174
+ ##
175
+ def tag_last(opt) # hooked
176
+ opt ||= {}
177
+ opt[:count] ||= 1
178
+ opt[:archive] ||= false
179
+ opt[:tags] ||= ['done']
180
+ opt[:sequential] ||= false
181
+ opt[:date] ||= false
182
+ opt[:remove] ||= false
183
+ opt[:update] ||= false
184
+ opt[:autotag] ||= false
185
+ opt[:back] ||= false
186
+ opt[:unfinished] ||= false
187
+ opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
188
+
189
+ items = filter_items(Items.new, opt: opt)
190
+
191
+ if opt[:interactive]
192
+ items = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, menu: true,
193
+ header: '',
194
+ prompt: 'Select entries to tag > ',
195
+ multiple: true,
196
+ sort: true,
197
+ show_if_single: true)
198
+
199
+ raise NoResults, 'no items selected' if items.empty?
204
200
 
205
- raise NoResults, 'no items matched your search' if items.empty?
201
+ end
206
202
 
207
- if opt[:tags].empty? && !opt[:autotag]
208
- completions = opt[:remove] ? all_tags(items) : all_tags(@content)
209
- if opt[:remove]
210
- puts "#{yellow}Available tags: #{boldwhite}#{completions.map(&:add_at).join(', ')}"
211
- else
212
- puts "#{yellow}Use tab to complete known tags"
213
- end
214
- opt[:tags] = Doing::Prompt.read_line(prompt: "Enter tag(s) to #{opt[:remove] ? 'remove' : 'add'}",
215
- completions: completions,
216
- default_response: '').to_tags
217
- raise UserCancelled, 'No tags provided' if opt[:tags].empty?
203
+ raise NoResults, 'no items matched your search' if items.empty?
204
+
205
+ if opt[:tags].empty? && !opt[:autotag]
206
+ completions = opt[:remove] ? all_tags(items) : all_tags(@content)
207
+ if opt[:remove]
208
+ puts "#{yellow}Available tags: #{boldwhite}#{completions.map(&:add_at).join(', ')}"
209
+ else
210
+ puts "#{yellow}Use tab to complete known tags"
218
211
  end
212
+ opt[:tags] = Doing::Prompt.read_line(prompt: "Enter tag(s) to #{opt[:remove] ? 'remove' : 'add'}",
213
+ completions: completions,
214
+ default_response: '').to_tags
215
+ raise UserCancelled, 'No tags provided' if opt[:tags].empty?
216
+ end
219
217
 
220
- items.each do |item|
221
- old_item = item.clone
222
- added = []
223
- removed = []
218
+ items.each do |item|
219
+ old_item = item.clone
220
+ added = []
221
+ removed = []
224
222
 
225
- item.date = opt[:start_date] if opt[:start_date]
223
+ item.date = opt[:start_date] if opt[:start_date]
226
224
 
227
- if opt[:autotag]
228
- new_title = autotag(item.title) if Doing.auto_tag
229
- if new_title == item.title
230
- logger.count(:skipped, level: :debug, message: '%count unchaged %items')
231
- # logger.debug('Autotag:', 'No changes')
232
- else
233
- logger.count(:added_tags)
234
- logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
235
- item.title = new_title
236
- end
225
+ if opt[:autotag]
226
+ new_title = autotag(item.title) if Doing.auto_tag
227
+ if new_title == item.title
228
+ logger.count(:skipped, level: :debug, message: '%count unchaged %items')
229
+ # logger.debug('Autotag:', 'No changes')
237
230
  else
238
- if opt[:done_date]
239
- done_date = opt[:done_date]
240
- elsif opt[:sequential]
241
- next_entry = next_item(item)
242
-
243
- done_date = if next_entry.nil?
244
- Time.now
245
- else
246
- next_entry.date - 60
247
- end
248
- else
249
- done_date = item.calculate_end_date(opt)
250
- end
231
+ logger.count(:added_tags)
232
+ logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
233
+ item.title = new_title
234
+ end
235
+ else
236
+ if opt[:done_date]
237
+ done_date = opt[:done_date]
238
+ elsif opt[:sequential]
239
+ next_entry = next_item(item)
240
+
241
+ done_date = if next_entry.nil?
242
+ Time.now
243
+ else
244
+ next_entry.date - 60
245
+ end
246
+ else
247
+ done_date = item.calculate_end_date(opt)
248
+ end
251
249
 
252
- opt[:tags].each do |tag|
253
- if tag == 'done' && !item.should_finish?
250
+ opt[:tags].each do |tag|
251
+ if tag == 'done' && !item.should_finish?
254
252
 
255
- Doing.logger.debug('Skipped:', "Item in never_finish: #{item.title}")
256
- logger.count(:skipped, level: :debug)
257
- next
258
- end
253
+ Doing.logger.debug('Skipped:', "Item in never_finish: #{item.title}")
254
+ logger.count(:skipped, level: :debug)
255
+ next
256
+ end
259
257
 
260
- tag = tag.strip
258
+ tag = tag.strip
261
259
 
262
- if tag =~ /^(\S+)\((.*?)\)$/
263
- m = Regexp.last_match
264
- tag = m[1]
265
- opt[:value] ||= m[2]
266
- end
260
+ if tag =~ /^(\S+)\((.*?)\)$/
261
+ m = Regexp.last_match
262
+ tag = m[1]
263
+ opt[:value] ||= m[2]
264
+ end
267
265
 
268
- if tag =~ /^done$/ && opt[:date] && item.should_time?
269
- max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
270
- max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
271
- elapsed = done_date - item.date
272
-
273
- if max_elapsed.positive? && (elapsed > max_elapsed) && !opt[:took]
274
- puts boldwhite(item.title)
275
- human = elapsed.time_string(format: :natural)
276
- res = Prompt.yn(yellow("Did this actually take #{human}"), default_response: true)
277
- unless res
278
- new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
279
- raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed > 0
280
-
281
- opt[:took] = new_elapsed
282
- done_date = item.calculate_end_date(opt) if opt[:took]
283
- end
266
+ if tag =~ /^done$/ && opt[:date] && item.should_time?
267
+ max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
268
+ max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
269
+ elapsed = done_date - item.date
270
+
271
+ if max_elapsed.positive? && (elapsed > max_elapsed) && !opt[:took]
272
+ puts boldwhite(item.title)
273
+ human = elapsed.time_string(format: :natural)
274
+ res = Prompt.yn(yellow("Did this actually take #{human}"), default_response: true)
275
+ unless res
276
+ new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
277
+ raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed > 0
278
+
279
+ opt[:took] = new_elapsed
280
+ done_date = item.calculate_end_date(opt) if opt[:took]
284
281
  end
285
282
  end
283
+ end
286
284
 
287
- if opt[:remove] || opt[:rename] || opt[:value]
288
- rename_to = nil
289
- if opt[:value]
290
- rename_to = tag
291
- elsif opt[:rename]
292
- rename_to = tag
293
- tag = opt[:rename]
294
- end
295
- old_title = item.title.dup
296
- force = opt[:value].nil? ? false : true
297
- item.title.tag!(tag, remove: opt[:remove], rename_to: rename_to, regex: opt[:regex], value: opt[:value], force: force)
298
- if old_title != item.title
299
- removed << tag
300
- added << rename_to if rename_to
301
- else
302
- logger.count(:skipped, level: :debug)
303
- end
285
+ if opt[:remove] || opt[:rename] || opt[:value]
286
+ rename_to = nil
287
+ if opt[:value]
288
+ rename_to = tag
289
+ elsif opt[:rename]
290
+ rename_to = tag
291
+ tag = opt[:rename]
292
+ end
293
+ old_title = item.title.dup
294
+ force = opt[:value].nil? ? false : true
295
+ item.title.tag!(tag, remove: opt[:remove], rename_to: rename_to, regex: opt[:regex], value: opt[:value], force: force)
296
+ if old_title != item.title
297
+ removed << tag
298
+ added << rename_to if rename_to
304
299
  else
305
- old_title = item.title.dup
306
- should_date = opt[:date] && item.should_time?
307
- item.title.tag!('done', remove: true) if tag =~ /done/ && (!should_date || opt[:update])
308
- item.title.tag!(tag, value: should_date ? done_date.strftime('%F %R') : nil)
309
- added << tag if old_title != item.title
300
+ logger.count(:skipped, level: :debug)
310
301
  end
302
+ else
303
+ old_title = item.title.dup
304
+ should_date = opt[:date] && item.should_time?
305
+ item.title.tag!('done', remove: true) if tag =~ /done/ && (!should_date || opt[:update])
306
+ item.title.tag!(tag, value: should_date ? done_date.strftime('%F %R') : nil)
307
+ added << tag if old_title != item.title
311
308
  end
312
309
  end
310
+ end
313
311
 
314
- logger.log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1)
315
-
316
- item.note.add(opt[:note]) if opt[:note]
312
+ logger.log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1)
317
313
 
318
- if opt[:archive] && opt[:section] != 'Archive' && (opt[:count]).positive?
319
- item.move_to('Archive', label: true)
320
- elsif opt[:archive] && opt[:count].zero?
321
- logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
322
- end
314
+ item.note.add(opt[:note]) if opt[:note]
323
315
 
324
- item.expand_date_tags(Doing.setting('date_tags'))
325
- Hooks.trigger :post_entry_updated, self, item, old_item
316
+ if opt[:archive] && opt[:section] != 'Archive' && (opt[:count]).positive?
317
+ item.move_to('Archive', label: true)
318
+ elsif opt[:archive] && opt[:count].zero?
319
+ logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
326
320
  end
327
321
 
328
- write(@doing_file)
322
+ item.expand_date_tags(Doing.setting('date_tags'))
323
+ Hooks.trigger :post_entry_updated, self, item, old_item
329
324
  end
330
325
 
331
- ##
332
- ## Accepts one tag and the raw text of a new item if the
333
- ## passed tag is on any item, it's replaced with @done.
334
- ## if new_item is not nil, it's tagged with the passed
335
- ## tag and inserted. This is for use where only one
336
- ## instance of a given tag should exist (@meanwhile)
337
- ##
338
- ## @param target_tag [String] Tag to replace
339
- ## @param opt [Hash] Additional Options
340
- ##
341
- ## @option opt :section [String] target section
342
- ## @option opt :archive [Boolean] archive old item
343
- ## @option opt :back [Date] backdate new item
344
- ## @option opt :new_item [String] content to use for new item
345
- ## @option opt :note [Array] note content for new item
346
- def stop_start(target_tag, opt)
347
- opt ||= {}
348
- tag = target_tag.dup
349
- opt[:section] ||= Doing.setting('current_section')
350
- opt[:archive] ||= false
351
- opt[:back] ||= Time.now
352
- opt[:new_item] ||= false
353
- opt[:note] ||= false
354
-
355
- opt[:section] = guess_section(opt[:section])
356
-
357
- tag.sub!(/^@/, '')
358
-
359
- found_items = 0
360
-
361
- @content.each_with_index do |item, i|
362
- old_item = i.clone
363
- next unless item.section == opt[:section] || opt[:section] =~ /all/i
364
-
365
- next unless item.title =~ /@#{tag}/
366
-
367
- item.title.add_tags!([tag, 'done'], remove: true)
368
- item.tag('done', value: opt[:back].strftime('%F %R'))
369
-
370
- found_items += 1
371
-
372
- if opt[:archive] && opt[:section] != 'Archive'
373
- item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})")
374
- item.move_to('Archive', label: false, log: false)
375
- logger.count(:completed_archived)
376
- logger.info('Completed/archived:', item.title)
377
- else
378
- logger.count(:completed)
379
- logger.info('Completed:', item.title)
380
- end
381
- Hooks.trigger :post_entry_updated, self, item, old_item
382
- end
326
+ write(@doing_file)
327
+ end
383
328
 
329
+ ##
330
+ ## Accepts one tag and the raw text of a new item if the
331
+ ## passed tag is on any item, it's replaced with @done.
332
+ ## if new_item is not nil, it's tagged with the passed
333
+ ## tag and inserted. This is for use where only one
334
+ ## instance of a given tag should exist (@meanwhile)
335
+ ##
336
+ ## @param target_tag [String] Tag to replace
337
+ ## @param opt [Hash] Additional Options
338
+ ##
339
+ ## @option opt :section [String] target section
340
+ ## @option opt :archive [Boolean] archive old item
341
+ ## @option opt :back [Date] backdate new item
342
+ ## @option opt :new_item [String] content to use for new item
343
+ ## @option opt :note [Array] note content for new item
344
+ def stop_start(target_tag, opt)
345
+ opt ||= {}
346
+ tag = target_tag.dup
347
+ opt[:section] ||= Doing.setting('current_section')
348
+ opt[:archive] ||= false
349
+ opt[:back] ||= Time.now
350
+ opt[:new_item] ||= false
351
+ opt[:note] ||= false
352
+
353
+ opt[:section] = guess_section(opt[:section])
354
+
355
+ tag.sub!(/^@/, '')
356
+
357
+ found_items = 0
358
+
359
+ @content.each_with_index do |item, i|
360
+ old_item = i.clone
361
+ next unless item.section == opt[:section] || opt[:section] =~ /all/i
362
+
363
+ next unless item.title =~ /@#{tag}/
364
+
365
+ item.title.add_tags!([tag, 'done'], remove: true)
366
+ item.tag('done', value: opt[:back].strftime('%F %R'))
367
+
368
+ found_items += 1
369
+
370
+ if opt[:archive] && opt[:section] != 'Archive'
371
+ item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})")
372
+ item.move_to('Archive', label: false, log: false)
373
+ logger.count(:completed_archived)
374
+ logger.info('Completed/archived:', item.title)
375
+ else
376
+ logger.count(:completed)
377
+ logger.info('Completed:', item.title)
378
+ end
379
+ Hooks.trigger :post_entry_updated, self, item, old_item
380
+ end
384
381
 
385
- logger.debug('Skipped:', "No active @#{tag} tasks found.") if found_items.zero?
386
382
 
387
- if opt[:new_item]
388
- date, title, note = format_input(opt[:new_item])
389
- opt[:back] = date unless date.nil?
390
- note.add(opt[:note]) if opt[:note]
391
- title.tag!(tag)
392
- add_item(title.cap_first, opt[:section], { note: note, back: opt[:back] })
393
- end
383
+ logger.debug('Skipped:', "No active @#{tag} tasks found.") if found_items.zero?
394
384
 
395
- write(@doing_file)
385
+ if opt[:new_item]
386
+ date, title, note = format_input(opt[:new_item])
387
+ opt[:back] = date unless date.nil?
388
+ note.add(opt[:note]) if opt[:note]
389
+ title.tag!(tag)
390
+ add_item(title.cap_first, opt[:section], { note: note, back: opt[:back] })
396
391
  end
397
392
 
398
- ##
399
- ## Delete a set of items from the main index
400
- ##
401
- ## @param items [Array] The items to delete
402
- ## @param force [Boolean] Force deletion without confirmation
403
- ##
404
- def delete_items(items, force: false)
405
- items.slice(0, 5).each { |i| puts i.to_pretty } unless force
406
- puts softpurple("+ #{items.size - 5} additional #{'item'.to_p(items.size - 5)}") if items.size > 5 && !force
407
-
408
- res = force ? true : Prompt.yn("Delete #{items.size} #{'item'.to_p(items.size)}?", default_response: 'y')
409
- return unless res
410
-
411
- items.each { |i| Hooks.trigger :post_entry_removed, self, @content.delete_item(i, single: items.count == 1) }
412
- # write(@doing_file)
393
+ write(@doing_file)
394
+ end
395
+
396
+ ##
397
+ ## Delete a set of items from the main index
398
+ ##
399
+ ## @param items [Array] The items to delete
400
+ ## @param force [Boolean] Force deletion without confirmation
401
+ ##
402
+ def delete_items(items, force: false)
403
+ items.slice(0, 5).each { |i| puts i.to_pretty } unless force
404
+ puts softpurple("+ #{items.size - 5} additional #{'item'.to_p(items.size - 5)}") if items.size > 5 && !force
405
+
406
+ res = force ? true : Prompt.yn("Delete #{items.size} #{'item'.to_p(items.size)}?", default_response: 'y')
407
+ return unless res
408
+
409
+ items.each { |i| Hooks.trigger :post_entry_removed, self, @content.delete_item(i, single: items.count == 1) }
410
+ # write(@doing_file)
411
+ end
412
+
413
+ ##
414
+ ## Move entries from a section to Archive or other specified
415
+ ## section
416
+ ##
417
+ ## @param section [String] The source section
418
+ ## @param options [Hash] Options
419
+ ##
420
+ def archive(section = Doing.setting('current_section'), options)
421
+ options ||= {}
422
+ count = options[:keep] || 0
423
+ destination = options[:destination] || 'Archive'
424
+ tags = options[:tags] || []
425
+ bool = options[:bool] || :and
426
+
427
+ section = choose_section if section.nil? || section =~ /choose/i
428
+ archive_all = section =~ /^all$/i # && !(tags.nil? || tags.empty?)
429
+ section = guess_section(section) unless archive_all
430
+
431
+ @content.add_section(destination, log: true)
432
+ # add_section(Section.new('Archive')) if destination =~ /^archive$/i && !@content.section?('Archive')
433
+
434
+ destination = guess_section(destination)
435
+
436
+ if @content.section?(destination) && (@content.section?(section) || archive_all)
437
+ do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before], after: options[:after], from: options[:from] })
438
+ write(doing_file)
439
+ else
440
+ raise InvalidArgument, 'Either source or destination does not exist'
413
441
  end
442
+ end
414
443
 
415
- ##
416
- ## Move entries from a section to Archive or other specified
417
- ## section
418
- ##
419
- ## @param section [String] The source section
420
- ## @param options [Hash] Options
421
- ##
422
- def archive(section = Doing.setting('current_section'), options)
423
- options ||= {}
424
- count = options[:keep] || 0
425
- destination = options[:destination] || 'Archive'
426
- tags = options[:tags] || []
427
- bool = options[:bool] || :and
428
-
429
- section = choose_section if section.nil? || section =~ /choose/i
430
- archive_all = section =~ /^all$/i # && !(tags.nil? || tags.empty?)
431
- section = guess_section(section) unless archive_all
432
-
433
- @content.add_section(destination, log: true)
434
- # add_section(Section.new('Archive')) if destination =~ /^archive$/i && !@content.section?('Archive')
435
-
436
- destination = guess_section(destination)
437
-
438
- if @content.section?(destination) && (@content.section?(section) || archive_all)
439
- do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before], after: options[:after], from: options[:from] })
440
- write(doing_file)
441
- else
442
- raise InvalidArgument, 'Either source or destination does not exist'
444
+ ##
445
+ ## Uses 'autotag' configuration to turn keywords into tags for time tracking.
446
+ ## Does not repeat tags in a title, and only converts the first instance of an
447
+ ## untagged keyword
448
+ ##
449
+ ## @param string [String] The text to tag
450
+ ##
451
+ def autotag(string)
452
+ return unless string
453
+ return string unless Doing.auto_tag
454
+
455
+ original = string.dup
456
+ text = string.dup
457
+
458
+ current_tags = text.scan(/@\w+/).map { |t| t.sub(/^@/, '') }
459
+ tagged = {
460
+ whitelisted: [],
461
+ synonyms: [],
462
+ transformed: [],
463
+ replaced: []
464
+ }
465
+
466
+ Doing.setting('autotag.whitelist').each do |tag|
467
+ next if text =~ /@#{tag}\b/i
468
+
469
+ text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m|
470
+ m.downcase! unless tag =~ /[A-Z]/
471
+ tagged[:whitelisted].push(m)
472
+ "@#{m}"
443
473
  end
444
474
  end
445
475
 
446
- ##
447
- ## Uses 'autotag' configuration to turn keywords into tags for time tracking.
448
- ## Does not repeat tags in a title, and only converts the first instance of an
449
- ## untagged keyword
450
- ##
451
- ## @param string [String] The text to tag
452
- ##
453
- def autotag(string)
454
- return unless string
455
- return string unless Doing.auto_tag
456
-
457
- original = string.dup
458
- text = string.dup
459
-
460
- current_tags = text.scan(/@\w+/).map { |t| t.sub(/^@/, '') }
461
- tagged = {
462
- whitelisted: [],
463
- synonyms: [],
464
- transformed: [],
465
- replaced: []
466
- }
467
-
468
- Doing.setting('autotag.whitelist').each do |tag|
469
- next if text =~ /@#{tag}\b/i
470
-
471
- text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m|
472
- m.downcase! unless tag =~ /[A-Z]/
473
- tagged[:whitelisted].push(m)
474
- "@#{m}"
476
+ Doing.setting('autotag.synonyms').each do |tag, v|
477
+ v.each do |word|
478
+ word = word.wildcard_to_rx
479
+ next unless text =~ /\b#{word}\b/i
480
+
481
+ unless current_tags.include?(tag) || tagged[:whitelisted].include?(tag)
482
+ tagged[:synonyms].push(tag)
483
+ tagged[:synonyms] = tagged[:synonyms].uniq
475
484
  end
476
485
  end
486
+ end
477
487
 
478
- Doing.setting('autotag.synonyms').each do |tag, v|
479
- v.each do |word|
480
- word = word.wildcard_to_rx
481
- next unless text =~ /\b#{word}\b/i
488
+ if Doing.setting('autotag.transform')
489
+ Doing.setting('autotag.transform').each do |tag|
490
+ next unless tag =~ /\S+:\S+/
482
491
 
483
- unless current_tags.include?(tag) || tagged[:whitelisted].include?(tag)
484
- tagged[:synonyms].push(tag)
485
- tagged[:synonyms] = tagged[:synonyms].uniq
486
- end
492
+ if tag =~ /::/
493
+ rx, r = tag.split(/::/)
494
+ else
495
+ rx, r = tag.split(/:/)
487
496
  end
488
- end
489
497
 
490
- if Doing.setting('autotag.transform')
491
- Doing.setting('autotag.transform').each do |tag|
492
- next unless tag =~ /\S+:\S+/
498
+ flag_rx = %r{/([r]+)$}
499
+ if r =~ flag_rx
500
+ flags = r.match(flag_rx)[1].split(//)
501
+ r.sub!(flag_rx, '')
502
+ end
503
+ r.gsub!(/\$/, '\\')
504
+ rx.sub!(/^@?/, '@')
505
+ regex = Regexp.new("(?<= |\\A)#{rx}(?= |\\Z)")
493
506
 
494
- if tag =~ /::/
495
- rx, r = tag.split(/::/)
496
- else
497
- rx, r = tag.split(/:/)
498
- end
507
+ text.sub!(regex) do
508
+ m = Regexp.last_match
509
+ new_tag = r
499
510
 
500
- flag_rx = %r{/([r]+)$}
501
- if r =~ flag_rx
502
- flags = r.match(flag_rx)[1].split(//)
503
- r.sub!(flag_rx, '')
511
+ m.to_a.slice(1, m.length - 1).each_with_index do |v, idx|
512
+ new_tag.gsub!("\\#{idx + 1}", v)
504
513
  end
505
- r.gsub!(/\$/, '\\')
506
- rx.sub!(/^@?/, '@')
507
- regex = Regexp.new("(?<= |\\A)#{rx}(?= |\\Z)")
508
-
509
- text.sub!(regex) do
510
- m = Regexp.last_match
511
- new_tag = r
512
-
513
- m.to_a.slice(1, m.length - 1).each_with_index do |v, idx|
514
- new_tag.gsub!("\\#{idx + 1}", v)
515
- end
516
- # Replace original tag if /r
517
- if flags&.include?('r')
518
- tagged[:replaced].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
519
- new_tag.split(/ /).map { |t| t.sub(/^@?/, '@') }.join(' ')
520
- else
521
- tagged[:transformed].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
522
- tagged[:transformed] = tagged[:transformed].uniq
523
- m[0]
524
- end
514
+ # Replace original tag if /r
515
+ if flags&.include?('r')
516
+ tagged[:replaced].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
517
+ new_tag.split(/ /).map { |t| t.sub(/^@?/, '@') }.join(' ')
518
+ else
519
+ tagged[:transformed].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
520
+ tagged[:transformed] = tagged[:transformed].uniq
521
+ m[0]
525
522
  end
526
523
  end
527
524
  end
525
+ end
528
526
 
529
- logger.debug('Autotag:', "whitelisted tags: #{tagged[:whitelisted].log_tags}") unless tagged[:whitelisted].empty?
530
- logger.debug('Autotag:', "synonyms: #{tagged[:synonyms].log_tags}") unless tagged[:synonyms].empty?
531
- logger.debug('Autotag:', "transforms: #{tagged[:transformed].log_tags}") unless tagged[:transformed].empty?
532
- logger.debug('Autotag:', "transform replaced: #{tagged[:replaced].log_tags}") unless tagged[:replaced].empty?
533
-
534
- tail_tags = tagged[:synonyms].concat(tagged[:transformed])
535
- tail_tags.sort!
536
- tail_tags.uniq!
527
+ logger.debug('Autotag:', "whitelisted tags: #{tagged[:whitelisted].log_tags}") unless tagged[:whitelisted].empty?
528
+ logger.debug('Autotag:', "synonyms: #{tagged[:synonyms].log_tags}") unless tagged[:synonyms].empty?
529
+ logger.debug('Autotag:', "transforms: #{tagged[:transformed].log_tags}") unless tagged[:transformed].empty?
530
+ logger.debug('Autotag:', "transform replaced: #{tagged[:replaced].log_tags}") unless tagged[:replaced].empty?
537
531
 
538
- text.add_tags!(tail_tags) unless tail_tags.empty?
532
+ tail_tags = tagged[:synonyms].concat(tagged[:transformed])
533
+ tail_tags.sort!
534
+ tail_tags.uniq!
539
535
 
540
- if text == original
541
- logger.debug('Autotag:', "no change to \"#{text.strip}\"")
542
- else
543
- new_tags = tagged[:whitelisted].concat(tail_tags).concat(tagged[:replaced])
544
- logger.debug('Autotag:', "added #{new_tags.log_tags} to \"#{text.strip}\"")
545
- logger.count(:autotag, level: :info, count: 1, message: 'autotag updated %count %items')
546
- end
536
+ text.add_tags!(tail_tags) unless tail_tags.empty?
547
537
 
548
- text.dedup_tags
538
+ if text == original
539
+ logger.debug('Autotag:', "no change to \"#{text.strip}\"")
540
+ else
541
+ new_tags = tagged[:whitelisted].concat(tail_tags).concat(tagged[:replaced])
542
+ logger.debug('Autotag:', "added #{new_tags.log_tags} to \"#{text.strip}\"")
543
+ logger.count(:autotag, level: :info, count: 1, message: 'autotag updated %count %items')
549
544
  end
550
545
 
551
- private
546
+ text.dedup_tags
547
+ end
552
548
 
553
- ##
554
- ## Helper function, performs the actual archiving
555
- ##
556
- ## @param section [String] The source section
557
- ## @param destination [String] The destination
558
- ## section
559
- ## @param opt [Hash] Additional Options
560
- ## @api private
561
- def do_archive(section, destination, opt)
562
- opt ||= {}
563
- count = opt[:count] || 0
564
- tags = opt[:tags] || []
565
- bool = opt[:bool] || :and
566
- label = opt[:label] || true
549
+ private
567
550
 
568
- section = guess_section(section)
569
- destination = guess_section(destination)
551
+ ##
552
+ ## Helper function, performs the actual archiving
553
+ ##
554
+ ## @param section [String] The source section
555
+ ## @param destination [String] The destination
556
+ ## section
557
+ ## @param opt [Hash] Additional Options
558
+ ## @api private
559
+ def do_archive(section, destination, opt)
560
+ opt ||= {}
561
+ count = opt[:count] || 0
562
+ tags = opt[:tags] || []
563
+ bool = opt[:bool] || :and
564
+ label = opt[:label] || true
570
565
 
571
- section_items = @content.in_section(section)
572
- max = section_items.count - count.to_i
566
+ section = guess_section(section)
567
+ destination = guess_section(destination)
573
568
 
574
- opt[:after] = opt[:from][0] if opt[:from]
575
- opt[:before] = opt[:from][1] if opt[:from]
569
+ section_items = @content.in_section(section)
570
+ max = section_items.count - count.to_i
576
571
 
577
- time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
572
+ opt[:after] = opt[:from][0] if opt[:from]
573
+ opt[:before] = opt[:from][1] if opt[:from]
578
574
 
579
- if opt[:before].is_a?(String) && opt[:before] =~ time_rx
580
- opt[:before] = opt[:before].chronify(guess: :end, future: false)
581
- end
575
+ time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
582
576
 
583
- if opt[:after].is_a?(String) && opt[:after] =~ time_rx
584
- opt[:after] = opt[:after].chronify(guess: :begin, future: false)
585
- end
577
+ if opt[:before].is_a?(String) && opt[:before] =~ time_rx
578
+ opt[:before] = opt[:before].chronify(guess: :end, future: false)
579
+ end
586
580
 
587
- counter = 0
581
+ if opt[:after].is_a?(String) && opt[:after] =~ time_rx
582
+ opt[:after] = opt[:after].chronify(guess: :begin, future: false)
583
+ end
588
584
 
589
- @content.map do |item|
590
- break if counter >= max
585
+ counter = 0
591
586
 
592
- next if item.section.downcase == destination.downcase
587
+ @content.map do |item|
588
+ break if counter >= max
593
589
 
594
- next if item.section.downcase != section.downcase && section != /^all$/i
590
+ next if item.section.downcase == destination.downcase
595
591
 
596
- next if (opt[:before] && item.date > opt[:before]) || (opt[:after] && item.date < opt[:after])
592
+ next if item.section.downcase != section.downcase && section != /^all$/i
597
593
 
598
- next if (!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s))
594
+ next if (opt[:before] && item.date > opt[:before]) || (opt[:after] && item.date < opt[:after])
599
595
 
600
- counter += 1
601
- old_item = item.clone
602
- item.move_to(destination, label: label, log: false)
603
- Hooks.trigger :post_entry_updated, self, item, old_item
604
- item
605
- end
596
+ next if (!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s))
606
597
 
607
- if counter.positive?
608
- logger.count(destination == 'Archive' ? :archived : :moved,
609
- level: :info,
610
- count: counter,
611
- message: "%count %items from #{section} to #{destination}")
612
- else
613
- logger.info('Skipped:', 'No items were moved')
614
- end
598
+ counter += 1
599
+ old_item = item.clone
600
+ item.move_to(destination, label: label, log: false)
601
+ Hooks.trigger :post_entry_updated, self, item, old_item
602
+ item
603
+ end
604
+
605
+ if counter.positive?
606
+ logger.count(destination == 'Archive' ? :archived : :moved,
607
+ level: :info,
608
+ count: counter,
609
+ message: "%count %items from #{section} to #{destination}")
610
+ else
611
+ logger.info('Skipped:', 'No items were moved')
615
612
  end
616
613
  end
617
614
  end