doing 2.1.37 → 2.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +7 -1
- data/bin/commands/config.rb +43 -34
- data/bin/commands/done.rb +1 -18
- data/bin/commands/finish.rb +30 -25
- data/bin/commands/grep.rb +3 -14
- data/bin/commands/last.rb +2 -8
- data/bin/commands/meanwhile.rb +13 -6
- data/bin/commands/now.rb +2 -4
- data/bin/commands/on.rb +4 -15
- data/bin/commands/recent.rb +2 -8
- data/bin/commands/reset.rb +24 -1
- data/bin/commands/select.rb +1 -1
- data/bin/commands/show.rb +8 -16
- data/bin/commands/since.rb +1 -12
- data/bin/commands/today.rb +2 -13
- data/bin/commands/view.rb +1 -1
- data/bin/commands/yesterday.rb +2 -13
- data/bin/doing +41 -36
- data/docs/doc/Array.html +1 -1
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +166 -20
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +1 -1
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +7 -3
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +7 -3
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +10 -2
- data/docs/doc/Doing/Errors/HistoryLimitError.html +194 -0
- data/docs/doc/Doing/Errors/InvalidPlugin.html +194 -0
- data/docs/doc/Doing/Errors/MissingBackupFile.html +194 -0
- data/docs/doc/Doing/Errors/NoResults.html +10 -2
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +10 -2
- data/docs/doc/Doing/Errors/WrongCommand.html +10 -2
- data/docs/doc/Doing/Errors.html +9 -9
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +114 -1576
- data/docs/doc/Doing/Items.html +121 -5
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/Note.html +1 -1
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +2 -2
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +5 -5
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +197 -4033
- data/docs/doc/Doing.html +2 -2
- data/docs/doc/FalseClass.html +1 -1
- data/docs/doc/GLI/Commands/Help.html +1 -1
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Object.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +1 -1
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +26 -5
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +237 -709
- data/docs/doc/top-level-namespace.html +3 -3
- data/docs/index.md +1 -1
- data/doing.rdoc +54 -7
- data/lib/completion/_doing.zsh +6 -6
- data/lib/completion/doing.bash +10 -10
- data/lib/completion/doing.fish +8 -2
- data/lib/doing/add_options.rb +31 -1
- data/lib/doing/chronify/array.rb +68 -18
- data/lib/doing/chronify/string.rb +3 -1
- data/lib/doing/colors.rb +77 -30
- data/lib/doing/completion.rb +4 -5
- data/lib/doing/errors.rb +51 -35
- data/lib/doing/hooks.rb +3 -3
- data/lib/doing/item/dates.rb +112 -0
- data/lib/doing/item/query.rb +433 -0
- data/lib/doing/item/state.rb +59 -0
- data/lib/doing/item/tags.rb +87 -0
- data/lib/doing/item.rb +6 -537
- data/lib/doing/items.rb +39 -14
- data/lib/doing/plugin_manager.rb +3 -3
- data/lib/doing/plugins/export/template_export.rb +4 -4
- data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
- data/lib/doing/prompt.rb +6 -8
- data/lib/doing/string/tags.rb +8 -2
- data/lib/doing/util_backup.rb +6 -8
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/display.rb +399 -0
- data/lib/doing/wwid/editor.rb +214 -0
- data/lib/doing/wwid/filetools.rb +186 -0
- data/lib/doing/wwid/filter.rb +218 -0
- data/lib/doing/wwid/guess.rb +87 -0
- data/lib/doing/wwid/interactive.rb +385 -0
- data/lib/doing/wwid/modify.rb +618 -0
- data/lib/doing/wwid/tags.rb +54 -0
- data/lib/doing/wwid/timers.rb +345 -0
- data/lib/doing/wwid/wwidutil.rb +104 -0
- data/lib/doing/wwid.rb +31 -2308
- metadata +19 -2
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doing
|
|
4
|
+
class WWID
|
|
5
|
+
# Timer methods for WWID class
|
|
6
|
+
module Timers
|
|
7
|
+
##
|
|
8
|
+
## Get total elapsed time for all tags in
|
|
9
|
+
## selection
|
|
10
|
+
##
|
|
11
|
+
## @param format [String] return format (html,
|
|
12
|
+
## json, or text)
|
|
13
|
+
## @param sort_by [Symbol] Sort by :name or :time
|
|
14
|
+
## @param sort_order [Symbol] The sort order (:asc or :desc)
|
|
15
|
+
##
|
|
16
|
+
def tag_times(format: :text, sort_by: :time, sort_order: :asc)
|
|
17
|
+
return '' if @timers.empty?
|
|
18
|
+
|
|
19
|
+
max = @timers.keys.sort_by(&:length).reverse[0].length + 1
|
|
20
|
+
|
|
21
|
+
total = @timers.delete('All')
|
|
22
|
+
|
|
23
|
+
tags_data = @timers.delete_if { |_k, v| v.zero? }
|
|
24
|
+
sorted_tags_data = if sort_by.normalize_tag_sort == :name
|
|
25
|
+
tags_data.sort_by { |k, _v| k }
|
|
26
|
+
else
|
|
27
|
+
tags_data.sort_by { |_k, v| v }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sorted_tags_data.reverse! if sort_order.normalize_order == :asc
|
|
31
|
+
case format
|
|
32
|
+
when :html
|
|
33
|
+
|
|
34
|
+
output = <<EOHEAD
|
|
35
|
+
<table>
|
|
36
|
+
<caption id="tagtotals">Tag Totals</caption>
|
|
37
|
+
<colgroup>
|
|
38
|
+
<col style="text-align:left;"/>
|
|
39
|
+
<col style="text-align:left;"/>
|
|
40
|
+
</colgroup>
|
|
41
|
+
<thead>
|
|
42
|
+
<tr>
|
|
43
|
+
<th style="text-align:left;">project</th>
|
|
44
|
+
<th style="text-align:left;">time</th>
|
|
45
|
+
</tr>
|
|
46
|
+
</thead>
|
|
47
|
+
<tbody>
|
|
48
|
+
EOHEAD
|
|
49
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
50
|
+
if v.positive?
|
|
51
|
+
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: :clock)}</td></tr>\n"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
tail = <<EOTAIL
|
|
55
|
+
<tr>
|
|
56
|
+
<td style="text-align:left;" colspan="2"></td>
|
|
57
|
+
</tr>
|
|
58
|
+
</tbody>
|
|
59
|
+
<tfoot>
|
|
60
|
+
<tr>
|
|
61
|
+
<td style="text-align:left;"><strong>Total</strong></td>
|
|
62
|
+
<td style="text-align:left;">#{total.time_string(format: :clock)}</td>
|
|
63
|
+
</tr>
|
|
64
|
+
</tfoot>
|
|
65
|
+
</table>
|
|
66
|
+
EOTAIL
|
|
67
|
+
output + tail
|
|
68
|
+
when :markdown
|
|
69
|
+
pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
|
|
70
|
+
pad = 7 if pad < 7
|
|
71
|
+
output = <<~EOHEADER
|
|
72
|
+
| #{' ' * (pad - 7)}project | time |
|
|
73
|
+
| #{'-' * (pad - 1)}: | :------- |
|
|
74
|
+
EOHEADER
|
|
75
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
76
|
+
if v.positive?
|
|
77
|
+
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
tail = '[Tag Totals]'
|
|
81
|
+
output + tail
|
|
82
|
+
when :json
|
|
83
|
+
output = []
|
|
84
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
85
|
+
output << {
|
|
86
|
+
'tag' => k,
|
|
87
|
+
'seconds' => v,
|
|
88
|
+
'formatted' => v.time_string(format: :clock)
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
output
|
|
92
|
+
when :human
|
|
93
|
+
output = []
|
|
94
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
95
|
+
spacer = ''
|
|
96
|
+
(max - k.length).times do
|
|
97
|
+
spacer += ' '
|
|
98
|
+
end
|
|
99
|
+
output.push("┃ #{spacer}#{k}:#{v.time_string(format: :hm)} ┃")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
header = '┏━━ Tag Totals '
|
|
103
|
+
(max - 2).times { header += '━' }
|
|
104
|
+
header += '┓'
|
|
105
|
+
footer = '┗'
|
|
106
|
+
(max + 12).times { footer += '━' }
|
|
107
|
+
footer += '┛'
|
|
108
|
+
divider = '┣'
|
|
109
|
+
(max + 12).times { divider += '━' }
|
|
110
|
+
divider += '┫'
|
|
111
|
+
output = output.empty? ? '' : "\n#{header}\n#{output.join("\n")}"
|
|
112
|
+
output += "\n#{divider}"
|
|
113
|
+
spacer = ''
|
|
114
|
+
(max - 6).times do
|
|
115
|
+
spacer += ' '
|
|
116
|
+
end
|
|
117
|
+
total_time = total.time_string(format: :hm)
|
|
118
|
+
total = "┃ #{spacer}total: "
|
|
119
|
+
total += total_time
|
|
120
|
+
total += ' ┃'
|
|
121
|
+
output += "\n#{total}"
|
|
122
|
+
output += "\n#{footer}"
|
|
123
|
+
output
|
|
124
|
+
else
|
|
125
|
+
output = []
|
|
126
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
127
|
+
spacer = ''
|
|
128
|
+
(max - k.length).times do
|
|
129
|
+
spacer += ' '
|
|
130
|
+
end
|
|
131
|
+
output.push("#{k}:#{spacer}#{v.time_string(format: :clock)}")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
output = output.empty? ? '' : "\n--- Tag Totals ---\n#{output.join("\n")}"
|
|
135
|
+
output += "\n\nTotal tracked: #{total.time_string(format: :clock)}\n"
|
|
136
|
+
output
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
##
|
|
141
|
+
## Gets the interval between entry's start
|
|
142
|
+
## date and @done date
|
|
143
|
+
##
|
|
144
|
+
## @param item [Item] The entry
|
|
145
|
+
## @param formatted [Boolean] Return human readable
|
|
146
|
+
## time (default seconds)
|
|
147
|
+
## @param record [Boolean] Add the interval to the
|
|
148
|
+
## total for each tag
|
|
149
|
+
##
|
|
150
|
+
## @return Interval in seconds, or [d, h, m] array if
|
|
151
|
+
## formatted is true. False if no end date or
|
|
152
|
+
## interval is 0
|
|
153
|
+
##
|
|
154
|
+
def get_interval(item, formatted: true, record: true)
|
|
155
|
+
if item.interval
|
|
156
|
+
seconds = item.interval
|
|
157
|
+
record_tag_times(item, seconds) if record
|
|
158
|
+
return seconds.positive? ? seconds : false unless formatted
|
|
159
|
+
|
|
160
|
+
return seconds.positive? ? seconds.time_string(format: :clock) : false
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
false
|
|
164
|
+
end##
|
|
165
|
+
## Get total elapsed time for all tags in
|
|
166
|
+
## selection
|
|
167
|
+
##
|
|
168
|
+
## @param format [String] return format (html,
|
|
169
|
+
## json, or text)
|
|
170
|
+
## @param sort_by [Symbol] Sort by :name or :time
|
|
171
|
+
## @param sort_order [Symbol] The sort order (:asc or :desc)
|
|
172
|
+
##
|
|
173
|
+
def tag_times(format: :text, sort_by: :time, sort_order: :asc)
|
|
174
|
+
return '' if @timers.empty?
|
|
175
|
+
|
|
176
|
+
max = @timers.keys.sort_by(&:length).reverse[0].length + 1
|
|
177
|
+
|
|
178
|
+
total = @timers.delete('All')
|
|
179
|
+
|
|
180
|
+
tags_data = @timers.delete_if { |_k, v| v.zero? }
|
|
181
|
+
sorted_tags_data = if sort_by.normalize_tag_sort == :name
|
|
182
|
+
tags_data.sort_by { |k, _v| k }
|
|
183
|
+
else
|
|
184
|
+
tags_data.sort_by { |_k, v| v }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
sorted_tags_data.reverse! if sort_order.normalize_order == :asc
|
|
188
|
+
case format
|
|
189
|
+
when :html
|
|
190
|
+
|
|
191
|
+
output = <<EOHEAD
|
|
192
|
+
<table>
|
|
193
|
+
<caption id="tagtotals">Tag Totals</caption>
|
|
194
|
+
<colgroup>
|
|
195
|
+
<col style="text-align:left;"/>
|
|
196
|
+
<col style="text-align:left;"/>
|
|
197
|
+
</colgroup>
|
|
198
|
+
<thead>
|
|
199
|
+
<tr>
|
|
200
|
+
<th style="text-align:left;">project</th>
|
|
201
|
+
<th style="text-align:left;">time</th>
|
|
202
|
+
</tr>
|
|
203
|
+
</thead>
|
|
204
|
+
<tbody>
|
|
205
|
+
EOHEAD
|
|
206
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
207
|
+
if v.positive?
|
|
208
|
+
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: :clock)}</td></tr>\n"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
tail = <<EOTAIL
|
|
212
|
+
<tr>
|
|
213
|
+
<td style="text-align:left;" colspan="2"></td>
|
|
214
|
+
</tr>
|
|
215
|
+
</tbody>
|
|
216
|
+
<tfoot>
|
|
217
|
+
<tr>
|
|
218
|
+
<td style="text-align:left;"><strong>Total</strong></td>
|
|
219
|
+
<td style="text-align:left;">#{total.time_string(format: :clock)}</td>
|
|
220
|
+
</tr>
|
|
221
|
+
</tfoot>
|
|
222
|
+
</table>
|
|
223
|
+
EOTAIL
|
|
224
|
+
output + tail
|
|
225
|
+
when :markdown
|
|
226
|
+
pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
|
|
227
|
+
pad = 7 if pad < 7
|
|
228
|
+
output = <<~EOHEADER
|
|
229
|
+
| #{' ' * (pad - 7)}project | time |
|
|
230
|
+
| #{'-' * (pad - 1)}: | :------- |
|
|
231
|
+
EOHEADER
|
|
232
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
233
|
+
if v.positive?
|
|
234
|
+
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
tail = '[Tag Totals]'
|
|
238
|
+
output + tail
|
|
239
|
+
when :json
|
|
240
|
+
output = []
|
|
241
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
242
|
+
output << {
|
|
243
|
+
'tag' => k,
|
|
244
|
+
'seconds' => v,
|
|
245
|
+
'formatted' => v.time_string(format: :clock)
|
|
246
|
+
}
|
|
247
|
+
end
|
|
248
|
+
output
|
|
249
|
+
when :human
|
|
250
|
+
output = []
|
|
251
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
252
|
+
spacer = ''
|
|
253
|
+
(max - k.length).times do
|
|
254
|
+
spacer += ' '
|
|
255
|
+
end
|
|
256
|
+
output.push("┃ #{spacer}#{k}:#{v.time_string(format: :hm)} ┃")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
header = '┏━━ Tag Totals '
|
|
260
|
+
(max - 2).times { header += '━' }
|
|
261
|
+
header += '┓'
|
|
262
|
+
footer = '┗'
|
|
263
|
+
(max + 12).times { footer += '━' }
|
|
264
|
+
footer += '┛'
|
|
265
|
+
divider = '┣'
|
|
266
|
+
(max + 12).times { divider += '━' }
|
|
267
|
+
divider += '┫'
|
|
268
|
+
output = output.empty? ? '' : "\n#{header}\n#{output.join("\n")}"
|
|
269
|
+
output += "\n#{divider}"
|
|
270
|
+
spacer = ''
|
|
271
|
+
(max - 6).times do
|
|
272
|
+
spacer += ' '
|
|
273
|
+
end
|
|
274
|
+
total_time = total.time_string(format: :hm)
|
|
275
|
+
total = "┃ #{spacer}total: "
|
|
276
|
+
total += total_time
|
|
277
|
+
total += ' ┃'
|
|
278
|
+
output += "\n#{total}"
|
|
279
|
+
output += "\n#{footer}"
|
|
280
|
+
output
|
|
281
|
+
else
|
|
282
|
+
output = []
|
|
283
|
+
sorted_tags_data.reverse.each do |k, v|
|
|
284
|
+
spacer = ''
|
|
285
|
+
(max - k.length).times do
|
|
286
|
+
spacer += ' '
|
|
287
|
+
end
|
|
288
|
+
output.push("#{k}:#{spacer}#{v.time_string(format: :clock)}")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
output = output.empty? ? '' : "\n--- Tag Totals ---\n#{output.join("\n")}"
|
|
292
|
+
output += "\n\nTotal tracked: #{total.time_string(format: :clock)}\n"
|
|
293
|
+
output
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
##
|
|
298
|
+
## Gets the interval between entry's start
|
|
299
|
+
## date and @done date
|
|
300
|
+
##
|
|
301
|
+
## @param item [Item] The entry
|
|
302
|
+
## @param formatted [Boolean] Return human readable
|
|
303
|
+
## time (default seconds)
|
|
304
|
+
## @param record [Boolean] Add the interval to the
|
|
305
|
+
## total for each tag
|
|
306
|
+
##
|
|
307
|
+
## @return Interval in seconds, or [d, h, m] array if
|
|
308
|
+
## formatted is true. False if no end date or
|
|
309
|
+
## interval is 0
|
|
310
|
+
##
|
|
311
|
+
def get_interval(item, formatted: true, record: true)
|
|
312
|
+
if item.interval
|
|
313
|
+
seconds = item.interval
|
|
314
|
+
record_tag_times(item, seconds) if record
|
|
315
|
+
return seconds.positive? ? seconds : false unless formatted
|
|
316
|
+
|
|
317
|
+
return seconds.positive? ? seconds.time_string(format: :clock) : false
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
false
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private
|
|
324
|
+
|
|
325
|
+
##
|
|
326
|
+
## Record times for item tags
|
|
327
|
+
##
|
|
328
|
+
## @param item [Item] The item to record
|
|
329
|
+
##
|
|
330
|
+
def record_tag_times(item, seconds)
|
|
331
|
+
item_hash = "#{item.date.strftime('%s')}#{item.title}#{item.section}"
|
|
332
|
+
return if @recorded_items.include?(item_hash)
|
|
333
|
+
item.title.scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
|
|
334
|
+
k = m[0] == 'done' ? 'All' : m[0].downcase
|
|
335
|
+
if @timers.key?(k)
|
|
336
|
+
@timers[k] += seconds
|
|
337
|
+
else
|
|
338
|
+
@timers[k] = seconds
|
|
339
|
+
end
|
|
340
|
+
@recorded_items.push(item_hash)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doing
|
|
4
|
+
class WWID
|
|
5
|
+
# Util methods for WWID class
|
|
6
|
+
module WWIDUtil
|
|
7
|
+
##
|
|
8
|
+
## Remove items from an array that already exist in
|
|
9
|
+
## @content based on start and end times
|
|
10
|
+
##
|
|
11
|
+
## @param items [Array] The items to
|
|
12
|
+
## deduplicate
|
|
13
|
+
## @param no_overlap [Boolean] Remove items with
|
|
14
|
+
## overlapping time spans
|
|
15
|
+
##
|
|
16
|
+
def dedup(items, no_overlap: false)
|
|
17
|
+
items.delete_if do |item|
|
|
18
|
+
duped = false
|
|
19
|
+
@content.each do |comp|
|
|
20
|
+
duped = no_overlap ? item.overlapping_time?(comp) : item.same_time?(comp)
|
|
21
|
+
break if duped
|
|
22
|
+
end
|
|
23
|
+
logger.count(:skipped, level: :debug, message: '%count overlapping %items') if duped
|
|
24
|
+
# logger.log_now(:debug, 'Skipped:', "overlapping entry: #{item.title}") if duped
|
|
25
|
+
duped
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
## Imports external entries
|
|
31
|
+
##
|
|
32
|
+
## @param paths [String] Path to JSON report file
|
|
33
|
+
## @param opt [Hash] Additional Options
|
|
34
|
+
##
|
|
35
|
+
def import(paths, opt)
|
|
36
|
+
opt ||= {}
|
|
37
|
+
Plugins.plugins[:import].each do |_, options|
|
|
38
|
+
next unless opt[:type] =~ /^(#{options[:trigger].normalize_trigger})$/i
|
|
39
|
+
|
|
40
|
+
if paths.count.positive?
|
|
41
|
+
paths.each do |path|
|
|
42
|
+
options[:class].import(self, path, options: opt)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
options[:class].import(self, nil, options: opt)
|
|
46
|
+
end
|
|
47
|
+
break
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
## Load configuration files and updated the @settings
|
|
53
|
+
## attribute with a Doing::Configuration object
|
|
54
|
+
##
|
|
55
|
+
## @param filename [String] (optional) path to
|
|
56
|
+
## alternative config file
|
|
57
|
+
##
|
|
58
|
+
def configure(filename = nil)
|
|
59
|
+
logger.benchmark(:configure, :start)
|
|
60
|
+
|
|
61
|
+
if filename
|
|
62
|
+
Doing.config_with(filename, { ignore_local: true })
|
|
63
|
+
elsif ENV['DOING_CONFIG']
|
|
64
|
+
Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
logger.benchmark(:configure, :finish)
|
|
68
|
+
|
|
69
|
+
Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
## Get difference between current content and last backup
|
|
74
|
+
##
|
|
75
|
+
## @param filename [String] The file path
|
|
76
|
+
##
|
|
77
|
+
def get_diff(filename = nil)
|
|
78
|
+
configure if Doing.settings.nil?
|
|
79
|
+
|
|
80
|
+
filename ||= Doing.setting('doing_file')
|
|
81
|
+
init_doing_file(filename)
|
|
82
|
+
current_content = @content.clone
|
|
83
|
+
backup_file = Util::Backup.last_backup(filename, count: 1)
|
|
84
|
+
raise DoingRuntimeError, 'No undo history to diff' if backup_file.nil?
|
|
85
|
+
|
|
86
|
+
backup = WWID.new
|
|
87
|
+
backup.config = Doing.settings
|
|
88
|
+
backup.init_doing_file(backup_file)
|
|
89
|
+
current_content.diff(backup.content)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
## Return a hash of changes between initial file read
|
|
94
|
+
## and current Items object
|
|
95
|
+
##
|
|
96
|
+
## @return [Hash] Hash containing `added` and
|
|
97
|
+
## `removed` keys with arrays of Item
|
|
98
|
+
##
|
|
99
|
+
def changes
|
|
100
|
+
@content.diff(@initial_content)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|