doing 2.1.41 → 2.1.42
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 +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)
|