doing 2.1.41 → 2.1.42
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/commands/again.rb +1 -3
- data/bin/commands/changes.rb +50 -34
- data/bin/commands/commands.rb +77 -52
- data/bin/commands/commands_accepting.rb +57 -53
- data/bin/commands/config.rb +2 -2
- data/bin/commands/finish.rb +94 -68
- data/bin/commands/flag.rb +5 -1
- data/bin/commands/now.rb +151 -107
- data/bin/commands/on.rb +7 -4
- data/bin/commands/undo.rb +4 -6
- data/docs/doc/Array.html +3 -2
- 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/ArrayNestedHash.html +1 -1
- data/docs/doc/Doing/ArrayTags.html +1 -1
- data/docs/doc/Doing/CSVExport.html +1 -1
- data/docs/doc/Doing/CalendarImport.html +1 -1
- data/docs/doc/Doing/Change.html +1 -1
- data/docs/doc/Doing/Changes.html +1 -1
- data/docs/doc/Doing/ChronifyArray.html +1 -1
- data/docs/doc/Doing/ChronifyNumeric.html +1 -1
- data/docs/doc/Doing/ChronifyString.html +1 -1
- data/docs/doc/Doing/Color.html +1 -1
- data/docs/doc/Doing/Completion/BashCompletions.html +1 -1
- data/docs/doc/Doing/Completion/FishCompletions.html +1 -1
- data/docs/doc/Doing/Completion/StringUtils.html +1 -1
- data/docs/doc/Doing/Completion/ZshCompletions.html +1 -1
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +3 -2
- data/docs/doc/Doing/DayOneRenderer.html +1 -1
- data/docs/doc/Doing/DayoneExport.html +1 -1
- data/docs/doc/Doing/DoingImport.html +1 -1
- data/docs/doc/Doing/Entry.html +1 -1
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
- data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
- data/docs/doc/Doing/Errors/MissingBackupFile.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/HTMLExport.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +1 -1
- data/docs/doc/Doing/ItemDates.html +1 -1
- data/docs/doc/Doing/ItemQuery.html +1 -1
- data/docs/doc/Doing/ItemState.html +1 -1
- data/docs/doc/Doing/ItemTags.html +1 -1
- data/docs/doc/Doing/Items.html +2 -1
- data/docs/doc/Doing/JSONExport.html +1 -1
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/MarkdownExport.html +1 -1
- data/docs/doc/Doing/Note.html +3 -2
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +181 -76
- data/docs/doc/Doing/Prompt.html +1 -1
- data/docs/doc/Doing/PromptChoose.html +1 -1
- data/docs/doc/Doing/PromptFZF.html +1 -1
- data/docs/doc/Doing/PromptInput.html +1 -1
- data/docs/doc/Doing/PromptSTD.html +1 -1
- data/docs/doc/Doing/PromptYN.html +1 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/StringHighlight.html +1 -1
- data/docs/doc/Doing/StringNormalize.html +1 -1
- data/docs/doc/Doing/StringQuery.html +1 -1
- data/docs/doc/Doing/StringTags.html +1 -1
- data/docs/doc/Doing/StringTransform.html +35 -1
- data/docs/doc/Doing/StringTruncate.html +1 -1
- data/docs/doc/Doing/StringURL.html +1 -1
- data/docs/doc/Doing/SymbolNormalize.html +1 -1
- data/docs/doc/Doing/TaskPaperExport.html +1 -1
- data/docs/doc/Doing/TemplateExport.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/TimingImport.html +1 -1
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +2 -156
- data/docs/doc/Doing/Util.html +66 -9
- data/docs/doc/Doing/Version.html +1 -1
- data/docs/doc/Doing/WWID.html +14 -1
- data/docs/doc/Doing.html +4 -4
- 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/Numeric.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 +2 -2
- 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 +16 -9
- 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 +289 -281
- data/docs/doc/top-level-namespace.html +9 -1
- data/doing.rdoc +7 -7
- data/lib/doing/add_options.rb +8 -0
- data/lib/doing/array/array.rb +2 -0
- data/lib/doing/array/cleanup.rb +31 -0
- data/lib/doing/configuration.rb +7 -3
- data/lib/doing/note.rb +1 -1
- data/lib/doing/pager.rb +9 -3
- data/lib/doing/plugin_manager.rb +30 -5
- data/lib/doing/prompt/choose.rb +1 -1
- data/lib/doing/prompt/input.rb +1 -1
- data/lib/doing/string/transform.rb +6 -0
- data/lib/doing/util.rb +12 -6
- data/lib/doing/util_backup.rb +55 -48
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/editor.rb +6 -3
- data/lib/doing/wwid/interactive.rb +10 -20
- data/lib/doing/wwid/modify.rb +2 -0
- data/lib/doing.rb +3 -3
- metadata +3 -2
@@ -150,12 +150,20 @@
|
|
150
150
|
<div class="discussion">
|
151
151
|
<p>Add presets of flags and switches to a command.</p>
|
152
152
|
|
153
|
+
<p>:output_template => --output, --config_template, --template</p>
|
154
|
+
|
153
155
|
<p>:add_entry => --noauto, --note, --ask, --editor, --back</p>
|
154
156
|
|
157
|
+
<p>:finish_entry => --at/finished, --from, --took</p>
|
158
|
+
|
159
|
+
<p>:time_display => --times, --duration, --totals, --tag_sort, --tag_order, --only_timed</p>
|
160
|
+
|
155
161
|
<p>:search => --search, --case, --exact</p>
|
156
162
|
|
157
163
|
<p>:tag_filter => --tag, --bool, --not, --val</p>
|
158
164
|
|
165
|
+
<p>:time_filter => --before, --after, --from</p>
|
166
|
+
|
159
167
|
<p>:date_filter => --before, --after, --from</p>
|
160
168
|
|
161
169
|
|
@@ -206,7 +214,7 @@
|
|
206
214
|
</div>
|
207
215
|
|
208
216
|
<div id="footer">
|
209
|
-
Generated on
|
217
|
+
Generated on Fri Mar 18 12:56:57 2022 by
|
210
218
|
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
211
219
|
0.9.27 (ruby-3.0.1).
|
212
220
|
</div>
|
data/doing.rdoc
CHANGED
@@ -5,7 +5,7 @@ record of what you've been doing, complete with tag-based time tracking. The
|
|
5
5
|
command line tool allows you to add entries, annotate with tags and notes, and
|
6
6
|
view your entries with myriad options, with a focus on a "natural" language syntax.
|
7
7
|
|
8
|
-
v2.1.
|
8
|
+
v2.1.42
|
9
9
|
|
10
10
|
=== Global Options
|
11
11
|
=== --config_file arg
|
@@ -435,8 +435,8 @@ Display a formatted list of changes in recent versions.
|
|
435
435
|
===== -l|--lookup VERSION
|
436
436
|
|
437
437
|
Look up a specific version. Specify versions as "MAJ.MIN.PATCH", MIN
|
438
|
-
|
439
|
-
|
438
|
+
and PATCH are optional. Use > or < to see all changes since or prior
|
439
|
+
to a version. Wildcards (*?) accepted unless using < or >.
|
440
440
|
|
441
441
|
[Default Value] None
|
442
442
|
[Must Match] (?-mix:^(?:(?:(?:[<>=]+|p(?:rior)|b(?:efore)|o(?:lder)|s(?:ince)|a(?:fter)|n(?:ewer))? *[0-9.*?]{1,10} *)+|(?:[\d.]+ *(?:-|to)+ *[0-9.]{1,10}))$)
|
@@ -445,7 +445,7 @@ Look up a specific version. Specify versions as "MAJ.MIN.PATCH", MIN
|
|
445
445
|
===== -s|--search arg
|
446
446
|
|
447
447
|
Show changelogs matching search terms (uses pattern-based searching).
|
448
|
-
|
448
|
+
Add slashes to search with regular expressions, e.g. `--search "/output.*flag/"`
|
449
449
|
|
450
450
|
[Default Value] None
|
451
451
|
|
@@ -816,8 +816,8 @@ Archive entries
|
|
816
816
|
|
817
817
|
===== --auto
|
818
818
|
Auto-generate finish dates from next entry's start time.
|
819
|
-
|
820
|
-
|
819
|
+
Automatically generate completion dates 1 minute before next item (in any section) began.
|
820
|
+
--auto overrides the --date and --back parameters.
|
821
821
|
|
822
822
|
|
823
823
|
|
@@ -1482,7 +1482,7 @@ Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]
|
|
1482
1482
|
===== --from TIME_RANGE
|
1483
1483
|
|
1484
1484
|
Set a start and optionally end time as a date range ("from 1pm to 2:30pm").
|
1485
|
-
|
1485
|
+
If an end time is provided, a dated @done tag will be added
|
1486
1486
|
|
1487
1487
|
[Default Value] None
|
1488
1488
|
|
data/lib/doing/add_options.rb
CHANGED
@@ -3,12 +3,20 @@
|
|
3
3
|
##
|
4
4
|
## Add presets of flags and switches to a command.
|
5
5
|
##
|
6
|
+
## :output_template => --output, --config_template, --template
|
7
|
+
##
|
6
8
|
## :add_entry => --noauto, --note, --ask, --editor, --back
|
7
9
|
##
|
10
|
+
## :finish_entry => --at/finished, --from, --took
|
11
|
+
##
|
12
|
+
## :time_display => --times, --duration, --totals, --tag_sort, --tag_order, --only_timed
|
13
|
+
##
|
8
14
|
## :search => --search, --case, --exact
|
9
15
|
##
|
10
16
|
## :tag_filter => --tag, --bool, --not, --val
|
11
17
|
##
|
18
|
+
## :time_filter => --before, --after, --from
|
19
|
+
##
|
12
20
|
## :date_filter => --before, --after, --from
|
13
21
|
##
|
14
22
|
## @param type [Symbol] The type
|
data/lib/doing/array/array.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Doing
|
2
|
+
module ArrayCleanup
|
3
|
+
##
|
4
|
+
## Like Array#compact -- removes nil items, but also
|
5
|
+
## removes empty strings, zero or negative numbers and FalseClass items
|
6
|
+
##
|
7
|
+
## @return [Array] Array without "bad" elements
|
8
|
+
##
|
9
|
+
def remove_bad
|
10
|
+
compact.map { |x| x.is_a?(String) ? x.strip : x }.select(&:good?)
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove_bad!
|
14
|
+
replace remove_empty
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
## Like Array#compact -- removes nil items, but also
|
19
|
+
## removes empty elements
|
20
|
+
##
|
21
|
+
## @return [Array] Array without empty elements
|
22
|
+
##
|
23
|
+
def remove_empty
|
24
|
+
compact.map { |x| x.is_a?(String) ? x.strip : x }.reject { |x| x.is_a?(String) ? x.empty? : false }
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove_empty!
|
28
|
+
replace remove_empty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/doing/configuration.rb
CHANGED
@@ -23,7 +23,8 @@ module Doing
|
|
23
23
|
'editors' => {
|
24
24
|
'default' => ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR'],
|
25
25
|
'doing_file' => nil,
|
26
|
-
'config' => nil
|
26
|
+
'config' => nil,
|
27
|
+
'pager' => nil
|
27
28
|
},
|
28
29
|
'plugins' => {
|
29
30
|
'plugin_path' => File.join(Util.user_home, '.config', 'doing', 'plugins'),
|
@@ -204,6 +205,7 @@ module Doing
|
|
204
205
|
real_path = []
|
205
206
|
unless keypath =~ /^[.*]?$/
|
206
207
|
paths = keypath.split(/[:.]/)
|
208
|
+
element_count = paths.count
|
207
209
|
while paths.length.positive? && !cfg.nil?
|
208
210
|
path = paths.shift
|
209
211
|
new_cfg = nil
|
@@ -220,6 +222,8 @@ module Doing
|
|
220
222
|
end
|
221
223
|
|
222
224
|
if new_cfg.nil?
|
225
|
+
return real_path if real_path[-1] == path && real_path.count == element_count
|
226
|
+
|
223
227
|
if distance < 5 && !create
|
224
228
|
return resolve_key_path(keypath, create: false, distance: distance + 1)
|
225
229
|
else
|
@@ -228,12 +232,12 @@ module Doing
|
|
228
232
|
|
229
233
|
resolved = real_path.count.positive? ? "Resolved #{real_path.join('.')}, but " : ''
|
230
234
|
Doing.logger.log_now(:warn, "#{resolved}#{path} is unknown")
|
231
|
-
new_path = [*real_path, path, *paths].join('.')
|
235
|
+
new_path = [*real_path, path, *paths].compact.join('.')
|
232
236
|
Doing.logger.log_now(:warn, "Continuing will create the path #{new_path}")
|
233
237
|
res = Prompt.yn('Key path not found, create it?', default_response: true)
|
234
238
|
raise InvalidArgument, 'Invalid key path' unless res
|
235
239
|
|
236
|
-
real_path.push(path).concat(paths)
|
240
|
+
real_path.push(path).concat(paths).compact!
|
237
241
|
Doing.logger.debug('Config:', "translated key path #{keypath} to #{real_path.join('.')}")
|
238
242
|
return real_path
|
239
243
|
end
|
data/lib/doing/note.rb
CHANGED
@@ -20,7 +20,7 @@ module Doing
|
|
20
20
|
##
|
21
21
|
## Add note contents, optionally replacing existing note
|
22
22
|
##
|
23
|
-
## @param note [Array] The note to add, can be
|
23
|
+
## @param note [Array|String|Note] The note to add, can be
|
24
24
|
## String, Array, or Note
|
25
25
|
## @param replace [Boolean] replace existing
|
26
26
|
## content
|
data/lib/doing/pager.rb
CHANGED
@@ -70,14 +70,20 @@ module Doing
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def pagers
|
73
|
-
[
|
74
|
-
|
73
|
+
[
|
74
|
+
Doing.setting('editors.pager'),
|
75
|
+
ENV['PAGER'],
|
76
|
+
'less -Xr',
|
77
|
+
ENV['GIT_PAGER'],
|
78
|
+
git_pager,
|
79
|
+
'more -r'
|
80
|
+
].remove_bad
|
75
81
|
end
|
76
82
|
|
77
83
|
def find_executable(*commands)
|
78
84
|
execs = commands.empty? ? pagers : commands
|
79
85
|
execs
|
80
|
-
.
|
86
|
+
.remove_bad.uniq
|
81
87
|
.find { |cmd| TTY::Which.exist?(cmd.split.first) }
|
82
88
|
end
|
83
89
|
|
data/lib/doing/plugin_manager.rb
CHANGED
@@ -4,10 +4,17 @@ module Doing
|
|
4
4
|
# Plugin handling
|
5
5
|
module Plugins
|
6
6
|
class << self
|
7
|
+
# Return the user's home directory
|
7
8
|
def user_home
|
8
9
|
@user_home ||= Util.user_home
|
9
10
|
end
|
10
11
|
|
12
|
+
# Storage for registered plugins. Hash with :import
|
13
|
+
# and :export keys containing hashes of available
|
14
|
+
# plugins.
|
15
|
+
#
|
16
|
+
# @return [Hash] registered plugins
|
17
|
+
#
|
11
18
|
def plugins
|
12
19
|
@plugins ||= {
|
13
20
|
import: {},
|
@@ -81,6 +88,17 @@ module Doing
|
|
81
88
|
Doing.logger.debug('Plugin Manager:', "Registered #{type} plugin \"#{title}\"")
|
82
89
|
end
|
83
90
|
|
91
|
+
##
|
92
|
+
## Verifies that a plugin is properly configured with
|
93
|
+
## necessary methods for its type. If the plugin fails
|
94
|
+
## validation, a PluginUncallable exception will be
|
95
|
+
## raised.
|
96
|
+
##
|
97
|
+
## @param title [String] The title
|
98
|
+
## @param type [Symbol] type, :import or
|
99
|
+
## :export
|
100
|
+
## @param klass [Class] Plugin class
|
101
|
+
##
|
84
102
|
def validate_plugin(title, type, klass)
|
85
103
|
type = valid_type(type)
|
86
104
|
if type == :import && !klass.respond_to?(:import)
|
@@ -122,8 +140,10 @@ module Doing
|
|
122
140
|
##
|
123
141
|
## List available plugins to stdout
|
124
142
|
##
|
125
|
-
## @param options
|
143
|
+
## @param options [Hash] additional options
|
126
144
|
##
|
145
|
+
## @option options :column [Boolean] display results in a single column
|
146
|
+
## @option options :type [String] Plugin type: all, import, or export
|
127
147
|
def list_plugins(options = {})
|
128
148
|
separator = options[:column] ? "\n" : "\t"
|
129
149
|
type = options[:type].nil? || options[:type] =~ /all/i ? 'all' : valid_type(options[:type])
|
@@ -144,9 +164,9 @@ module Doing
|
|
144
164
|
##
|
145
165
|
## Return array of available plugin names
|
146
166
|
##
|
147
|
-
## @param type Plugin type (:import, :export)
|
167
|
+
## @param type [Symbol] Plugin type (:import, :export)
|
148
168
|
##
|
149
|
-
## @return [Array
|
169
|
+
## @return [Array] Array of plugin names (String)
|
150
170
|
##
|
151
171
|
def available_plugins(type: :export)
|
152
172
|
type = valid_type(type)
|
@@ -159,7 +179,7 @@ module Doing
|
|
159
179
|
## @param type Plugin type (:import, :export)
|
160
180
|
## @param separator The separator to join names with
|
161
181
|
##
|
162
|
-
## @return [String] Plugin names
|
182
|
+
## @return [String] Plugin names joined with separator
|
163
183
|
##
|
164
184
|
def plugin_names(type: :export, separator: '|')
|
165
185
|
type = valid_type(type)
|
@@ -190,7 +210,7 @@ module Doing
|
|
190
210
|
## @param type [Symbol] Plugin type (:import,
|
191
211
|
## :export)
|
192
212
|
##
|
193
|
-
## @return [Array
|
213
|
+
## @return [Array] Array of template names (String)
|
194
214
|
##
|
195
215
|
def plugin_templates(type: :export)
|
196
216
|
type = valid_type(type)
|
@@ -236,6 +256,9 @@ module Doing
|
|
236
256
|
## @param trigger [String] The trigger to test
|
237
257
|
## @param type [Symbol] the plugin type
|
238
258
|
## (:import, :export)
|
259
|
+
## @param save_to [String] if a path is
|
260
|
+
## specified, write the template
|
261
|
+
## to that path. Nil for STDOUT
|
239
262
|
##
|
240
263
|
## @return [String] string content of template for trigger
|
241
264
|
##
|
@@ -255,6 +278,8 @@ module Doing
|
|
255
278
|
raise Errors::InvalidArgument, "No template type matched \"#{trigger}\""
|
256
279
|
end
|
257
280
|
|
281
|
+
private
|
282
|
+
|
258
283
|
def save_template(tpl, dir, filename)
|
259
284
|
dir = File.expand_path(dir)
|
260
285
|
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
data/lib/doing/prompt/choose.rb
CHANGED
@@ -104,8 +104,8 @@ module Doing
|
|
104
104
|
|
105
105
|
fzf_args.concat([%(--filter="#{query}"), opt.fetch(:sort) ? '' : '--no-sort'])
|
106
106
|
end
|
107
|
-
|
108
107
|
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
108
|
+
|
109
109
|
selected = []
|
110
110
|
res.split(/\n/).each do |item|
|
111
111
|
idx = item.match(/^ *(\d+)\)/)[1].to_i
|
data/lib/doing/prompt/input.rb
CHANGED
data/lib/doing/util.rb
CHANGED
@@ -47,13 +47,13 @@ module Doing
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
# Non-destructive version of deep_merge_hashes!
|
51
|
-
#
|
50
|
+
# Non-destructive version of deep_merge_hashes!
|
51
|
+
# @see {deep_merge_hashes!}
|
52
52
|
#
|
53
53
|
# @return the merged hashes.
|
54
54
|
#
|
55
|
-
# @param [Hash]
|
56
|
-
# @param [Hash]
|
55
|
+
# @param master_hash [Hash] The master hash
|
56
|
+
# @param other_hash [Hash] The other hash
|
57
57
|
#
|
58
58
|
def deep_merge_hashes(master_hash, other_hash)
|
59
59
|
deep_merge_hashes!(master_hash.clone, other_hash)
|
@@ -61,13 +61,19 @@ module Doing
|
|
61
61
|
|
62
62
|
# Merges a master hash with another hash, recursively.
|
63
63
|
#
|
64
|
-
#
|
65
|
-
#
|
64
|
+
# @param target [Hash] the "parent" hash whose
|
65
|
+
# values will be overridden
|
66
|
+
# @param overwrite [Hash] the other hash whose
|
67
|
+
# values will be persisted after
|
68
|
+
# the merge
|
66
69
|
#
|
67
70
|
# This code was lovingly stolen from some random gem:
|
68
71
|
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
|
69
72
|
#
|
70
73
|
# Thanks to whoever made it.
|
74
|
+
#
|
75
|
+
# @return [Hash] merged hashes
|
76
|
+
#
|
71
77
|
def deep_merge_hashes!(target, overwrite)
|
72
78
|
merge_values(target, overwrite)
|
73
79
|
merge_default_proc(target, overwrite)
|
data/lib/doing/util_backup.rb
CHANGED
@@ -24,19 +24,6 @@ module Doing
|
|
24
24
|
clear_redo(filename)
|
25
25
|
end
|
26
26
|
|
27
|
-
##
|
28
|
-
## Delete all redo files
|
29
|
-
##
|
30
|
-
## @param filename [String] The filename
|
31
|
-
##
|
32
|
-
def clear_redo(filename)
|
33
|
-
filename ||= Doing.setting('doing_file')
|
34
|
-
backups = Dir.glob("undone*___#{File.basename(filename)}", base: backup_dir).sort.reverse
|
35
|
-
backups.each do |file|
|
36
|
-
FileUtils.rm(File.join(backup_dir, file))
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
27
|
##
|
41
28
|
## Retrieve the most recent backup
|
42
29
|
##
|
@@ -65,7 +52,8 @@ module Doing
|
|
65
52
|
raise HistoryLimitError, 'End of undo history' if backup_file.nil?
|
66
53
|
|
67
54
|
save_undone(filename)
|
68
|
-
|
55
|
+
move_backup(backup_file, filename)
|
56
|
+
|
69
57
|
prune_backups_after(File.basename(backup_file))
|
70
58
|
Doing.logger.warn('File update:', "restored from #{backup_file}")
|
71
59
|
Doing.logger.benchmark(:restore_backup, :finish)
|
@@ -78,7 +66,7 @@ module Doing
|
|
78
66
|
##
|
79
67
|
def redo_backup(filename = nil, count: 1)
|
80
68
|
filename ||= Doing.setting('doing_file')
|
81
|
-
|
69
|
+
|
82
70
|
undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort.reverse
|
83
71
|
total = undones.count
|
84
72
|
count = total if count > total
|
@@ -90,7 +78,7 @@ module Doing
|
|
90
78
|
|
91
79
|
redo_file = File.join(backup_dir, undone)
|
92
80
|
|
93
|
-
|
81
|
+
move_backup(redo_file, filename)
|
94
82
|
|
95
83
|
skipped.each do |f|
|
96
84
|
FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, '')))
|
@@ -100,14 +88,6 @@ module Doing
|
|
100
88
|
Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining")
|
101
89
|
end
|
102
90
|
|
103
|
-
def clear_undone(filename = nil)
|
104
|
-
filename ||= Doing.setting('doing_file')
|
105
|
-
# redo_file = File.join(backup_dir, "undone___#{File.basename(filename)}")
|
106
|
-
Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).each do |f|
|
107
|
-
FileUtils.rm(File.join(backup_dir, f))
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
91
|
##
|
112
92
|
## Select from recent undos. If a filename is
|
113
93
|
## provided, only backups of that filename will be used.
|
@@ -136,7 +116,7 @@ module Doing
|
|
136
116
|
|
137
117
|
redo_file = File.join(backup_dir, undone)
|
138
118
|
|
139
|
-
|
119
|
+
move_backup(redo_file, filename)
|
140
120
|
|
141
121
|
skipped.each do |f|
|
142
122
|
FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, '')))
|
@@ -165,11 +145,47 @@ module Doing
|
|
165
145
|
|
166
146
|
backup_file = show_menu(options, filename)
|
167
147
|
Util.write_to_file(File.join(backup_dir, "undone___#{File.basename(filename)}"), IO.read(filename), backup: false)
|
168
|
-
|
148
|
+
move_backup(backup_file, filename)
|
169
149
|
prune_backups_after(File.basename(backup_file))
|
170
150
|
Doing.logger.warn('File update:', "restored from #{backup_file}")
|
171
151
|
end
|
172
152
|
|
153
|
+
##
|
154
|
+
## Writes a copy of the content to a dated backup file
|
155
|
+
## in a hidden directory
|
156
|
+
##
|
157
|
+
## @param filename [String] The filename
|
158
|
+
##
|
159
|
+
def write_backup(filename = nil)
|
160
|
+
Doing.logger.benchmark(:_write_backup, :start)
|
161
|
+
filename ||= Doing.setting('doing_file')
|
162
|
+
|
163
|
+
unless File.exist?(filename)
|
164
|
+
Doing.logger.debug('Backup:', "original file doesn't exist (#{filename})")
|
165
|
+
return
|
166
|
+
end
|
167
|
+
|
168
|
+
backup_file = File.join(backup_dir, "#{timestamp_filename}___#{File.basename(filename)}")
|
169
|
+
# compressed = Zlib::Deflate.deflate(content)
|
170
|
+
# Zlib::GzipWriter.open(backup_file + '.gz') do |gz|
|
171
|
+
# gz.write(IO.read(filename))
|
172
|
+
# end
|
173
|
+
|
174
|
+
FileUtils.cp(filename, backup_file)
|
175
|
+
|
176
|
+
prune_backups(filename, Doing.setting('history_size').to_i)
|
177
|
+
clear_undone(filename)
|
178
|
+
Doing.logger.benchmark(:_write_backup, :finish)
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def move_backup(source, dest)
|
184
|
+
Hooks.trigger :pre_write, WWID.new, dest
|
185
|
+
FileUtils.mv(source, dest)
|
186
|
+
Hooks.trigger :post_write, dest
|
187
|
+
end
|
188
|
+
|
173
189
|
def show_menu(options, filename)
|
174
190
|
if TTY::Which.which('colordiff')
|
175
191
|
preview = 'colordiff -U 1'
|
@@ -209,36 +225,27 @@ module Doing
|
|
209
225
|
result.strip.split(/\t/).last
|
210
226
|
end
|
211
227
|
|
228
|
+
def clear_undone(filename = nil)
|
229
|
+
filename ||= Doing.setting('doing_file')
|
230
|
+
# redo_file = File.join(backup_dir, "undone___#{File.basename(filename)}")
|
231
|
+
Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).each do |f|
|
232
|
+
FileUtils.rm(File.join(backup_dir, f))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
212
236
|
##
|
213
|
-
##
|
214
|
-
## in a hidden directory
|
237
|
+
## Delete all redo files
|
215
238
|
##
|
216
239
|
## @param filename [String] The filename
|
217
240
|
##
|
218
|
-
def
|
219
|
-
Doing.logger.benchmark(:_write_backup, :start)
|
241
|
+
def clear_redo(filename)
|
220
242
|
filename ||= Doing.setting('doing_file')
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
return
|
243
|
+
backups = Dir.glob("undone*___#{File.basename(filename)}", base: backup_dir).sort.reverse
|
244
|
+
backups.each do |file|
|
245
|
+
FileUtils.rm(File.join(backup_dir, file))
|
225
246
|
end
|
226
|
-
|
227
|
-
backup_file = File.join(backup_dir, "#{timestamp_filename}___#{File.basename(filename)}")
|
228
|
-
# compressed = Zlib::Deflate.deflate(content)
|
229
|
-
# Zlib::GzipWriter.open(backup_file + '.gz') do |gz|
|
230
|
-
# gz.write(IO.read(filename))
|
231
|
-
# end
|
232
|
-
|
233
|
-
FileUtils.cp(filename, backup_file)
|
234
|
-
|
235
|
-
prune_backups(filename, Doing.setting('history_size').to_i)
|
236
|
-
clear_undone(filename)
|
237
|
-
Doing.logger.benchmark(:_write_backup, :finish)
|
238
247
|
end
|
239
248
|
|
240
|
-
private
|
241
|
-
|
242
249
|
def timestamp_filename
|
243
250
|
Time.now.strftime('%Y-%m-%d_%H.%M.%S')
|
244
251
|
end
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid/editor.rb
CHANGED
@@ -12,7 +12,7 @@ module Doing
|
|
12
12
|
|
13
13
|
raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?
|
14
14
|
|
15
|
-
tmpfile = Tempfile.new(['
|
15
|
+
tmpfile = Tempfile.new(['doing_temp', '.doing'])
|
16
16
|
|
17
17
|
File.open(tmpfile.path, 'w+') do |f|
|
18
18
|
f.puts input
|
@@ -134,14 +134,15 @@ module Doing
|
|
134
134
|
end
|
135
135
|
divider = "-----------"
|
136
136
|
notice =<<~EONOTICE
|
137
|
+
|
137
138
|
# - You may delete entries, but leave all divider lines (---) in place.
|
138
139
|
# - Start and @done dates replaced with a time string (yesterday 3pm) will
|
139
140
|
# be parsed automatically. Do not delete the pipe (|) between start date
|
140
141
|
# and entry title.
|
141
142
|
EONOTICE
|
142
|
-
input = "#{editable_items.map(&:strip).join("\n#{divider}\n")}\n
|
143
|
+
input = "#{editable_items.map(&:strip).join("\n#{divider}\n")}\n"
|
143
144
|
|
144
|
-
new_items = fork_editor(input).split(/^#{divider}/).map(&:strip)
|
145
|
+
new_items = fork_editor(input, message: notice).split(/^#{divider}/).map(&:strip)
|
145
146
|
|
146
147
|
new_items.each_with_index do |new_item, i|
|
147
148
|
input_lines = new_item.split(/[\n\r]+/).delete_if(&:ignore?)
|
@@ -190,6 +191,8 @@ module Doing
|
|
190
191
|
content = ["#{item.date.strftime('%F %R')} | #{item.title.dup}"]
|
191
192
|
content << item.note.strip_lines.join("\n") unless item.note.empty?
|
192
193
|
new_item = fork_editor(content.join("\n"))
|
194
|
+
raise UserCancelled, 'No change' if new_item.strip == content.join("\n").strip
|
195
|
+
|
193
196
|
date, title, note = format_input(new_item)
|
194
197
|
date ||= item.date
|
195
198
|
|
@@ -92,22 +92,21 @@ module Doing
|
|
92
92
|
|
93
93
|
actions.concat(['resume/repeat', 'begin/reset']) if items.count == 1
|
94
94
|
|
95
|
-
choice = Prompt.choose_from(actions,
|
95
|
+
choice = Prompt.choose_from(actions.map(&:titlecase),
|
96
96
|
prompt: 'What do you want to do with the selected items? > ',
|
97
97
|
multiple: true,
|
98
98
|
sorted: false,
|
99
99
|
fzf_args: ["--height=#{actions.count + 3}", '--tac', '--no-sort', '--info=hidden'])
|
100
100
|
return unless choice
|
101
101
|
|
102
|
-
to_do = choice.strip.split(/\n/)
|
102
|
+
to_do = choice.strip.split(/\n/).map(&:downcase)
|
103
|
+
|
103
104
|
to_do.each do |action|
|
104
105
|
case action
|
105
|
-
when /resume/
|
106
|
-
opt[
|
107
|
-
when /
|
108
|
-
opt[:
|
109
|
-
when /autotag/
|
110
|
-
opt[:autotag] = true
|
106
|
+
when /(resume|reset|autotag|archive|delete|finish|cancel|flag)/
|
107
|
+
opt[action.to_sym] = true
|
108
|
+
when /edit/
|
109
|
+
opt[:editor] = true
|
111
110
|
when /(add|remove) tag/
|
112
111
|
type = action =~ /^add/ ? 'add' : 'remove'
|
113
112
|
raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
|
@@ -149,21 +148,9 @@ module Doing
|
|
149
148
|
|
150
149
|
opt[:save_to] = filename
|
151
150
|
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
151
|
when /move/
|
163
152
|
section = choose_section.strip
|
164
153
|
opt[:move] = section.strip unless section =~ /^ *$/
|
165
|
-
when /flag/
|
166
|
-
opt[:flag] = true
|
167
154
|
end
|
168
155
|
end
|
169
156
|
end
|
@@ -263,6 +250,9 @@ module Doing
|
|
263
250
|
write(@doing_file)
|
264
251
|
|
265
252
|
if opt[:editor]
|
253
|
+
sleep 2 # This seems to be necessary between running fzf
|
254
|
+
# and forking the editor, otherwise vim gets all
|
255
|
+
# screwy and I can't figure out why
|
266
256
|
edit_items(items) # hooked
|
267
257
|
|
268
258
|
write(@doing_file)
|