doing 2.1.37 → 2.1.40
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +7 -1
- data/bin/commands/config.rb +43 -34
- data/bin/commands/done.rb +1 -18
- data/bin/commands/finish.rb +30 -25
- data/bin/commands/grep.rb +3 -14
- data/bin/commands/last.rb +2 -8
- data/bin/commands/meanwhile.rb +13 -6
- data/bin/commands/now.rb +2 -4
- data/bin/commands/on.rb +4 -15
- data/bin/commands/recent.rb +2 -8
- data/bin/commands/reset.rb +24 -1
- data/bin/commands/select.rb +1 -1
- data/bin/commands/show.rb +8 -16
- data/bin/commands/since.rb +1 -12
- data/bin/commands/today.rb +2 -13
- data/bin/commands/view.rb +1 -1
- data/bin/commands/yesterday.rb +2 -13
- data/bin/doing +41 -36
- data/docs/doc/Array.html +1 -1
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +166 -20
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +1 -1
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +7 -3
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +7 -3
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +10 -2
- data/docs/doc/Doing/Errors/HistoryLimitError.html +194 -0
- data/docs/doc/Doing/Errors/InvalidPlugin.html +194 -0
- data/docs/doc/Doing/Errors/MissingBackupFile.html +194 -0
- data/docs/doc/Doing/Errors/NoResults.html +10 -2
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +10 -2
- data/docs/doc/Doing/Errors/WrongCommand.html +10 -2
- data/docs/doc/Doing/Errors.html +9 -9
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +114 -1576
- data/docs/doc/Doing/Items.html +121 -5
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/Note.html +1 -1
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +2 -2
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +5 -5
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +197 -4033
- data/docs/doc/Doing.html +2 -2
- data/docs/doc/FalseClass.html +1 -1
- data/docs/doc/GLI/Commands/Help.html +1 -1
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Object.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +1 -1
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +26 -5
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +237 -709
- data/docs/doc/top-level-namespace.html +3 -3
- data/docs/index.md +1 -1
- data/doing.rdoc +54 -7
- data/lib/completion/_doing.zsh +6 -6
- data/lib/completion/doing.bash +10 -10
- data/lib/completion/doing.fish +8 -2
- data/lib/doing/add_options.rb +31 -1
- data/lib/doing/chronify/array.rb +68 -18
- data/lib/doing/chronify/string.rb +3 -1
- data/lib/doing/colors.rb +77 -30
- data/lib/doing/completion.rb +4 -5
- data/lib/doing/errors.rb +51 -35
- data/lib/doing/hooks.rb +3 -3
- data/lib/doing/item/dates.rb +112 -0
- data/lib/doing/item/query.rb +433 -0
- data/lib/doing/item/state.rb +59 -0
- data/lib/doing/item/tags.rb +87 -0
- data/lib/doing/item.rb +6 -537
- data/lib/doing/items.rb +39 -14
- data/lib/doing/plugin_manager.rb +3 -3
- data/lib/doing/plugins/export/template_export.rb +4 -4
- data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
- data/lib/doing/prompt.rb +6 -8
- data/lib/doing/string/tags.rb +8 -2
- data/lib/doing/util_backup.rb +6 -8
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/display.rb +399 -0
- data/lib/doing/wwid/editor.rb +214 -0
- data/lib/doing/wwid/filetools.rb +186 -0
- data/lib/doing/wwid/filter.rb +218 -0
- data/lib/doing/wwid/guess.rb +87 -0
- data/lib/doing/wwid/interactive.rb +385 -0
- data/lib/doing/wwid/modify.rb +618 -0
- data/lib/doing/wwid/tags.rb +54 -0
- data/lib/doing/wwid/timers.rb +345 -0
- data/lib/doing/wwid/wwidutil.rb +104 -0
- data/lib/doing/wwid.rb +31 -2308
- metadata +19 -2
data/lib/doing/prompt.rb
CHANGED
@@ -58,14 +58,12 @@ module Doing
|
|
58
58
|
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
59
59
|
Readline.completion_append_character = ' '
|
60
60
|
Readline.completion_proc = comp
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
prompt_text << yellow(' to cancel')
|
68
|
-
puts prompt_text.join('')
|
61
|
+
puts format(['%<promptcolor>s%<prompt>s %<textcolor>sEnter a blank line',
|
62
|
+
'(%<keycolor>sreturn twice%<textcolor>s)',
|
63
|
+
'to end editing and save,',
|
64
|
+
'%<keycolor>sCTRL-C%<textcolor>s to cancel%<reset>s'].join(' '),
|
65
|
+
{ promptcolor: boldgreen, prompt: prompt.sub(/:?$/, ':'),
|
66
|
+
textcolor: yellow, keycolor: boldwhite, reset: reset })
|
69
67
|
|
70
68
|
res = []
|
71
69
|
|
data/lib/doing/string/tags.rb
CHANGED
@@ -117,14 +117,20 @@ module Doing
|
|
117
117
|
else
|
118
118
|
Doing.logger.debug('Skipped:', "not tagged #{"@#{tag}".cyan}")
|
119
119
|
end
|
120
|
-
elsif title =~ /@#{tag}(?=[ (]|$)/
|
120
|
+
elsif title =~ /@#{tag}(?=[ (]|$)/ && !value.good?
|
121
121
|
Doing.logger.debug('Skipped:', "already tagged #{"@#{tag}".cyan}")
|
122
122
|
return title
|
123
123
|
else
|
124
124
|
add = tag
|
125
125
|
add += "(#{value})" unless value.nil?
|
126
|
+
|
126
127
|
title.chomp!
|
127
|
-
|
128
|
+
|
129
|
+
if value && title =~ /@#{tag}(?=[ (]|$)/
|
130
|
+
title.sub!(/@#{tag}(\(.*?\))?/, "@#{add}")
|
131
|
+
else
|
132
|
+
title += " @#{add}"
|
133
|
+
end
|
128
134
|
|
129
135
|
title.dedup_tags!
|
130
136
|
title.chomp!
|
data/lib/doing/util_backup.rb
CHANGED
@@ -62,7 +62,7 @@ module Doing
|
|
62
62
|
filename ||= Doing.setting('doing_file')
|
63
63
|
|
64
64
|
backup_file = last_backup(filename, count: count)
|
65
|
-
raise
|
65
|
+
raise HistoryLimitError, 'End of undo history' if backup_file.nil?
|
66
66
|
|
67
67
|
save_undone(filename)
|
68
68
|
FileUtils.mv(backup_file, filename)
|
@@ -86,7 +86,7 @@ module Doing
|
|
86
86
|
skipped = undones.slice!(0, count)
|
87
87
|
undone = skipped.pop
|
88
88
|
|
89
|
-
raise
|
89
|
+
raise HistoryLimitError, 'End of redo history' if undone.nil?
|
90
90
|
|
91
91
|
redo_file = File.join(backup_dir, undone)
|
92
92
|
|
@@ -118,8 +118,7 @@ module Doing
|
|
118
118
|
filename ||= Doing.setting('doing_file')
|
119
119
|
|
120
120
|
undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort
|
121
|
-
|
122
|
-
raise DoingRuntimeError, 'End of redo history' if undones.empty?
|
121
|
+
raise HistoryLimitError, 'End of redo history' if undones.empty?
|
123
122
|
|
124
123
|
total = undones.count
|
125
124
|
options = undones.each_with_object([]) do |file, arr|
|
@@ -128,8 +127,7 @@ module Doing
|
|
128
127
|
|
129
128
|
arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}")
|
130
129
|
end
|
131
|
-
|
132
|
-
raise DoingRuntimeError, 'No backup files to load' if options.empty?
|
130
|
+
raise MissingBackupFile, 'No backup files to load' if options.empty?
|
133
131
|
|
134
132
|
backup_file = show_menu(options, filename)
|
135
133
|
idx = undones.index(File.basename(backup_file))
|
@@ -163,7 +161,7 @@ module Doing
|
|
163
161
|
arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}")
|
164
162
|
end
|
165
163
|
|
166
|
-
raise
|
164
|
+
raise MissingBackupFile, 'No backup files to load' if options.empty?
|
167
165
|
|
168
166
|
backup_file = show_menu(options, filename)
|
169
167
|
Util.write_to_file(File.join(backup_dir, "undone___#{File.basename(filename)}"), IO.read(filename), backup: false)
|
@@ -281,7 +279,7 @@ module Doing
|
|
281
279
|
def create_backup_dir
|
282
280
|
dir = File.expand_path(Doing.setting('backup_dir')) || File.join(user_home, '.doing_backup')
|
283
281
|
if File.exist?(dir) && !File.directory?(dir)
|
284
|
-
raise
|
282
|
+
raise DoingNoTraceError.new("#{dir} is not a directory", topic: 'History:', exit_code: 27)
|
285
283
|
|
286
284
|
end
|
287
285
|
|
data/lib/doing/version.rb
CHANGED
@@ -0,0 +1,399 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
class WWID
|
5
|
+
# Display methods for WWID class
|
6
|
+
module Display
|
7
|
+
##
|
8
|
+
## Display contents of a section based on options
|
9
|
+
##
|
10
|
+
## @param opt [Hash] Additional Options
|
11
|
+
##
|
12
|
+
def list_section(opt, items: Items.new)
|
13
|
+
logger.benchmark(:list_section, :start)
|
14
|
+
opt[:config_template] ||= 'default'
|
15
|
+
|
16
|
+
tpl_cfg = Doing.setting(['templates', opt[:config_template]])
|
17
|
+
|
18
|
+
cfg = if opt[:view_template]
|
19
|
+
Doing.setting(['views', opt[:view_template]]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
|
20
|
+
else
|
21
|
+
tpl_cfg
|
22
|
+
end
|
23
|
+
|
24
|
+
cfg.deep_merge({
|
25
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
26
|
+
'date_format' => Doing.setting('default_date_format'),
|
27
|
+
'order' => Doing.setting('order') || :asc,
|
28
|
+
'tags_color' => Doing.setting('tags_color'),
|
29
|
+
'duration' => Doing.setting('duration'),
|
30
|
+
'interval_format' => Doing.setting('interval_format')
|
31
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
32
|
+
|
33
|
+
opt[:duration] ||= cfg['duration'] || false
|
34
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
35
|
+
opt[:count] ||= 0
|
36
|
+
opt[:age] ||= :newest
|
37
|
+
opt[:age] = opt[:age].normalize_age
|
38
|
+
opt[:format] ||= cfg['date_format']
|
39
|
+
opt[:order] ||= cfg['order'] || :asc
|
40
|
+
opt[:tag_order] ||= :asc
|
41
|
+
opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
|
42
|
+
opt[:template] ||= cfg['template']
|
43
|
+
opt[:sort_tags] ||= opt[:tag_sort]
|
44
|
+
|
45
|
+
# opt[:highlight] ||= true
|
46
|
+
title = ''
|
47
|
+
is_single = true
|
48
|
+
if opt[:section].nil?
|
49
|
+
opt[:section] = choose_section
|
50
|
+
title = opt[:section]
|
51
|
+
elsif opt[:section].instance_of?(String)
|
52
|
+
title = if opt[:section] =~ /^all$/i
|
53
|
+
if opt[:page_title]
|
54
|
+
opt[:page_title]
|
55
|
+
elsif opt[:tag_filter] && opt[:tag_filter]['bool'].normalize_bool != :not
|
56
|
+
opt[:tag_filter]['tags'].map { |tag| "@#{tag}" }.join(' + ')
|
57
|
+
else
|
58
|
+
'doing'
|
59
|
+
end
|
60
|
+
else
|
61
|
+
guess_section(opt[:section])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
items = filter_items(items, opt: opt)
|
66
|
+
|
67
|
+
items.reverse! unless opt[:order].normalize_order == :desc
|
68
|
+
|
69
|
+
if opt[:delete]
|
70
|
+
delete_items(items, force: opt[:force])
|
71
|
+
|
72
|
+
write(@doing_file)
|
73
|
+
return
|
74
|
+
elsif opt[:editor]
|
75
|
+
edit_items(items)
|
76
|
+
|
77
|
+
write(@doing_file)
|
78
|
+
return
|
79
|
+
elsif opt[:interactive]
|
80
|
+
opt[:menu] = !opt[:force]
|
81
|
+
opt[:query] = '' # opt[:search]
|
82
|
+
opt[:multiple] = true
|
83
|
+
selected = Prompt.choose_from_items(items.reverse, include_section: opt[:section] =~ /^all$/i, **opt)
|
84
|
+
|
85
|
+
raise NoResults, 'no items selected' if selected.nil? || selected.empty?
|
86
|
+
|
87
|
+
act_on(selected, opt)
|
88
|
+
return
|
89
|
+
end
|
90
|
+
|
91
|
+
opt[:output] ||= 'template'
|
92
|
+
opt[:wrap_width] ||= Doing.setting('templates.default.wrap_width', 0)
|
93
|
+
|
94
|
+
logger.benchmark(:list_section, :finish)
|
95
|
+
output(items, title, is_single, opt)
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
## Display entries within a date range
|
100
|
+
##
|
101
|
+
## @param dates [Array] [start, end]
|
102
|
+
## @param section [String] The section
|
103
|
+
## @param times (Bool) Show times
|
104
|
+
## @param output [String] Output format
|
105
|
+
## @param opt [Hash] Additional Options
|
106
|
+
##
|
107
|
+
def list_date(dates, section, times = nil, output = nil, opt)
|
108
|
+
opt ||= {}
|
109
|
+
opt[:totals] ||= false
|
110
|
+
opt[:sort_tags] ||= false
|
111
|
+
section = guess_section(section)
|
112
|
+
# :date_filter expects an array with start and end date
|
113
|
+
dates = dates.split_date_range if dates.instance_of?(String)
|
114
|
+
|
115
|
+
opt[:section] = section
|
116
|
+
opt[:count] = 0
|
117
|
+
opt[:order] = :asc
|
118
|
+
opt[:date_filter] = dates
|
119
|
+
opt[:times] = times
|
120
|
+
opt[:output] = output
|
121
|
+
|
122
|
+
time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
|
123
|
+
if opt[:from] && opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
|
124
|
+
opt[:time_filter] = opt[:from]
|
125
|
+
end
|
126
|
+
|
127
|
+
list_section(opt)
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
## Show all entries from the current day
|
132
|
+
##
|
133
|
+
## @param times [Boolean] show times
|
134
|
+
## @param output [String] output format
|
135
|
+
## @param opt [Hash] Options
|
136
|
+
##
|
137
|
+
def today(times = true, output = nil, opt)
|
138
|
+
opt ||= {}
|
139
|
+
opt[:totals] ||= false
|
140
|
+
opt[:sort_tags] ||= false
|
141
|
+
|
142
|
+
cfg = Doing.setting('templates').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
143
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
144
|
+
'date_format' => Doing.setting('default_date_format'),
|
145
|
+
'order' => Doing.setting('order') || :asc,
|
146
|
+
'tags_color' => Doing.setting('tags_color'),
|
147
|
+
'duration' => Doing.setting('duration'),
|
148
|
+
'interval_format' => Doing.setting('interval_format')
|
149
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
150
|
+
|
151
|
+
template = opt[:template] || cfg['template']
|
152
|
+
|
153
|
+
opt[:duration] ||= cfg['duration'] || false
|
154
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
155
|
+
|
156
|
+
options = {
|
157
|
+
after: opt[:after],
|
158
|
+
before: opt[:before],
|
159
|
+
count: 0,
|
160
|
+
duration: opt[:duration],
|
161
|
+
from: opt[:from],
|
162
|
+
format: cfg['date_format'],
|
163
|
+
interval_format: opt[:interval_format],
|
164
|
+
only_timed: opt[:only_timed],
|
165
|
+
order: cfg['order'] || :asc,
|
166
|
+
output: output,
|
167
|
+
section: opt[:section],
|
168
|
+
sort_tags: opt[:sort_tags],
|
169
|
+
template: template,
|
170
|
+
times: times,
|
171
|
+
today: true,
|
172
|
+
totals: opt[:totals],
|
173
|
+
wrap_width: cfg['wrap_width'],
|
174
|
+
tags_color: cfg['tags_color'],
|
175
|
+
config_template: opt[:config_template]
|
176
|
+
}
|
177
|
+
list_section(options)
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
## Show entries from the previous day
|
182
|
+
##
|
183
|
+
## @param section [String] The section
|
184
|
+
## @param times (Bool) Show times
|
185
|
+
## @param output [String] Output format
|
186
|
+
## @param opt [Hash] Additional Options
|
187
|
+
##
|
188
|
+
def yesterday(section, times = nil, output = nil, opt)
|
189
|
+
opt ||= {}
|
190
|
+
opt[:totals] ||= false
|
191
|
+
opt[:sort_tags] ||= false
|
192
|
+
opt[:config_template] ||= 'today'
|
193
|
+
opt[:yesterday] = true
|
194
|
+
|
195
|
+
section = guess_section(section)
|
196
|
+
y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d')
|
197
|
+
opt[:after] = "#{y} #{opt[:after]}" if opt[:after]
|
198
|
+
opt[:before] = "#{y} #{opt[:before]}" if opt[:before]
|
199
|
+
|
200
|
+
opt[:output] = output
|
201
|
+
opt[:section] = section
|
202
|
+
opt[:times] = times
|
203
|
+
opt[:count] = 0
|
204
|
+
|
205
|
+
list_section(opt)
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
## Show recent entries
|
210
|
+
##
|
211
|
+
## @param count [Integer] The number to show
|
212
|
+
## @param section [String] The section to show from, default Currently
|
213
|
+
## @param opt [Hash] Additional Options
|
214
|
+
##
|
215
|
+
def recent(count = 10, section = nil, opt)
|
216
|
+
opt ||= {}
|
217
|
+
times = opt[:t] || true
|
218
|
+
opt[:totals] ||= false
|
219
|
+
opt[:sort_tags] ||= false
|
220
|
+
|
221
|
+
cfg = Doing.setting('templates.recent').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
222
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
223
|
+
'date_format' => Doing.setting('default_date_format'),
|
224
|
+
'order' => Doing.setting('order') || :asc,
|
225
|
+
'tags_color' => Doing.setting('tags_color'),
|
226
|
+
'duration' => Doing.setting('duration'),
|
227
|
+
'interval_format' => Doing.setting('interval_format')
|
228
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
229
|
+
opt[:duration] ||= cfg['duration'] || false
|
230
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
231
|
+
|
232
|
+
section ||= Doing.setting('current_section')
|
233
|
+
section = guess_section(section)
|
234
|
+
|
235
|
+
opt[:section] = section
|
236
|
+
opt[:wrap_width] = cfg['wrap_width']
|
237
|
+
opt[:count] = count
|
238
|
+
opt[:format] = cfg['date_format']
|
239
|
+
opt[:template] = opt[:template] || cfg['template']
|
240
|
+
opt[:order] = :asc
|
241
|
+
opt[:times] = times
|
242
|
+
|
243
|
+
list_section(opt)
|
244
|
+
end
|
245
|
+
|
246
|
+
##
|
247
|
+
## Show the last entry
|
248
|
+
##
|
249
|
+
## @param times (Bool) Show times
|
250
|
+
## @param section [String] Section to pull from, default Currently
|
251
|
+
##
|
252
|
+
def last(times: true, section: nil, options: {})
|
253
|
+
section = section.nil? || section =~ /all/i ? 'All' : guess_section(section)
|
254
|
+
cfg = Doing.setting(['templates', options[:config_template]]).deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
255
|
+
'wrap_width' => Doing.setting('wrap_width', 0),
|
256
|
+
'date_format' => Doing.setting('default_date_format'),
|
257
|
+
'order' => Doing.setting('order', :asc),
|
258
|
+
'tags_color' => Doing.setting('tags_color'),
|
259
|
+
'duration' => Doing.setting('duration'),
|
260
|
+
'interval_format' => Doing.setting('interval_format')
|
261
|
+
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
262
|
+
options[:duration] ||= cfg['duration'] || false
|
263
|
+
options[:interval_format] ||= cfg['interval_format'] || 'text'
|
264
|
+
|
265
|
+
opts = {
|
266
|
+
case: options[:case],
|
267
|
+
config_template: options[:config_template] || 'last',
|
268
|
+
count: 1,
|
269
|
+
delete: options[:delete],
|
270
|
+
duration: options[:duration],
|
271
|
+
format: cfg['date_format'],
|
272
|
+
interval_format: options[:interval_format],
|
273
|
+
not: options[:negate],
|
274
|
+
output: options[:output],
|
275
|
+
section: section,
|
276
|
+
template: options[:template] || cfg['template'],
|
277
|
+
times: times,
|
278
|
+
val: options[:val],
|
279
|
+
wrap_width: cfg['wrap_width']
|
280
|
+
}
|
281
|
+
|
282
|
+
if options[:tag]
|
283
|
+
opts[:tag_filter] = {
|
284
|
+
'tags' => options[:tag],
|
285
|
+
'bool' => options[:tag_bool]
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
opts[:search] = options[:search] if options[:search]
|
290
|
+
|
291
|
+
list_section(opts)
|
292
|
+
end
|
293
|
+
|
294
|
+
##
|
295
|
+
## Return the content of the last note for a given section
|
296
|
+
##
|
297
|
+
## @param section [String] The section to retrieve from, default
|
298
|
+
## All
|
299
|
+
##
|
300
|
+
def last_note(section = 'All')
|
301
|
+
section = guess_section(section)
|
302
|
+
|
303
|
+
last_item = last_entry({ section: section })
|
304
|
+
|
305
|
+
raise NoEntryError, 'No entry found' unless last_item
|
306
|
+
|
307
|
+
logger.log_now(:info, 'Edit note:', last_item.title)
|
308
|
+
|
309
|
+
note = last_item.note&.to_s || ''
|
310
|
+
"#{last_item.title}\n# EDIT BELOW THIS LINE ------------\n#{note}"
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
## Get the last entry
|
315
|
+
##
|
316
|
+
## @param opt [Hash] Additional Options
|
317
|
+
##
|
318
|
+
def last_entry(opt)
|
319
|
+
opt ||= {}
|
320
|
+
opt[:tag_bool] ||= :and
|
321
|
+
opt[:section] ||= Doing.setting('current_section')
|
322
|
+
|
323
|
+
items = filter_items(Items.new, opt: opt)
|
324
|
+
|
325
|
+
logger.debug('Filtered:', "Parameters matched #{items.count} entries")
|
326
|
+
|
327
|
+
if opt[:interactive]
|
328
|
+
last_entry = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i,
|
329
|
+
menu: true,
|
330
|
+
header: '',
|
331
|
+
prompt: 'Select an entry > ',
|
332
|
+
multiple: false,
|
333
|
+
sort: false,
|
334
|
+
show_if_single: true
|
335
|
+
)
|
336
|
+
else
|
337
|
+
last_entry = items.max_by { |item| item.date }
|
338
|
+
end
|
339
|
+
|
340
|
+
last_entry
|
341
|
+
end
|
342
|
+
|
343
|
+
private
|
344
|
+
|
345
|
+
##
|
346
|
+
## Generate output using available export plugins
|
347
|
+
##
|
348
|
+
## @param items [Array] The items
|
349
|
+
## @param title [String] Page title
|
350
|
+
## @param is_single [Boolean] Indicates if single
|
351
|
+
## section
|
352
|
+
## @param opt [Hash] Additional options
|
353
|
+
##
|
354
|
+
## @return [String] formatted output based on opt[:output]
|
355
|
+
## template trigger
|
356
|
+
## @api private
|
357
|
+
def output(items, title, is_single, opt)
|
358
|
+
logger.benchmark(:output, :start)
|
359
|
+
opt ||= {}
|
360
|
+
out = nil
|
361
|
+
|
362
|
+
raise InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export)
|
363
|
+
|
364
|
+
export_options = { page_title: title, is_single: is_single, options: opt }
|
365
|
+
|
366
|
+
Hooks.trigger :pre_export, self, opt[:output], items
|
367
|
+
|
368
|
+
Plugins.plugins[:export].each do |_, options|
|
369
|
+
next unless opt[:output] =~ /^(#{options[:trigger].normalize_trigger})$/i
|
370
|
+
|
371
|
+
out = options[:class].render(self, items, variables: export_options)
|
372
|
+
break
|
373
|
+
end
|
374
|
+
|
375
|
+
logger.debug('Output:', "#{items.count} #{items.count == 1 ? 'item' : 'items'} shown")
|
376
|
+
logger.benchmark(:output, :finish)
|
377
|
+
out
|
378
|
+
end
|
379
|
+
|
380
|
+
##
|
381
|
+
## Get next item in the index
|
382
|
+
##
|
383
|
+
## @param item [Item] target item
|
384
|
+
## @param options [Hash] additional options
|
385
|
+
## @see #filter_items
|
386
|
+
##
|
387
|
+
## @return [Item] the next chronological item in the index
|
388
|
+
##
|
389
|
+
def next_item(item, options = {})
|
390
|
+
options ||= {}
|
391
|
+
items = filter_items(Items.new, opt: options)
|
392
|
+
|
393
|
+
idx = items.index(item)
|
394
|
+
|
395
|
+
idx.positive? ? items[idx - 1] : nil
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|