doing 2.1.40 → 2.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +4 -4
- data/bin/commands/changes.rb +1 -1
- data/bin/commands/tag_dir.rb +49 -15
- 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 +84 -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 +2 -2
- 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 +1 -1
- 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 +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 +256 -0
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +47 -3
- 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 +460 -11
- 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 +16 -3
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +31 -682
- 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 +565 -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 +101 -2
- data/docs/doc/Doing/TimingImport.html +285 -0
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +9 -7
- data/docs/doc/Doing/Util.html +2 -2
- data/docs/doc/Doing/Version.html +523 -0
- data/docs/doc/Doing/WWID/WWIDUtil.html +510 -0
- data/docs/doc/Doing/WWID.html +4377 -217
- 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 +324 -8
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +1 -1
- data/docs/doc/index.html +1 -1
- data/docs/doc/method_list.html +2326 -542
- data/docs/doc/top-level-namespace.html +2 -2
- data/doing.rdoc +13 -3
- data/lib/completion/_doing.zsh +1 -1
- data/lib/completion/doing.bash +2 -2
- data/lib/completion/doing.fish +3 -1
- data/lib/doing/array/array.rb +16 -12
- 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 +1 -3
- 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/completion/completion_string.rb +25 -0
- data/lib/doing/completion.rb +1 -1
- data/lib/doing/good.rb +8 -0
- data/lib/doing/item/dates.rb +1 -1
- data/lib/doing/{item.rb → item/item.rb} +10 -5
- data/lib/doing/item/query.rb +1 -1
- data/lib/doing/item/state.rb +1 -1
- data/lib/doing/item/tags.rb +1 -1
- 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/plugins/export/markdown_export.rb +4 -2
- 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 +1 -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_backup.rb +2 -2
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/display.rb +357 -360
- data/lib/doing/wwid/editor.rb +173 -176
- data/lib/doing/wwid/filetools.rb +156 -159
- data/lib/doing/wwid/filter.rb +191 -183
- data/lib/doing/wwid/guess.rb +58 -60
- data/lib/doing/wwid/interactive.rb +332 -330
- data/lib/doing/wwid/modify.rb +509 -512
- data/lib/doing/wwid/tags.rb +38 -41
- data/lib/doing/wwid/timers.rb +293 -296
- data/lib/doing/{wwid.rb → wwid/wwid.rb} +32 -23
- data/lib/doing/wwid/wwidutil.rb +79 -82
- data/lib/doing.rb +5 -5
- data/lib/helpers/threaded_tests.rb +1 -0
- metadata +76 -14
- data/lib/doing/changelog.rb +0 -6
- data/lib/doing/completion/string.rb +0 -17
- data/lib/doing/items.rb +0 -221
- data/lib/doing/prompt.rb +0 -330
|
@@ -2,384 +2,386 @@
|
|
|
2
2
|
|
|
3
3
|
module Doing
|
|
4
4
|
class WWID
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if opt[:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
opt[:search] = search
|
|
24
|
-
end
|
|
5
|
+
##
|
|
6
|
+
## Display an interactive menu of entries
|
|
7
|
+
##
|
|
8
|
+
## @param opt [Hash] Additional options
|
|
9
|
+
##
|
|
10
|
+
## Options hash is shared with #filter_items and #act_on
|
|
11
|
+
##
|
|
12
|
+
def interactive(opt)
|
|
13
|
+
opt ||= {}
|
|
14
|
+
opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
|
|
15
|
+
|
|
16
|
+
search = nil
|
|
17
|
+
|
|
18
|
+
if opt[:search]
|
|
19
|
+
search = opt[:search]
|
|
20
|
+
search.sub!(/^'?/, "'") if opt[:exact]
|
|
21
|
+
opt[:search] = search
|
|
22
|
+
end
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
# opt[:query] = opt[:search] if opt[:search] && !opt[:query]
|
|
25
|
+
opt[:query] = "!#{opt[:query]}" if opt[:query] && opt[:not]
|
|
26
|
+
opt[:multiple] = true
|
|
27
|
+
opt[:show_if_single] = true
|
|
28
|
+
filter_options = %i[after before case date_filter from fuzzy not search section val].each_with_object({}) {
|
|
29
|
+
|k, hsh| hsh[k] = opt[k]
|
|
30
|
+
}
|
|
31
|
+
items = filter_items(Items.new, opt: filter_options)
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
menu_options = %i[search query exact multiple show_if_single menu sort case].each_with_object({}) {
|
|
34
|
+
|k, hsh| hsh[k] = opt[k]
|
|
35
|
+
}
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
selection = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, **menu_options)
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
raise NoResults, 'no items selected' if selection.nil? || selection.empty?
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
act_on(selection, opt)
|
|
42
|
+
end
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
44
|
+
##
|
|
45
|
+
## Perform actions on a set of entries. If
|
|
46
|
+
## no valid action is included in the opt
|
|
47
|
+
## hash and the terminal is a TTY, a menu
|
|
48
|
+
## will be presented
|
|
49
|
+
##
|
|
50
|
+
## @param items [Array] Array of Items to affect
|
|
51
|
+
## @param opt [Hash] Options and actions to perform
|
|
52
|
+
##
|
|
53
|
+
## @option opt [Boolean] :editor
|
|
54
|
+
## @option opt [Boolean] :delete
|
|
55
|
+
## @option opt [String] :tag
|
|
56
|
+
## @option opt [Boolean] :flag
|
|
57
|
+
## @option opt [Boolean] :finish
|
|
58
|
+
## @option opt [Boolean] :cancel
|
|
59
|
+
## @option opt [Boolean] :archive
|
|
60
|
+
## @option opt [String] :output
|
|
61
|
+
## @option opt [String] :save_to
|
|
62
|
+
## @option opt [Boolean] :again
|
|
63
|
+
## @option opt [Boolean] :resume
|
|
64
|
+
##
|
|
65
|
+
def act_on(items, opt)
|
|
66
|
+
opt ||= {}
|
|
67
|
+
actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
|
|
68
|
+
has_action = false
|
|
69
|
+
single = items.count == 1
|
|
70
|
+
|
|
71
|
+
actions.each do |a|
|
|
72
|
+
if opt[a]
|
|
73
|
+
has_action = true
|
|
74
|
+
break
|
|
78
75
|
end
|
|
76
|
+
end
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
when /archive/
|
|
154
|
-
opt[:archive] = true
|
|
155
|
-
when /delete/
|
|
156
|
-
opt[:delete] = true
|
|
157
|
-
when /edit/
|
|
158
|
-
opt[:editor] = true
|
|
159
|
-
when /finish/
|
|
160
|
-
opt[:finish] = true
|
|
161
|
-
when /cancel/
|
|
162
|
-
opt[:cancel] = true
|
|
163
|
-
when /move/
|
|
164
|
-
section = choose_section.strip
|
|
165
|
-
opt[:move] = section.strip unless section =~ /^ *$/
|
|
166
|
-
when /flag/
|
|
167
|
-
opt[:flag] = true
|
|
78
|
+
unless has_action
|
|
79
|
+
actions = [
|
|
80
|
+
'add tag',
|
|
81
|
+
'remove tag',
|
|
82
|
+
'autotag',
|
|
83
|
+
'cancel',
|
|
84
|
+
'delete',
|
|
85
|
+
'finish',
|
|
86
|
+
'flag',
|
|
87
|
+
'archive',
|
|
88
|
+
'move',
|
|
89
|
+
'edit',
|
|
90
|
+
'output formatted'
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
actions.concat(['resume/repeat', 'begin/reset']) if items.count == 1
|
|
94
|
+
|
|
95
|
+
choice = Prompt.choose_from(actions,
|
|
96
|
+
prompt: 'What do you want to do with the selected items? > ',
|
|
97
|
+
multiple: true,
|
|
98
|
+
sorted: false,
|
|
99
|
+
fzf_args: ["--height=#{actions.count + 3}", '--tac', '--no-sort', '--info=hidden'])
|
|
100
|
+
return unless choice
|
|
101
|
+
|
|
102
|
+
to_do = choice.strip.split(/\n/)
|
|
103
|
+
to_do.each do |action|
|
|
104
|
+
case action
|
|
105
|
+
when /resume/
|
|
106
|
+
opt[:resume] = true
|
|
107
|
+
when /reset/
|
|
108
|
+
opt[:reset] = true
|
|
109
|
+
when /autotag/
|
|
110
|
+
opt[:autotag] = true
|
|
111
|
+
when /(add|remove) tag/
|
|
112
|
+
type = action =~ /^add/ ? 'add' : 'remove'
|
|
113
|
+
raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
|
|
114
|
+
|
|
115
|
+
tags = type == 'add' ? all_tags(@content) : all_tags(items)
|
|
116
|
+
|
|
117
|
+
add_msg = type == 'add' ? ', include values with tag(value)' : ''
|
|
118
|
+
puts "#{yellow}Separate multiple tags with spaces, hit tab to complete known tags#{add_msg}"
|
|
119
|
+
puts "#{boldgreen}Available tags: #{boldwhite}#{tags.sort.map(&:add_at).join(', ')}" if type == 'remove'
|
|
120
|
+
tag = Prompt.read_line(prompt: "Tags to #{type}", completions: tags)
|
|
121
|
+
|
|
122
|
+
# print "#{yellow("Tag to #{type}: ")}#{reset}"
|
|
123
|
+
# tag = $stdin.gets
|
|
124
|
+
next if tag =~ /^ *$/
|
|
125
|
+
|
|
126
|
+
opt[:tag] = tag.strip.sub(/^@/, '')
|
|
127
|
+
opt[:remove] = true if type == 'remove'
|
|
128
|
+
when /output formatted/
|
|
129
|
+
plugins = Plugins.available_plugins(type: :export).sort
|
|
130
|
+
output_format = Prompt.choose_from(plugins,
|
|
131
|
+
prompt: 'Which output format? > ',
|
|
132
|
+
fzf_args: [
|
|
133
|
+
"--height=#{plugins.count + 3}",
|
|
134
|
+
'--tac',
|
|
135
|
+
'--no-sort',
|
|
136
|
+
'--info=hidden'
|
|
137
|
+
])
|
|
138
|
+
next if output_format =~ /^ *$/
|
|
139
|
+
|
|
140
|
+
raise UserCancelled unless output_format
|
|
141
|
+
|
|
142
|
+
opt[:output] = output_format.strip
|
|
143
|
+
res = opt[:force] ? false : Prompt.yn('Save to file?', default_response: 'n')
|
|
144
|
+
if res
|
|
145
|
+
# print "#{yellow('File path/name: ')}#{reset}"
|
|
146
|
+
# filename = $stdin.gets.strip
|
|
147
|
+
filename = Prompt.read_line(prompt: 'File path/name')
|
|
148
|
+
next if filename.empty?
|
|
149
|
+
|
|
150
|
+
opt[:save_to] = filename
|
|
168
151
|
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
|
+
when /move/
|
|
163
|
+
section = choose_section.strip
|
|
164
|
+
opt[:move] = section.strip unless section =~ /^ *$/
|
|
165
|
+
when /flag/
|
|
166
|
+
opt[:flag] = true
|
|
169
167
|
end
|
|
170
168
|
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if opt[:resume] || opt[:reset]
|
|
172
|
+
raise InvalidArgument, 'resume and restart can only be used on a single entry' if items.count > 1
|
|
173
|
+
|
|
174
|
+
item = items[0]
|
|
175
|
+
if opt[:resume] && !opt[:reset]
|
|
176
|
+
repeat_item(item, { editor: opt[:editor] }) # hooked
|
|
177
|
+
elsif opt[:reset]
|
|
178
|
+
res = Prompt.enter_text('Start date (blank for current time)', default_response: '')
|
|
179
|
+
date = if res =~ /^ *$/
|
|
180
|
+
Time.now
|
|
181
|
+
else
|
|
182
|
+
res.chronify(guess: :begin)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
res = if item.tags?('done', :and) && !opt[:resume]
|
|
186
|
+
opt[:force] ? true : Prompt.yn('Remove @done tag?', default_response: 'y')
|
|
187
|
+
else
|
|
188
|
+
opt[:resume]
|
|
189
|
+
end
|
|
190
|
+
old_item = item.clone
|
|
191
|
+
new_entry = reset_item(item, date: date, resume: res)
|
|
192
|
+
@content.update_item(item, new_entry)
|
|
193
|
+
Hooks.trigger :post_entry_updated, self, new_entry, old_item
|
|
194
|
+
end
|
|
195
|
+
write(@doing_file)
|
|
171
196
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
item = items[0]
|
|
176
|
-
if opt[:resume] && !opt[:reset]
|
|
177
|
-
repeat_item(item, { editor: opt[:editor] }) # hooked
|
|
178
|
-
elsif opt[:reset]
|
|
179
|
-
res = Prompt.enter_text('Start date (blank for current time)', default_response: '')
|
|
180
|
-
if res =~ /^ *$/
|
|
181
|
-
date = Time.now
|
|
182
|
-
else
|
|
183
|
-
date = res.chronify(guess: :begin)
|
|
184
|
-
end
|
|
197
|
+
return
|
|
198
|
+
end
|
|
185
199
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
opt[:resume]
|
|
190
|
-
end
|
|
191
|
-
old_item = item.clone
|
|
192
|
-
new_entry = reset_item(item, date: date, resume: res)
|
|
193
|
-
@content.update_item(item, new_entry)
|
|
194
|
-
Hooks.trigger :post_entry_updated, self, new_entry, old_item
|
|
195
|
-
end
|
|
196
|
-
write(@doing_file)
|
|
200
|
+
if opt[:delete]
|
|
201
|
+
delete_items(items, force: opt[:force]) # hooked
|
|
202
|
+
write(@doing_file)
|
|
197
203
|
|
|
198
|
-
|
|
204
|
+
return
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
if opt[:flag]
|
|
208
|
+
tag = Doing.setting('marker_tag', 'flagged')
|
|
209
|
+
items.map! do |i|
|
|
210
|
+
old_item = i.clone
|
|
211
|
+
i.tag(tag, date: false, remove: opt[:remove], single: single)
|
|
212
|
+
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
199
213
|
end
|
|
214
|
+
end
|
|
200
215
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
216
|
+
if opt[:finish] || opt[:cancel]
|
|
217
|
+
tag = 'done'
|
|
218
|
+
items.map! do |i|
|
|
219
|
+
next unless i.should_finish?
|
|
204
220
|
|
|
205
|
-
|
|
221
|
+
old_item = i.clone
|
|
222
|
+
should_date = !opt[:cancel] && i.should_time?
|
|
223
|
+
i.tag(tag, date: should_date, remove: opt[:remove], single: single)
|
|
224
|
+
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
206
225
|
end
|
|
226
|
+
end
|
|
207
227
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
228
|
+
if opt[:autotag]
|
|
229
|
+
items.map! do |i|
|
|
230
|
+
new_title = autotag(i.title)
|
|
231
|
+
if new_title == i.title
|
|
232
|
+
logger.count(:skipped, level: :debug, message: '%count unchaged %items')
|
|
233
|
+
# logger.debug('Autotag:', 'No changes')
|
|
234
|
+
else
|
|
235
|
+
logger.count(:added_tags)
|
|
236
|
+
logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
|
|
211
237
|
old_item = i.clone
|
|
212
|
-
i.
|
|
238
|
+
i.title = new_title
|
|
213
239
|
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
214
240
|
end
|
|
215
241
|
end
|
|
242
|
+
end
|
|
216
243
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
225
|
-
end
|
|
226
|
-
end
|
|
244
|
+
if opt[:tag]
|
|
245
|
+
tag = opt[:tag]
|
|
246
|
+
items.map! do |i|
|
|
247
|
+
old_item = i.clone
|
|
248
|
+
i.tag(tag, date: false, remove: opt[:remove], single: single)
|
|
249
|
+
i.expand_date_tags(Doing.setting('date_tags'))
|
|
250
|
+
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
227
251
|
end
|
|
252
|
+
end
|
|
228
253
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
else
|
|
236
|
-
logger.count(:added_tags)
|
|
237
|
-
logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
|
|
238
|
-
old_item = i.clone
|
|
239
|
-
i.title = new_title
|
|
240
|
-
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
241
|
-
end
|
|
242
|
-
end
|
|
254
|
+
if opt[:archive] || opt[:move]
|
|
255
|
+
section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
|
|
256
|
+
items.map! do |i|
|
|
257
|
+
old_item = i.clone
|
|
258
|
+
i.move_to(section, label: true)
|
|
259
|
+
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
243
260
|
end
|
|
261
|
+
end
|
|
244
262
|
|
|
245
|
-
|
|
246
|
-
tag = opt[:tag]
|
|
247
|
-
items.map! do |i|
|
|
248
|
-
old_item = i.clone
|
|
249
|
-
i.tag(tag, date: false, remove: opt[:remove], single: single)
|
|
250
|
-
i.expand_date_tags(Doing.setting('date_tags'))
|
|
251
|
-
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
252
|
-
end
|
|
253
|
-
end
|
|
263
|
+
write(@doing_file)
|
|
254
264
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
items.map! do |i|
|
|
258
|
-
old_item = i.clone
|
|
259
|
-
i.move_to(section, label: true)
|
|
260
|
-
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
261
|
-
end
|
|
262
|
-
end
|
|
265
|
+
if opt[:editor]
|
|
266
|
+
edit_items(items) # hooked
|
|
263
267
|
|
|
264
268
|
write(@doing_file)
|
|
269
|
+
end
|
|
265
270
|
|
|
266
|
-
|
|
267
|
-
edit_items(items) # hooked
|
|
271
|
+
return unless opt[:output]
|
|
268
272
|
|
|
269
|
-
|
|
270
|
-
end
|
|
273
|
+
items.each { |i| i.title = "#{i.title} @section(#{i.section})" }
|
|
271
274
|
|
|
272
|
-
|
|
275
|
+
export_items = Items.new
|
|
276
|
+
export_items.concat(items)
|
|
277
|
+
export_items.add_section(Section.new('Export'), log: false)
|
|
278
|
+
options = { section: 'All' }
|
|
273
279
|
|
|
274
|
-
|
|
280
|
+
if opt[:output] =~ /doing/
|
|
281
|
+
options[:output] = 'template'
|
|
282
|
+
options[:template] = '- %date | %title%note'
|
|
283
|
+
else
|
|
284
|
+
options[:output] = opt[:output]
|
|
285
|
+
options[:template] = opt[:template] || nil
|
|
286
|
+
end
|
|
275
287
|
|
|
276
|
-
|
|
277
|
-
export_items.concat(items)
|
|
278
|
-
export_items.add_section(Section.new('Export'), log: false)
|
|
279
|
-
options = { section: 'All' }
|
|
288
|
+
output = list_section(options, items: export_items) # hooked
|
|
280
289
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
options[:template] = opt[:template] || nil
|
|
290
|
+
if opt[:save_to]
|
|
291
|
+
file = File.expand_path(opt[:save_to])
|
|
292
|
+
if File.exist?(file)
|
|
293
|
+
# Create a backup copy for the undo command
|
|
294
|
+
FileUtils.cp(file, "#{file}~")
|
|
287
295
|
end
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if opt[:save_to]
|
|
292
|
-
file = File.expand_path(opt[:save_to])
|
|
293
|
-
if File.exist?(file)
|
|
294
|
-
# Create a backup copy for the undo command
|
|
295
|
-
FileUtils.cp(file, "#{file}~")
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
File.open(file, 'w+') do |f|
|
|
299
|
-
f.puts output
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
logger.warn('File written:', file)
|
|
303
|
-
else
|
|
304
|
-
Doing::Pager.page output
|
|
297
|
+
File.open(file, 'w+') do |f|
|
|
298
|
+
f.puts output
|
|
305
299
|
end
|
|
306
|
-
end
|
|
307
300
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
## @return [String] The selected section name
|
|
312
|
-
##
|
|
313
|
-
def choose_section(include_all: false)
|
|
314
|
-
options = @content.section_titles.sort
|
|
315
|
-
options.unshift('All') if include_all
|
|
316
|
-
choice = Prompt.choose_from(options, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
|
|
317
|
-
choice ? choice.strip : choice
|
|
301
|
+
logger.warn('File written:', file)
|
|
302
|
+
else
|
|
303
|
+
Doing::Pager.page output
|
|
318
304
|
end
|
|
305
|
+
end
|
|
319
306
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
end
|
|
307
|
+
##
|
|
308
|
+
## Generate a menu of sections and allow user selection
|
|
309
|
+
##
|
|
310
|
+
## @return [String] The selected section name
|
|
311
|
+
##
|
|
312
|
+
def choose_section(include_all: false)
|
|
313
|
+
options = @content.section_titles.sort
|
|
314
|
+
options.unshift('All') if include_all
|
|
315
|
+
choice = Prompt.choose_from(options, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
|
|
316
|
+
choice ? choice.strip : choice
|
|
317
|
+
end
|
|
332
318
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
319
|
+
##
|
|
320
|
+
## Generate a menu of tags and allow user selection
|
|
321
|
+
##
|
|
322
|
+
## @return [String] The selected tag name
|
|
323
|
+
##
|
|
324
|
+
def choose_tag(section = 'All', items: nil, include_all: false)
|
|
325
|
+
items ||= @content.in_section(section)
|
|
326
|
+
tags = all_tags(items, counts: true).map { |t, c| "@#{t} (#{c})" }
|
|
327
|
+
tags.unshift('No tag filter') if include_all
|
|
328
|
+
choice = Prompt.choose_from(tags,
|
|
329
|
+
sorted: false,
|
|
330
|
+
multiple: true,
|
|
331
|
+
prompt: 'Choose tag(s) > ',
|
|
332
|
+
fzf_args: ['--height=60%'])
|
|
333
|
+
choice ? choice.split(/\n/).map { |t| t.strip.sub(/ \(.*?\)$/, '') }.join(' ') : choice
|
|
334
|
+
end
|
|
344
335
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
336
|
+
##
|
|
337
|
+
## Generate a menu of sections and tags and allow user selection
|
|
338
|
+
##
|
|
339
|
+
## @return [String] The selected section or tag name
|
|
340
|
+
##
|
|
341
|
+
def choose_section_tag
|
|
342
|
+
options = @content.section_titles.sort
|
|
343
|
+
options.concat(@content.all_tags.sort.map { |t| "@#{t}" })
|
|
344
|
+
choice = Prompt.choose_from(options, prompt: 'Choose a section or tag > ', fzf_args: ['--height=60%'])
|
|
345
|
+
choice ? choice.strip : choice
|
|
346
|
+
end
|
|
354
347
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
|
|
365
|
-
date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
|
|
366
|
-
|
|
367
|
-
elapsed = finish_date - date
|
|
368
|
-
|
|
369
|
-
if max_elapsed.positive? && (elapsed > max_elapsed)
|
|
370
|
-
puts boldwhite(title) if title
|
|
371
|
-
human = elapsed.time_string(format: :natural)
|
|
372
|
-
res = Prompt.yn(yellow("Did this entry actually take #{human}"), default_response: true)
|
|
373
|
-
unless res
|
|
374
|
-
new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
|
|
375
|
-
raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
|
|
376
|
-
|
|
377
|
-
finish_date = date + new_elapsed if new_elapsed
|
|
378
|
-
end
|
|
379
|
-
end
|
|
348
|
+
##
|
|
349
|
+
## Generate a menu of views and allow user selection
|
|
350
|
+
##
|
|
351
|
+
## @return [String] The selected view name
|
|
352
|
+
##
|
|
353
|
+
def choose_view
|
|
354
|
+
choice = Prompt.choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%'])
|
|
355
|
+
choice ? choice.strip : choice
|
|
356
|
+
end
|
|
380
357
|
|
|
381
|
-
|
|
358
|
+
##
|
|
359
|
+
## Interactively verify an item modification if elapsed time is greater than configured threshold
|
|
360
|
+
##
|
|
361
|
+
## @param date [String] Item date
|
|
362
|
+
## @param finish_date [String] The finish date
|
|
363
|
+
## @param title [String] The Item title
|
|
364
|
+
##
|
|
365
|
+
def verify_duration(date, finish_date, title: nil)
|
|
366
|
+
max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
|
|
367
|
+
max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
|
|
368
|
+
date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
|
|
369
|
+
|
|
370
|
+
elapsed = finish_date - date
|
|
371
|
+
|
|
372
|
+
if max_elapsed.positive? && (elapsed > max_elapsed)
|
|
373
|
+
puts boldwhite(title) if title
|
|
374
|
+
human = elapsed.time_string(format: :natural)
|
|
375
|
+
res = Prompt.yn(yellow("Did this entry actually take #{human}"), default_response: true)
|
|
376
|
+
unless res
|
|
377
|
+
new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
|
|
378
|
+
raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
|
|
379
|
+
|
|
380
|
+
finish_date = date + new_elapsed if new_elapsed
|
|
381
|
+
end
|
|
382
382
|
end
|
|
383
|
+
|
|
384
|
+
finish_date
|
|
383
385
|
end
|
|
384
386
|
end
|
|
385
387
|
end
|