doing 2.1.39 → 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/.yardopts +1 -1
- data/CHANGELOG.md +67 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +4 -4
- 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 +45 -36
- data/bin/commands/done.rb +1 -18
- data/bin/commands/finish.rb +90 -59
- data/bin/commands/flag.rb +5 -1
- 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 +151 -107
- data/bin/commands/on.rb +8 -18
- 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 +6 -17
- data/bin/commands/since.rb +1 -12
- data/bin/commands/tag_dir.rb +49 -15
- data/bin/commands/today.rb +2 -13
- data/bin/commands/undo.rb +4 -6
- data/bin/commands/view.rb +1 -1
- data/bin/commands/yesterday.rb +2 -13
- data/bin/doing +15 -8
- data/{Dockerfile → docker/Dockerfile} +3 -1
- data/{Dockerfile-2.6 → docker/Dockerfile-2.6} +2 -2
- data/{Dockerfile-2.7 → docker/Dockerfile-2.7} +2 -2
- data/{Dockerfile-3.0 → docker/Dockerfile-3.0} +2 -2
- data/{bash_profile → docker/bash_profile} +0 -0
- data/{inputrc → docker/inputrc} +0 -0
- data/docs/doc/Array.html +85 -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 +198 -0
- data/docs/doc/Doing/ArrayTags.html +424 -0
- data/docs/doc/Doing/CSVExport.html +266 -0
- data/docs/doc/Doing/CalendarImport.html +232 -0
- data/docs/doc/Doing/Change.html +617 -0
- data/docs/doc/Doing/Changes.html +468 -0
- data/docs/doc/Doing/ChronifyArray.html +347 -0
- data/docs/doc/Doing/ChronifyNumeric.html +271 -0
- data/docs/doc/Doing/ChronifyString.html +682 -0
- data/docs/doc/Doing/Color.html +167 -21
- data/docs/doc/Doing/Completion/BashCompletions.html +445 -0
- data/docs/doc/Doing/Completion/FishCompletions.html +445 -0
- data/docs/doc/Doing/Completion/StringUtils.html +229 -0
- data/docs/doc/Doing/Completion/ZshCompletions.html +445 -0
- data/docs/doc/Doing/Completion.html +17 -3
- data/docs/doc/Doing/Configuration.html +3 -2
- data/docs/doc/Doing/DayOneRenderer.html +383 -0
- data/docs/doc/Doing/DayoneExport.html +290 -0
- data/docs/doc/Doing/DoingImport.html +391 -0
- data/docs/doc/Doing/Entry.html +381 -0
- 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/HTMLExport.html +256 -0
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +179 -1660
- data/docs/doc/Doing/ItemDates.html +564 -0
- data/docs/doc/Doing/ItemQuery.html +614 -0
- data/docs/doc/Doing/ItemState.html +387 -0
- data/docs/doc/Doing/ItemTags.html +498 -0
- data/docs/doc/Doing/Items.html +581 -15
- data/docs/doc/Doing/JSONExport.html +222 -0
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/MarkdownExport.html +266 -0
- data/docs/doc/Doing/MarkdownRenderer.html +383 -0
- data/docs/doc/Doing/Note.html +18 -4
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +181 -76
- data/docs/doc/Doing/Prompt.html +32 -683
- data/docs/doc/Doing/PromptChoose.html +484 -0
- data/docs/doc/Doing/PromptFZF.html +391 -0
- data/docs/doc/Doing/PromptInput.html +572 -0
- data/docs/doc/Doing/PromptSTD.html +293 -0
- data/docs/doc/Doing/PromptYN.html +237 -0
- data/docs/doc/Doing/Section.html +58 -2
- data/docs/doc/Doing/StringHighlight.html +533 -0
- data/docs/doc/Doing/StringNormalize.html +929 -0
- data/docs/doc/Doing/StringQuery.html +725 -0
- data/docs/doc/Doing/StringTags.html +884 -0
- data/docs/doc/Doing/StringTransform.html +599 -0
- data/docs/doc/Doing/StringTruncate.html +448 -0
- data/docs/doc/Doing/StringURL.html +409 -0
- data/docs/doc/Doing/SymbolNormalize.html +341 -0
- data/docs/doc/Doing/TaskPaperExport.html +222 -0
- data/docs/doc/Doing/TemplateExport.html +249 -0
- data/docs/doc/Doing/TemplateString.html +102 -3
- data/docs/doc/Doing/TimingImport.html +285 -0
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +11 -163
- data/docs/doc/Doing/Util.html +67 -10
- data/docs/doc/Doing/Version.html +523 -0
- data/docs/doc/Doing/WWID/WWIDUtil.html +510 -0
- data/docs/doc/Doing/WWID.html +476 -139
- data/docs/doc/Doing/WWIDDisplay.html +865 -0
- data/docs/doc/Doing/WWIDEditor.html +466 -0
- data/docs/doc/Doing/WWIDFileTools.html +359 -0
- data/docs/doc/Doing/WWIDFilter.html +466 -0
- data/docs/doc/Doing/WWIDGuess.html +299 -0
- data/docs/doc/Doing/WWIDInteractive.html +752 -0
- data/docs/doc/Doing/WWIDModify.html +1078 -0
- data/docs/doc/Doing/WWIDTags.html +302 -0
- data/docs/doc/Doing/WWIDTimers.html +359 -0
- data/docs/doc/Doing/WWIDUtil.html +510 -0
- data/docs/doc/Doing.html +9 -6
- 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 +23 -78
- 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 +58 -633
- data/docs/doc/Symbol.html +9 -224
- data/docs/doc/Time.html +119 -13
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +348 -4
- 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 +1904 -592
- data/docs/doc/top-level-namespace.html +12 -4
- data/docs/index.md +1 -1
- data/doing.rdoc +67 -15
- data/lib/completion/_doing.zsh +6 -6
- data/lib/completion/doing.bash +10 -10
- data/lib/completion/doing.fish +10 -3
- data/lib/doing/add_options.rb +39 -1
- data/lib/doing/array/array.rb +18 -12
- data/lib/doing/array/cleanup.rb +31 -0
- data/lib/doing/array/nested_hash.rb +1 -1
- data/lib/doing/array/tags.rb +6 -5
- data/lib/doing/changelog/changelog.rb +6 -0
- data/lib/doing/chronify/array.rb +65 -25
- data/lib/doing/chronify/chronify.rb +12 -0
- data/lib/doing/chronify/numeric.rb +3 -2
- data/lib/doing/chronify/string.rb +1 -1
- data/lib/doing/colors.rb +77 -30
- data/lib/doing/completion/completion_string.rb +25 -0
- data/lib/doing/completion.rb +4 -5
- data/lib/doing/configuration.rb +7 -3
- data/lib/doing/errors.rb +51 -35
- data/lib/doing/good.rb +8 -0
- data/lib/doing/hooks.rb +3 -3
- data/lib/doing/item/dates.rb +112 -0
- data/lib/doing/item/item.rb +128 -0
- data/lib/doing/{item.rb → item/query.rb} +2 -353
- data/lib/doing/item/state.rb +59 -0
- data/lib/doing/item/tags.rb +87 -0
- data/lib/doing/items/filter.rb +67 -0
- data/lib/doing/items/items.rb +57 -0
- data/lib/doing/items/modify.rb +36 -0
- data/lib/doing/items/sections.rb +83 -0
- data/lib/doing/items/util.rb +74 -0
- data/lib/doing/normalize.rb +10 -2
- data/lib/doing/note.rb +1 -1
- data/lib/doing/pager.rb +9 -3
- data/lib/doing/plugin_manager.rb +33 -8
- data/lib/doing/plugins/export/markdown_export.rb +4 -2
- data/lib/doing/plugins/export/template_export.rb +4 -4
- data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
- data/lib/doing/plugins/import/doing_import.rb +1 -1
- data/lib/doing/prompt/choose.rb +118 -0
- data/lib/doing/prompt/fzf.rb +84 -0
- data/lib/doing/prompt/input.rb +129 -0
- data/lib/doing/prompt/prompt.rb +41 -0
- data/lib/doing/prompt/std.rb +32 -0
- data/lib/doing/prompt/yn.rb +64 -0
- data/lib/doing/section.rb +4 -0
- data/lib/doing/string/highlight.rb +1 -1
- data/lib/doing/string/query.rb +1 -1
- data/lib/doing/string/string.rb +18 -7
- data/lib/doing/string/tags.rb +14 -3
- data/lib/doing/string/transform.rb +7 -1
- data/lib/doing/string/truncate.rb +1 -1
- data/lib/doing/string/url.rb +1 -1
- data/lib/doing/time.rb +19 -1
- data/lib/doing/util.rb +12 -6
- data/lib/doing/util_backup.rb +62 -57
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/display.rb +396 -0
- data/lib/doing/wwid/editor.rb +214 -0
- data/lib/doing/wwid/filetools.rb +183 -0
- data/lib/doing/wwid/filter.rb +226 -0
- data/lib/doing/wwid/guess.rb +85 -0
- data/lib/doing/wwid/interactive.rb +377 -0
- data/lib/doing/wwid/modify.rb +617 -0
- data/lib/doing/wwid/tags.rb +51 -0
- data/lib/doing/wwid/timers.rb +342 -0
- data/lib/doing/wwid/wwid.rb +121 -0
- data/lib/doing/wwid/wwidutil.rb +101 -0
- data/lib/doing.rb +7 -7
- data/lib/helpers/threaded_tests.rb +1 -0
- metadata +94 -14
- data/lib/doing/changelog.rb +0 -6
- data/lib/doing/completion/string.rb +0 -17
- data/lib/doing/items.rb +0 -196
- data/lib/doing/prompt.rb +0 -330
- data/lib/doing/wwid.rb +0 -2398
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 limit Maximum number of backups to retain
|
|
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
|
##
|
|
@@ -62,10 +49,11 @@ module Doing
|
|
|
62
49
|
filename ||= Doing.setting('doing_file')
|
|
63
50
|
|
|
64
51
|
backup_file = last_backup(filename, count: count)
|
|
65
|
-
raise
|
|
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
|
|
@@ -86,11 +74,11 @@ module Doing
|
|
|
86
74
|
skipped = undones.slice!(0, count)
|
|
87
75
|
undone = skipped.pop
|
|
88
76
|
|
|
89
|
-
raise
|
|
77
|
+
raise HistoryLimitError, 'End of redo history' if undone.nil?
|
|
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.
|
|
@@ -118,8 +98,7 @@ module Doing
|
|
|
118
98
|
filename ||= Doing.setting('doing_file')
|
|
119
99
|
|
|
120
100
|
undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort
|
|
121
|
-
|
|
122
|
-
raise DoingRuntimeError, 'End of redo history' if undones.empty?
|
|
101
|
+
raise HistoryLimitError, 'End of redo history' if undones.empty?
|
|
123
102
|
|
|
124
103
|
total = undones.count
|
|
125
104
|
options = undones.each_with_object([]) do |file, arr|
|
|
@@ -128,8 +107,7 @@ module Doing
|
|
|
128
107
|
|
|
129
108
|
arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}")
|
|
130
109
|
end
|
|
131
|
-
|
|
132
|
-
raise DoingRuntimeError, 'No backup files to load' if options.empty?
|
|
110
|
+
raise MissingBackupFile, 'No backup files to load' if options.empty?
|
|
133
111
|
|
|
134
112
|
backup_file = show_menu(options, filename)
|
|
135
113
|
idx = undones.index(File.basename(backup_file))
|
|
@@ -138,7 +116,7 @@ module Doing
|
|
|
138
116
|
|
|
139
117
|
redo_file = File.join(backup_dir, undone)
|
|
140
118
|
|
|
141
|
-
|
|
119
|
+
move_backup(redo_file, filename)
|
|
142
120
|
|
|
143
121
|
skipped.each do |f|
|
|
144
122
|
FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, '')))
|
|
@@ -163,15 +141,51 @@ module Doing
|
|
|
163
141
|
arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}")
|
|
164
142
|
end
|
|
165
143
|
|
|
166
|
-
raise
|
|
144
|
+
raise MissingBackupFile, 'No backup files to load' if options.empty?
|
|
167
145
|
|
|
168
146
|
backup_file = show_menu(options, filename)
|
|
169
147
|
Util.write_to_file(File.join(backup_dir, "undone___#{File.basename(filename)}"), IO.read(filename), backup: false)
|
|
170
|
-
|
|
148
|
+
move_backup(backup_file, filename)
|
|
171
149
|
prune_backups_after(File.basename(backup_file))
|
|
172
150
|
Doing.logger.warn('File update:', "restored from #{backup_file}")
|
|
173
151
|
end
|
|
174
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
|
+
|
|
175
189
|
def show_menu(options, filename)
|
|
176
190
|
if TTY::Which.which('colordiff')
|
|
177
191
|
preview = 'colordiff -U 1'
|
|
@@ -211,36 +225,27 @@ module Doing
|
|
|
211
225
|
result.strip.split(/\t/).last
|
|
212
226
|
end
|
|
213
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
|
+
|
|
214
236
|
##
|
|
215
|
-
##
|
|
216
|
-
## in a hidden directory
|
|
237
|
+
## Delete all redo files
|
|
217
238
|
##
|
|
218
|
-
## @param
|
|
239
|
+
## @param filename [String] The filename
|
|
219
240
|
##
|
|
220
|
-
def
|
|
221
|
-
Doing.logger.benchmark(:_write_backup, :start)
|
|
241
|
+
def clear_redo(filename)
|
|
222
242
|
filename ||= Doing.setting('doing_file')
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
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))
|
|
227
246
|
end
|
|
228
|
-
|
|
229
|
-
backup_file = File.join(backup_dir, "#{timestamp_filename}___#{File.basename(filename)}")
|
|
230
|
-
# compressed = Zlib::Deflate.deflate(content)
|
|
231
|
-
# Zlib::GzipWriter.open(backup_file + '.gz') do |gz|
|
|
232
|
-
# gz.write(IO.read(filename))
|
|
233
|
-
# end
|
|
234
|
-
|
|
235
|
-
FileUtils.cp(filename, backup_file)
|
|
236
|
-
|
|
237
|
-
prune_backups(filename, Doing.setting('history_size').to_i)
|
|
238
|
-
clear_undone(filename)
|
|
239
|
-
Doing.logger.benchmark(:_write_backup, :finish)
|
|
240
247
|
end
|
|
241
248
|
|
|
242
|
-
private
|
|
243
|
-
|
|
244
249
|
def timestamp_filename
|
|
245
250
|
Time.now.strftime('%Y-%m-%d_%H.%M.%S')
|
|
246
251
|
end
|
|
@@ -281,7 +286,7 @@ module Doing
|
|
|
281
286
|
def create_backup_dir
|
|
282
287
|
dir = File.expand_path(Doing.setting('backup_dir')) || File.join(user_home, '.doing_backup')
|
|
283
288
|
if File.exist?(dir) && !File.directory?(dir)
|
|
284
|
-
raise
|
|
289
|
+
raise DoingNoTraceError.new("#{dir} is not a directory", topic: 'History:', exit_code: 27)
|
|
285
290
|
|
|
286
291
|
end
|
|
287
292
|
|
data/lib/doing/version.rb
CHANGED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doing
|
|
4
|
+
class WWID
|
|
5
|
+
##
|
|
6
|
+
## Display contents of a section based on options
|
|
7
|
+
##
|
|
8
|
+
## @param opt [Hash] Additional Options
|
|
9
|
+
##
|
|
10
|
+
def list_section(opt, items: Items.new)
|
|
11
|
+
logger.benchmark(:list_section, :start)
|
|
12
|
+
opt[:config_template] ||= 'default'
|
|
13
|
+
|
|
14
|
+
tpl_cfg = Doing.setting(['templates', opt[:config_template]])
|
|
15
|
+
|
|
16
|
+
cfg = if opt[:view_template]
|
|
17
|
+
Doing.setting(['views', opt[:view_template]]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
18
|
+
else
|
|
19
|
+
tpl_cfg
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
cfg.deep_merge({
|
|
23
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
|
24
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
25
|
+
'order' => Doing.setting('order') || :asc,
|
|
26
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
27
|
+
'duration' => Doing.setting('duration'),
|
|
28
|
+
'interval_format' => Doing.setting('interval_format')
|
|
29
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
30
|
+
|
|
31
|
+
opt[:duration] ||= cfg['duration'] || false
|
|
32
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
|
33
|
+
opt[:count] ||= 0
|
|
34
|
+
opt[:age] ||= :newest
|
|
35
|
+
opt[:age] = opt[:age].normalize_age
|
|
36
|
+
opt[:format] ||= cfg['date_format']
|
|
37
|
+
opt[:order] ||= cfg['order'] || :asc
|
|
38
|
+
opt[:tag_order] ||= :asc
|
|
39
|
+
opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
|
|
40
|
+
opt[:template] ||= cfg['template']
|
|
41
|
+
opt[:sort_tags] ||= opt[:tag_sort]
|
|
42
|
+
|
|
43
|
+
# opt[:highlight] ||= true
|
|
44
|
+
title = ''
|
|
45
|
+
is_single = true
|
|
46
|
+
if opt[:section].nil?
|
|
47
|
+
opt[:section] = choose_section
|
|
48
|
+
title = opt[:section]
|
|
49
|
+
elsif opt[:section].instance_of?(String)
|
|
50
|
+
title = if opt[:section] =~ /^all$/i
|
|
51
|
+
if opt[:page_title]
|
|
52
|
+
opt[:page_title]
|
|
53
|
+
elsif opt[:tag_filter] && opt[:tag_filter]['bool'].normalize_bool != :not
|
|
54
|
+
opt[:tag_filter]['tags'].map { |tag| "@#{tag}" }.join(' + ')
|
|
55
|
+
else
|
|
56
|
+
'doing'
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
guess_section(opt[:section])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
items = filter_items(items, opt: opt)
|
|
64
|
+
|
|
65
|
+
items.reverse! unless opt[:order].normalize_order == :desc
|
|
66
|
+
|
|
67
|
+
if opt[:delete]
|
|
68
|
+
delete_items(items, force: opt[:force])
|
|
69
|
+
|
|
70
|
+
write(@doing_file)
|
|
71
|
+
return
|
|
72
|
+
elsif opt[:editor]
|
|
73
|
+
edit_items(items)
|
|
74
|
+
|
|
75
|
+
write(@doing_file)
|
|
76
|
+
return
|
|
77
|
+
elsif opt[:interactive]
|
|
78
|
+
opt[:menu] = !opt[:force]
|
|
79
|
+
opt[:query] = '' # opt[:search]
|
|
80
|
+
opt[:multiple] = true
|
|
81
|
+
selected = Prompt.choose_from_items(items.reverse, include_section: opt[:section] =~ /^all$/i, **opt)
|
|
82
|
+
|
|
83
|
+
raise NoResults, 'no items selected' if selected.nil? || selected.empty?
|
|
84
|
+
|
|
85
|
+
act_on(selected, opt)
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
opt[:output] ||= 'template'
|
|
90
|
+
opt[:wrap_width] ||= Doing.setting('templates.default.wrap_width', 0)
|
|
91
|
+
|
|
92
|
+
logger.benchmark(:list_section, :finish)
|
|
93
|
+
output(items, title, is_single, opt)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
## Display entries within a date range
|
|
98
|
+
##
|
|
99
|
+
## @param dates [Array] [start, end]
|
|
100
|
+
## @param section [String] The section
|
|
101
|
+
## @param times (Bool) Show times
|
|
102
|
+
## @param output [String] Output format
|
|
103
|
+
## @param opt [Hash] Additional Options
|
|
104
|
+
##
|
|
105
|
+
def list_date(dates, section, times = nil, output = nil, opt)
|
|
106
|
+
opt ||= {}
|
|
107
|
+
opt[:totals] ||= false
|
|
108
|
+
opt[:sort_tags] ||= false
|
|
109
|
+
section = guess_section(section)
|
|
110
|
+
# :date_filter expects an array with start and end date
|
|
111
|
+
dates = dates.split_date_range if dates.instance_of?(String)
|
|
112
|
+
|
|
113
|
+
opt[:section] = section
|
|
114
|
+
opt[:count] = 0
|
|
115
|
+
opt[:order] = :asc
|
|
116
|
+
opt[:date_filter] = dates
|
|
117
|
+
opt[:times] = times
|
|
118
|
+
opt[:output] = output
|
|
119
|
+
|
|
120
|
+
time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
|
|
121
|
+
if opt[:from] && opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
|
|
122
|
+
opt[:time_filter] = opt[:from]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
list_section(opt)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
## Show all entries from the current day
|
|
130
|
+
##
|
|
131
|
+
## @param times [Boolean] show times
|
|
132
|
+
## @param output [String] output format
|
|
133
|
+
## @param opt [Hash] Options
|
|
134
|
+
##
|
|
135
|
+
def today(times = true, output = nil, opt)
|
|
136
|
+
opt ||= {}
|
|
137
|
+
opt[:totals] ||= false
|
|
138
|
+
opt[:sort_tags] ||= false
|
|
139
|
+
|
|
140
|
+
cfg = Doing.setting('templates').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
|
141
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
|
142
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
143
|
+
'order' => Doing.setting('order') || :asc,
|
|
144
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
145
|
+
'duration' => Doing.setting('duration'),
|
|
146
|
+
'interval_format' => Doing.setting('interval_format')
|
|
147
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
148
|
+
|
|
149
|
+
template = opt[:template] || cfg['template']
|
|
150
|
+
|
|
151
|
+
opt[:duration] ||= cfg['duration'] || false
|
|
152
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
|
153
|
+
|
|
154
|
+
options = {
|
|
155
|
+
after: opt[:after],
|
|
156
|
+
before: opt[:before],
|
|
157
|
+
count: 0,
|
|
158
|
+
duration: opt[:duration],
|
|
159
|
+
from: opt[:from],
|
|
160
|
+
format: cfg['date_format'],
|
|
161
|
+
interval_format: opt[:interval_format],
|
|
162
|
+
only_timed: opt[:only_timed],
|
|
163
|
+
order: cfg['order'] || :asc,
|
|
164
|
+
output: output,
|
|
165
|
+
section: opt[:section],
|
|
166
|
+
sort_tags: opt[:sort_tags],
|
|
167
|
+
template: template,
|
|
168
|
+
times: times,
|
|
169
|
+
today: true,
|
|
170
|
+
totals: opt[:totals],
|
|
171
|
+
wrap_width: cfg['wrap_width'],
|
|
172
|
+
tags_color: cfg['tags_color'],
|
|
173
|
+
config_template: opt[:config_template]
|
|
174
|
+
}
|
|
175
|
+
list_section(options)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
##
|
|
179
|
+
## Show entries from the previous day
|
|
180
|
+
##
|
|
181
|
+
## @param section [String] The section
|
|
182
|
+
## @param times (Bool) Show times
|
|
183
|
+
## @param output [String] Output format
|
|
184
|
+
## @param opt [Hash] Additional Options
|
|
185
|
+
##
|
|
186
|
+
def yesterday(section, times = nil, output = nil, opt)
|
|
187
|
+
opt ||= {}
|
|
188
|
+
opt[:totals] ||= false
|
|
189
|
+
opt[:sort_tags] ||= false
|
|
190
|
+
opt[:config_template] ||= 'today'
|
|
191
|
+
opt[:yesterday] = true
|
|
192
|
+
|
|
193
|
+
section = guess_section(section)
|
|
194
|
+
y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d')
|
|
195
|
+
opt[:after] = "#{y} #{opt[:after]}" if opt[:after]
|
|
196
|
+
opt[:before] = "#{y} #{opt[:before]}" if opt[:before]
|
|
197
|
+
|
|
198
|
+
opt[:output] = output
|
|
199
|
+
opt[:section] = section
|
|
200
|
+
opt[:times] = times
|
|
201
|
+
opt[:count] = 0
|
|
202
|
+
|
|
203
|
+
list_section(opt)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
##
|
|
207
|
+
## Show recent entries
|
|
208
|
+
##
|
|
209
|
+
## @param count [Integer] The number to show
|
|
210
|
+
## @param section [String] The section to show from, default Currently
|
|
211
|
+
## @param opt [Hash] Additional Options
|
|
212
|
+
##
|
|
213
|
+
def recent(count = 10, section = nil, opt)
|
|
214
|
+
opt ||= {}
|
|
215
|
+
times = opt[:t] || true
|
|
216
|
+
opt[:totals] ||= false
|
|
217
|
+
opt[:sort_tags] ||= false
|
|
218
|
+
|
|
219
|
+
cfg = Doing.setting('templates.recent').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
|
220
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
|
221
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
222
|
+
'order' => Doing.setting('order') || :asc,
|
|
223
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
224
|
+
'duration' => Doing.setting('duration'),
|
|
225
|
+
'interval_format' => Doing.setting('interval_format')
|
|
226
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
227
|
+
opt[:duration] ||= cfg['duration'] || false
|
|
228
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
|
229
|
+
|
|
230
|
+
section ||= Doing.setting('current_section')
|
|
231
|
+
section = guess_section(section)
|
|
232
|
+
|
|
233
|
+
opt[:section] = section
|
|
234
|
+
opt[:wrap_width] = cfg['wrap_width']
|
|
235
|
+
opt[:count] = count
|
|
236
|
+
opt[:format] = cfg['date_format']
|
|
237
|
+
opt[:template] = opt[:template] || cfg['template']
|
|
238
|
+
opt[:order] = :asc
|
|
239
|
+
opt[:times] = times
|
|
240
|
+
|
|
241
|
+
list_section(opt)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
##
|
|
245
|
+
## Show the last entry
|
|
246
|
+
##
|
|
247
|
+
## @param times (Bool) Show times
|
|
248
|
+
## @param section [String] Section to pull from, default Currently
|
|
249
|
+
##
|
|
250
|
+
def last(times: true, section: nil, options: {})
|
|
251
|
+
section = section.nil? || section =~ /all/i ? 'All' : guess_section(section)
|
|
252
|
+
cfg = Doing.setting(['templates', options[:config_template]]).deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
|
253
|
+
'wrap_width' => Doing.setting('wrap_width', 0),
|
|
254
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
255
|
+
'order' => Doing.setting('order', :asc),
|
|
256
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
257
|
+
'duration' => Doing.setting('duration'),
|
|
258
|
+
'interval_format' => Doing.setting('interval_format')
|
|
259
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
260
|
+
options[:duration] ||= cfg['duration'] || false
|
|
261
|
+
options[:interval_format] ||= cfg['interval_format'] || 'text'
|
|
262
|
+
|
|
263
|
+
opts = {
|
|
264
|
+
case: options[:case],
|
|
265
|
+
config_template: options[:config_template] || 'last',
|
|
266
|
+
count: 1,
|
|
267
|
+
delete: options[:delete],
|
|
268
|
+
duration: options[:duration],
|
|
269
|
+
format: cfg['date_format'],
|
|
270
|
+
interval_format: options[:interval_format],
|
|
271
|
+
not: options[:negate],
|
|
272
|
+
output: options[:output],
|
|
273
|
+
section: section,
|
|
274
|
+
template: options[:template] || cfg['template'],
|
|
275
|
+
times: times,
|
|
276
|
+
val: options[:val],
|
|
277
|
+
wrap_width: cfg['wrap_width']
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if options[:tag]
|
|
281
|
+
opts[:tag_filter] = {
|
|
282
|
+
'tags' => options[:tag],
|
|
283
|
+
'bool' => options[:tag_bool]
|
|
284
|
+
}
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
opts[:search] = options[:search] if options[:search]
|
|
288
|
+
|
|
289
|
+
list_section(opts)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
##
|
|
293
|
+
## Return the content of the last note for a given section
|
|
294
|
+
##
|
|
295
|
+
## @param section [String] The section to retrieve from, default
|
|
296
|
+
## All
|
|
297
|
+
##
|
|
298
|
+
def last_note(section = 'All')
|
|
299
|
+
section = guess_section(section)
|
|
300
|
+
|
|
301
|
+
last_item = last_entry({ section: section })
|
|
302
|
+
|
|
303
|
+
raise NoEntryError, 'No entry found' unless last_item
|
|
304
|
+
|
|
305
|
+
logger.log_now(:info, 'Edit note:', last_item.title)
|
|
306
|
+
|
|
307
|
+
note = last_item.note&.to_s || ''
|
|
308
|
+
"#{last_item.title}\n# EDIT BELOW THIS LINE ------------\n#{note}"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
##
|
|
312
|
+
## Get the last entry
|
|
313
|
+
##
|
|
314
|
+
## @param opt [Hash] Additional Options
|
|
315
|
+
##
|
|
316
|
+
def last_entry(opt)
|
|
317
|
+
opt ||= {}
|
|
318
|
+
opt[:tag_bool] ||= :and
|
|
319
|
+
opt[:section] ||= Doing.setting('current_section')
|
|
320
|
+
|
|
321
|
+
items = filter_items(Items.new, opt: opt)
|
|
322
|
+
|
|
323
|
+
logger.debug('Filtered:', "Parameters matched #{items.count} entries")
|
|
324
|
+
|
|
325
|
+
if opt[:interactive]
|
|
326
|
+
last_entry = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i,
|
|
327
|
+
menu: true,
|
|
328
|
+
header: '',
|
|
329
|
+
prompt: 'Select an entry > ',
|
|
330
|
+
multiple: false,
|
|
331
|
+
sort: false,
|
|
332
|
+
show_if_single: true
|
|
333
|
+
)
|
|
334
|
+
else
|
|
335
|
+
last_entry = items.max_by { |item| item.date }
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
last_entry
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
private
|
|
342
|
+
|
|
343
|
+
##
|
|
344
|
+
## Generate output using available export plugins
|
|
345
|
+
##
|
|
346
|
+
## @param items [Array] The items
|
|
347
|
+
## @param title [String] Page title
|
|
348
|
+
## @param is_single [Boolean] Indicates if single
|
|
349
|
+
## section
|
|
350
|
+
## @param opt [Hash] Additional options
|
|
351
|
+
##
|
|
352
|
+
## @return [String] formatted output based on opt[:output]
|
|
353
|
+
## template trigger
|
|
354
|
+
## @api private
|
|
355
|
+
def output(items, title, is_single, opt)
|
|
356
|
+
logger.benchmark(:output, :start)
|
|
357
|
+
opt ||= {}
|
|
358
|
+
out = nil
|
|
359
|
+
|
|
360
|
+
raise InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export)
|
|
361
|
+
|
|
362
|
+
export_options = { page_title: title, is_single: is_single, options: opt }
|
|
363
|
+
|
|
364
|
+
Hooks.trigger :pre_export, self, opt[:output], items
|
|
365
|
+
|
|
366
|
+
Plugins.plugins[:export].each do |_, options|
|
|
367
|
+
next unless opt[:output] =~ /^(#{options[:trigger].normalize_trigger})$/i
|
|
368
|
+
|
|
369
|
+
out = options[:class].render(self, items, variables: export_options)
|
|
370
|
+
break
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
logger.debug('Output:', "#{items.count} #{items.count == 1 ? 'item' : 'items'} shown")
|
|
374
|
+
logger.benchmark(:output, :finish)
|
|
375
|
+
out
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
##
|
|
379
|
+
## Get next item in the index
|
|
380
|
+
##
|
|
381
|
+
## @param item [Item] target item
|
|
382
|
+
## @param options [Hash] additional options
|
|
383
|
+
## @see #filter_items
|
|
384
|
+
##
|
|
385
|
+
## @return [Item] the next chronological item in the index
|
|
386
|
+
##
|
|
387
|
+
def next_item(item, options = {})
|
|
388
|
+
options ||= {}
|
|
389
|
+
items = filter_items(Items.new, opt: options)
|
|
390
|
+
|
|
391
|
+
idx = items.index(item)
|
|
392
|
+
|
|
393
|
+
idx.positive? ? items[idx - 1] : nil
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|