doing 2.1.39 → 2.1.40
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 +23 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -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/on.rb +3 -16
- 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/today.rb +2 -13
- data/bin/commands/view.rb +1 -1
- data/bin/commands/yesterday.rb +2 -13
- data/bin/doing +15 -8
- 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 +90 -1615
- 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 +293 -773
- data/docs/doc/top-level-namespace.html +3 -3
- data/docs/index.md +1 -1
- data/doing.rdoc +49 -7
- data/lib/completion/_doing.zsh +5 -5
- data/lib/completion/doing.bash +8 -8
- data/lib/completion/doing.fish +7 -2
- data/lib/doing/add_options.rb +31 -1
- data/lib/doing/chronify/array.rb +64 -22
- 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 -667
- data/lib/doing/items.rb +38 -13
- 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/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 -2317
- metadata +19 -2
data/lib/doing/items.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Doing
|
|
4
|
-
#
|
|
4
|
+
# A collection of Item objects
|
|
5
5
|
class Items < Array
|
|
6
6
|
attr_accessor :sections
|
|
7
7
|
|
|
@@ -27,13 +27,29 @@ module Doing
|
|
|
27
27
|
def section?(section)
|
|
28
28
|
has_section = false
|
|
29
29
|
section = section.is_a?(Section) ? section.title.downcase : section.downcase
|
|
30
|
-
@sections.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
@sections.map { |i| i.title.downcase }.include?(section)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
## Return the best section match for a search query
|
|
35
|
+
##
|
|
36
|
+
## @param frag The search query
|
|
37
|
+
## @param distance The distance apart characters can be (fuzziness)
|
|
38
|
+
##
|
|
39
|
+
## @return [Section] (first) matching section object
|
|
40
|
+
##
|
|
41
|
+
def guess_section(frag, distance: 2)
|
|
42
|
+
section = nil
|
|
43
|
+
re = frag.to_rx(distance: distance, case_type: :ignore)
|
|
44
|
+
@sections.each do |sect|
|
|
45
|
+
next unless sect.title =~ /#{re}/i
|
|
46
|
+
|
|
47
|
+
Doing.logger.debug('Match:', %(Assuming "#{sect.title}" from "#{frag}"))
|
|
48
|
+
section = sect
|
|
49
|
+
break
|
|
35
50
|
end
|
|
36
|
-
|
|
51
|
+
|
|
52
|
+
section
|
|
37
53
|
end
|
|
38
54
|
|
|
39
55
|
# Add a new section to the sections array. Accepts
|
|
@@ -131,17 +147,26 @@ module Doing
|
|
|
131
147
|
end
|
|
132
148
|
|
|
133
149
|
##
|
|
134
|
-
## Return Items containing items that don't exist in
|
|
150
|
+
## Return Items containing items that don't exist in
|
|
151
|
+
## receiver
|
|
135
152
|
##
|
|
136
153
|
## @param items [Items] Receiver
|
|
137
154
|
##
|
|
155
|
+
## @return [Hash] Hash of added and deleted items
|
|
156
|
+
##
|
|
138
157
|
def diff(items)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
a = clone
|
|
159
|
+
b = items.clone
|
|
160
|
+
|
|
161
|
+
a.delete_if do |item|
|
|
162
|
+
if b.index(item)
|
|
163
|
+
b.delete(item)
|
|
164
|
+
true
|
|
165
|
+
else
|
|
166
|
+
false
|
|
167
|
+
end
|
|
143
168
|
end
|
|
144
|
-
|
|
169
|
+
{ deleted: b, added: a }
|
|
145
170
|
end
|
|
146
171
|
|
|
147
172
|
##
|
data/lib/doing/plugin_manager.rb
CHANGED
|
@@ -84,11 +84,11 @@ module Doing
|
|
|
84
84
|
def validate_plugin(title, type, klass)
|
|
85
85
|
type = valid_type(type)
|
|
86
86
|
if type == :import && !klass.respond_to?(:import)
|
|
87
|
-
raise Errors::PluginUncallable.new('Import plugins must respond to :import', type
|
|
87
|
+
raise Errors::PluginUncallable.new('Import plugins must respond to :import', type, title)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
if type == :export && !klass.respond_to?(:render)
|
|
91
|
-
raise Errors::PluginUncallable.new('Export plugins must respond to :render', type
|
|
91
|
+
raise Errors::PluginUncallable.new('Export plugins must respond to :render', type, title)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
type
|
|
@@ -113,7 +113,7 @@ module Doing
|
|
|
113
113
|
when /^e(x(p(o(r(t)?)?)?)?)?$/
|
|
114
114
|
:export
|
|
115
115
|
else
|
|
116
|
-
raise Errors::InvalidPluginType
|
|
116
|
+
raise Errors::InvalidPluginType.new('Invalid plugin type', 'unrecognized')
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
type.to_sym
|
|
@@ -17,7 +17,7 @@ module Doing
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def self.render(wwid, items, variables: {})
|
|
20
|
-
|
|
20
|
+
Doing.logger.benchmark(:template_render, :start)
|
|
21
21
|
return if items.nil?
|
|
22
22
|
|
|
23
23
|
opt = variables[:options]
|
|
@@ -126,18 +126,18 @@ module Doing
|
|
|
126
126
|
|
|
127
127
|
output.gsub!(/\\%/, '%')
|
|
128
128
|
|
|
129
|
-
output.highlight_search!(opt[:search]) if opt[:
|
|
129
|
+
output.highlight_search!(opt[:search]) if opt[:output] =~ /^temp/ && opt[:search] && !opt[:not] && opt[:hilite]
|
|
130
130
|
|
|
131
131
|
out += "#{output}\n"
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
-
# Doing.logger.debug('Template Export:', "#{items.count} items output to template #{opt[:
|
|
134
|
+
# Doing.logger.debug('Template Export:', "#{items.count} items output to template #{opt[:output]}")
|
|
135
135
|
if opt[:totals]
|
|
136
136
|
out += wwid.tag_times(format: Doing.setting('timer_format').to_sym,
|
|
137
137
|
sort_by: opt[:sort_tags],
|
|
138
138
|
sort_order: opt[:tag_order])
|
|
139
139
|
end
|
|
140
|
-
|
|
140
|
+
Doing.logger.benchmark(:template_render, :finish)
|
|
141
141
|
out
|
|
142
142
|
end
|
|
143
143
|
|
|
Binary file
|
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
|