doing 2.1.89 → 2.1.91
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/.irbrc +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +251 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +5 -3
- data/bin/commands/changes.rb +3 -1
- data/bin/commands/choose.rb +2 -0
- data/bin/commands/colors.rb +5 -3
- data/bin/commands/commands_accepting.rb +3 -3
- data/bin/commands/completion.rb +2 -1
- data/bin/commands/config.rb +8 -6
- data/bin/commands/done.rb +19 -12
- data/bin/commands/finish.rb +4 -2
- data/bin/commands/grep.rb +9 -5
- data/bin/commands/import.rb +14 -11
- data/bin/commands/install_fzf.rb +4 -2
- data/bin/commands/last.rb +31 -27
- data/bin/commands/meanwhile.rb +6 -2
- data/bin/commands/note.rb +6 -3
- data/bin/commands/now.rb +2 -0
- data/bin/commands/open.rb +6 -1
- data/bin/commands/plugins.rb +2 -0
- data/bin/commands/recent.rb +47 -33
- data/bin/commands/reset.rb +7 -3
- data/bin/commands/rotate.rb +6 -3
- data/bin/commands/sections.rb +3 -3
- data/bin/commands/select.rb +2 -0
- data/bin/commands/show.rb +24 -20
- data/bin/commands/since.rb +10 -3
- data/bin/commands/tag.rb +18 -16
- data/bin/commands/tag_dir.rb +5 -2
- data/bin/commands/tags.rb +17 -17
- data/bin/commands/template.rb +8 -2
- data/bin/commands/today.rb +10 -2
- data/bin/commands/undo.rb +2 -0
- data/bin/commands/update.rb +9 -7
- data/bin/commands/view.rb +11 -8
- data/bin/commands/views.rb +2 -0
- data/bin/commands/yesterday.rb +6 -1
- data/bin/doing +54 -57
- data/docs/doc/Array.html +3 -3
- data/docs/doc/BooleanTermParser/Clause.html +3 -3
- data/docs/doc/BooleanTermParser/Operator.html +3 -3
- data/docs/doc/BooleanTermParser/Query.html +3 -3
- data/docs/doc/BooleanTermParser/QueryParser.html +3 -3
- data/docs/doc/BooleanTermParser/QueryTransformer.html +3 -3
- data/docs/doc/BooleanTermParser.html +3 -3
- data/docs/doc/Doing/ArrayCleanup.html +3 -3
- data/docs/doc/Doing/ArrayNestedHash.html +3 -3
- data/docs/doc/Doing/ArrayTags.html +9 -9
- data/docs/doc/Doing/ByDayExport.html +3 -3
- data/docs/doc/Doing/CSVExport.html +4 -4
- data/docs/doc/Doing/CalendarImport.html +4 -4
- data/docs/doc/Doing/Change.html +3 -3
- data/docs/doc/Doing/Changes.html +3 -3
- data/docs/doc/Doing/ChronifyArray.html +3 -3
- data/docs/doc/Doing/ChronifyNumeric.html +3 -3
- data/docs/doc/Doing/ChronifyString.html +6 -6
- data/docs/doc/Doing/Color.html +88 -47
- data/docs/doc/Doing/Completion/BashCompletions.html +3 -3
- data/docs/doc/Doing/Completion/FigCompletions.html +3 -3
- data/docs/doc/Doing/Completion/FishCompletions.html +3 -3
- data/docs/doc/Doing/Completion/StringUtils.html +3 -3
- data/docs/doc/Doing/Completion/ZshCompletions.html +3 -3
- data/docs/doc/Doing/Completion.html +5 -5
- data/docs/doc/Doing/Configuration.html +6 -6
- data/docs/doc/Doing/DayOneRenderer.html +3 -3
- data/docs/doc/Doing/DayoneExport.html +4 -4
- data/docs/doc/Doing/DoingExport.html +5 -5
- data/docs/doc/Doing/DoingImport.html +4 -4
- data/docs/doc/Doing/Entry.html +3 -3
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +3 -3
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +3 -3
- data/docs/doc/Doing/Errors/DoingStandardError.html +3 -3
- data/docs/doc/Doing/Errors/EmptyInput.html +3 -3
- data/docs/doc/Doing/Errors/HistoryLimitError.html +3 -3
- data/docs/doc/Doing/Errors/InvalidPlugin.html +3 -3
- data/docs/doc/Doing/Errors/MissingBackupFile.html +3 -3
- data/docs/doc/Doing/Errors/NoResults.html +3 -3
- data/docs/doc/Doing/Errors/PluginException.html +3 -3
- data/docs/doc/Doing/Errors/UserCancelled.html +3 -3
- data/docs/doc/Doing/Errors/WrongCommand.html +3 -3
- data/docs/doc/Doing/Errors.html +3 -3
- data/docs/doc/Doing/HTMLExport.html +4 -4
- data/docs/doc/Doing/Hooks.html +3 -16
- data/docs/doc/Doing/Item.html +4 -4
- data/docs/doc/Doing/ItemDates.html +3 -3
- data/docs/doc/Doing/ItemQuery.html +3 -3
- data/docs/doc/Doing/ItemState.html +3 -3
- data/docs/doc/Doing/ItemTags.html +3 -3
- data/docs/doc/Doing/Items.html +3 -3
- data/docs/doc/Doing/JSONExport.html +4 -4
- data/docs/doc/Doing/JSONImport.html +4 -4
- data/docs/doc/Doing/Logger.html +183 -3
- data/docs/doc/Doing/MarkdownExport.html +4 -4
- data/docs/doc/Doing/Note.html +3 -3
- data/docs/doc/Doing/Pager.html +54 -58
- data/docs/doc/Doing/Plugins.html +3 -3
- data/docs/doc/Doing/Prompt.html +4 -4
- data/docs/doc/Doing/PromptChoose.html +3 -3
- data/docs/doc/Doing/PromptFZF.html +3 -3
- data/docs/doc/Doing/PromptInput.html +3 -3
- data/docs/doc/Doing/PromptSTD.html +3 -3
- data/docs/doc/Doing/PromptYN.html +3 -3
- data/docs/doc/Doing/Section.html +3 -3
- data/docs/doc/Doing/StringHighlight.html +3 -3
- data/docs/doc/Doing/StringNormalize.html +3 -3
- data/docs/doc/Doing/StringQuery.html +4 -4
- data/docs/doc/Doing/StringTags.html +3 -3
- data/docs/doc/Doing/StringTransform.html +3 -3
- data/docs/doc/Doing/StringTruncate.html +3 -3
- data/docs/doc/Doing/StringURL.html +3 -3
- data/docs/doc/Doing/SymbolNormalize.html +5 -5
- data/docs/doc/Doing/TaskPaperExport.html +4 -4
- data/docs/doc/Doing/TemplateExport.html +5 -5
- data/docs/doc/Doing/TemplateString.html +7 -7
- data/docs/doc/Doing/TimingImport.html +4 -4
- data/docs/doc/Doing/Types.html +3 -3
- data/docs/doc/Doing/Util/Backup.html +4 -17
- data/docs/doc/Doing/Util.html +56 -61
- data/docs/doc/Doing/Version.html +3 -3
- data/docs/doc/Doing/WWID.html +6 -4
- data/docs/doc/Doing.html +4 -4
- data/docs/doc/FalseClass.html +3 -3
- data/docs/doc/GLI/Commands/Help.html +5 -5
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +3 -3
- data/docs/doc/GLI/Commands.html +3 -3
- data/docs/doc/GLI.html +3 -3
- data/docs/doc/Hash.html +4 -4
- data/docs/doc/Numeric.html +3 -3
- data/docs/doc/Object.html +3 -3
- data/docs/doc/PhraseParser/Operator.html +3 -3
- data/docs/doc/PhraseParser/PhraseClause.html +3 -3
- data/docs/doc/PhraseParser/Query.html +3 -3
- data/docs/doc/PhraseParser/QueryParser.html +3 -3
- data/docs/doc/PhraseParser/QueryTransformer.html +3 -3
- data/docs/doc/PhraseParser/TermClause.html +3 -3
- data/docs/doc/PhraseParser.html +3 -3
- data/docs/doc/Status.html +3 -3
- data/docs/doc/String.html +51 -5
- data/docs/doc/Symbol.html +3 -3
- data/docs/doc/Time.html +3 -3
- data/docs/doc/TrueClass.html +3 -3
- data/docs/doc/_index.html +4 -4
- data/docs/doc/class_list.html +6 -3
- data/docs/doc/css/full_list.css +3 -3
- data/docs/doc/css/style.css +6 -0
- data/docs/doc/file.README.html +4 -4
- data/docs/doc/file_list.html +5 -2
- data/docs/doc/frames.html +1 -1
- data/docs/doc/index.html +4 -4
- data/docs/doc/js/app.js +294 -264
- data/docs/doc/js/full_list.js +30 -4
- data/docs/doc/method_list.html +443 -392
- data/docs/doc/top-level-namespace.html +3 -3
- data/doing.gemspec +2 -0
- data/doing.rdoc +1 -30
- data/example_plugin.rb +1 -4
- data/lib/completion/_doing.zsh +0 -4
- data/lib/completion/doing.bash +0 -11
- data/lib/completion/doing.fish +0 -5
- data/lib/completion/doing.ts +0 -40
- data/lib/doing/add_options.rb +2 -2
- data/lib/doing/array/cleanup.rb +2 -0
- data/lib/doing/array/nested_hash.rb +2 -0
- data/lib/doing/boolean_term_parser.rb +3 -3
- data/lib/doing/changelog/changes.rb +4 -5
- data/lib/doing/changelog/version.rb +8 -11
- data/lib/doing/chronify/array.rb +4 -4
- data/lib/doing/chronify/string.rb +5 -4
- data/lib/doing/cli_status.rb +7 -2
- data/lib/doing/colors.rb +93 -51
- data/lib/doing/completion/bash_completion.rb +36 -39
- data/lib/doing/completion/completion_string.rb +2 -1
- data/lib/doing/completion/fig_completion.rb +33 -33
- data/lib/doing/completion/fish_completion.rb +22 -23
- data/lib/doing/completion/zsh_completion.rb +5 -5
- data/lib/doing/completion.rb +7 -4
- data/lib/doing/configuration.rb +21 -13
- data/lib/doing/errors.rb +1 -4
- data/lib/doing/good.rb +1 -1
- data/lib/doing/hash.rb +4 -4
- data/lib/doing/help_monkey_patch.rb +1 -1
- data/lib/doing/hooks.rb +6 -2
- data/lib/doing/item/dates.rb +3 -1
- data/lib/doing/item/item.rb +1 -1
- data/lib/doing/item/query.rb +14 -14
- data/lib/doing/item/state.rb +2 -0
- data/lib/doing/logger.rb +113 -45
- data/lib/doing/markdown_document_listener.rb +25 -25
- data/lib/doing/normalize.rb +1 -1
- data/lib/doing/pager.rb +73 -29
- data/lib/doing/plugin_manager.rb +4 -5
- data/lib/doing/plugins/export/byday.rb +1 -1
- data/lib/doing/plugins/export/dayone_export.rb +28 -27
- data/lib/doing/plugins/export/doing_export.rb +1 -1
- data/lib/doing/plugins/export/html_export.rb +3 -3
- data/lib/doing/plugins/export/json_export.rb +4 -5
- data/lib/doing/plugins/export/markdown_export.rb +10 -2
- data/lib/doing/plugins/export/taskpaper_export.rb +1 -0
- data/lib/doing/plugins/export/template_export.rb +110 -107
- data/lib/doing/plugins/import/calendar_import.rb +1 -1
- data/lib/doing/plugins/import/doing_import.rb +2 -2
- data/lib/doing/plugins/import/timing_import.rb +3 -3
- data/lib/doing/prompt/choose.rb +5 -6
- data/lib/doing/prompt/fzf.rb +3 -2
- data/lib/doing/prompt/input.rb +10 -9
- data/lib/doing/prompt/yn.rb +9 -11
- data/lib/doing/string/tags.rb +7 -4
- data/lib/doing/string/transform.rb +15 -10
- data/lib/doing/string/truncate.rb +1 -0
- data/lib/doing/string/url.rb +1 -1
- data/lib/doing/template_string.rb +13 -9
- data/lib/doing/time.rb +4 -2
- data/lib/doing/util.rb +12 -11
- data/lib/doing/util_backup.rb +29 -26
- data/lib/doing/version.rb +3 -1
- data/lib/doing/wwid/display.rb +182 -151
- data/lib/doing/wwid/editor.rb +13 -12
- data/lib/doing/wwid/filetools.rb +13 -11
- data/lib/doing/wwid/filter.rb +41 -40
- data/lib/doing/wwid/guess.rb +6 -8
- data/lib/doing/wwid/interactive.rb +10 -10
- data/lib/doing/wwid/modify.rb +55 -55
- data/lib/doing/wwid/timers.rb +4 -5
- data/lib/doing/wwid/wwid.rb +0 -0
- data/lib/doing/wwid/wwidutil.rb +8 -10
- data/lib/examples/commands/wiki.rb +2 -0
- data/lib/examples/plugins/capture_thing_import.rb +1 -1
- data/lib/examples/plugins/say_export.rb +1 -2
- data/lib/examples/plugins/wiki_export/wiki_export.rb +3 -4
- data/lib/helpers/fzf/test/test_go.rb +5 -5
- data/lib/helpers/threaded_tests.rb +20 -20
- data/lib/helpers/threaded_tests_string.rb +2 -0
- data/rdoc_to_mmd.rb +5 -4
- data/rdocfixer.rb +1 -2
- data/scripts/deploy.rb +3 -4
- data/scripts/generate_bash_completions.rb +40 -43
- data/scripts/generate_fish_completions.rb +17 -15
- data/scripts/generate_zsh_completions.rb +9 -7
- data/scripts/setting_replace.rb +1 -0
- data/scripts/sort_commands.rb +4 -2
- data/yard_templates/default/method_details/setup.rb +3 -1
- metadata +3 -1
data/lib/doing/wwid/editor.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'English'
|
|
3
4
|
module Doing
|
|
4
5
|
class WWID
|
|
5
6
|
##
|
|
@@ -37,7 +38,7 @@ module Doing
|
|
|
37
38
|
Process.wait(pid)
|
|
38
39
|
|
|
39
40
|
begin
|
|
40
|
-
if
|
|
41
|
+
if $CHILD_STATUS.exitstatus.zero?
|
|
41
42
|
input = IO.read(tmpfile.path)
|
|
42
43
|
else
|
|
43
44
|
exit_now! 'Cancelled'
|
|
@@ -84,7 +85,7 @@ module Doing
|
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
note = Note.new
|
|
87
|
-
note.add(input_lines[1
|
|
88
|
+
note.add(input_lines[1..]) if input_lines.length > 1
|
|
88
89
|
# If title line ends in a parenthetical, use that as the note
|
|
89
90
|
if note.empty? && title =~ /\s+\(.*?\)$/
|
|
90
91
|
title.sub!(/\s+\((?<note>.*?)\)$/) do
|
|
@@ -123,24 +124,24 @@ module Doing
|
|
|
123
124
|
end
|
|
124
125
|
|
|
125
126
|
def edit_items(items)
|
|
126
|
-
items.sort_by!
|
|
127
|
+
items.sort_by!(&:date)
|
|
127
128
|
editable_items = []
|
|
128
129
|
|
|
129
130
|
items.each do |i|
|
|
130
131
|
editable = "#{i.date.strftime('%F %R')} | #{i.title}"
|
|
131
|
-
old_note = i.note
|
|
132
|
+
old_note = i.note&.strip_lines&.join("\n")
|
|
132
133
|
editable += "\n#{old_note}" unless old_note.nil?
|
|
133
134
|
editable_items << editable
|
|
134
135
|
end
|
|
135
|
-
divider =
|
|
136
|
-
notice
|
|
136
|
+
divider = '-----------'
|
|
137
|
+
notice = <<~EONOTICE
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
# - You may delete entries, but leave all divider lines (---) in place.
|
|
140
|
+
# - Start and @done dates replaced with a time string (yesterday 3pm) will
|
|
141
|
+
# be parsed automatically. Do not delete the pipe (|) between start date
|
|
142
|
+
# and entry title.
|
|
142
143
|
EONOTICE
|
|
143
|
-
input =
|
|
144
|
+
input = "#{editable_items.map(&:strip).join("\n#{divider}\n")}\n"
|
|
144
145
|
|
|
145
146
|
new_items = fork_editor(input, message: notice).split(/^#{divider}/).map(&:strip)
|
|
146
147
|
|
|
@@ -162,7 +163,7 @@ module Doing
|
|
|
162
163
|
item.date = date || items[i].date
|
|
163
164
|
item.title = title
|
|
164
165
|
item.note = note
|
|
165
|
-
if
|
|
166
|
+
if item.equal?(old_item)
|
|
166
167
|
Doing.logger.count(:skipped, level: :debug)
|
|
167
168
|
else
|
|
168
169
|
Doing.logger.count(:updated)
|
data/lib/doing/wwid/filetools.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Doing
|
|
|
8
8
|
## @param path [String] Override path to a doing file, optional
|
|
9
9
|
##
|
|
10
10
|
def init_doing_file(path = nil)
|
|
11
|
-
@doing_file =
|
|
11
|
+
@doing_file = File.expand_path(Doing.setting('doing_file'))
|
|
12
12
|
|
|
13
13
|
if path.nil?
|
|
14
14
|
create(@doing_file) unless File.exist?(@doing_file)
|
|
@@ -107,7 +107,7 @@ module Doing
|
|
|
107
107
|
keep = opt[:keep] || 0
|
|
108
108
|
tags = []
|
|
109
109
|
tags.concat(opt[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if opt[:tag]
|
|
110
|
-
bool
|
|
110
|
+
bool = opt[:bool] || :and
|
|
111
111
|
|
|
112
112
|
sect = opt[:section] !~ /^all$/i ? guess_section(opt[:section]) : 'all'
|
|
113
113
|
|
|
@@ -121,27 +121,29 @@ module Doing
|
|
|
121
121
|
|
|
122
122
|
section_items.each do |item|
|
|
123
123
|
break if counter >= max
|
|
124
|
+
|
|
124
125
|
if opt[:before]
|
|
125
126
|
time_string = opt[:before]
|
|
126
127
|
cutoff = time_string.chronify(guess: :begin)
|
|
127
128
|
end
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Hooks.trigger :post_entry_removed, self, item.clone
|
|
132
|
-
raise DoingRuntimeError, "Error deleting item: #{item}" if new_item.nil?
|
|
130
|
+
next if (!tags.empty? && !item.tags?(tags,
|
|
131
|
+
bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff)
|
|
133
132
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
new_item = @content.delete(item)
|
|
134
|
+
Hooks.trigger :post_entry_removed, self, item.clone
|
|
135
|
+
raise DoingRuntimeError, "Error deleting item: #{item}" if new_item.nil?
|
|
136
|
+
|
|
137
|
+
new_content.add_section(new_item.section, log: false)
|
|
138
|
+
new_content.push(new_item)
|
|
139
|
+
counter += 1
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
if counter.positive?
|
|
141
143
|
logger.count(:rotated,
|
|
142
144
|
level: :info,
|
|
143
145
|
count: counter,
|
|
144
|
-
message:
|
|
146
|
+
message: 'Rotated %count %items')
|
|
145
147
|
else
|
|
146
148
|
logger.info('Skipped:', 'No items were rotated')
|
|
147
149
|
end
|
data/lib/doing/wwid/filter.rb
CHANGED
|
@@ -13,7 +13,9 @@ module Doing
|
|
|
13
13
|
# @return [Items] Filtered Items array
|
|
14
14
|
#
|
|
15
15
|
def fuzzy_filter_items(items, query, case_type: :smart)
|
|
16
|
-
scannable = items.map.with_index
|
|
16
|
+
scannable = items.map.with_index do |item, idx|
|
|
17
|
+
"#{item.title} #{item.note.join(' ')}".gsub(/[|*?!]/, '') + "|#{idx}"
|
|
18
|
+
end.join("\n")
|
|
17
19
|
|
|
18
20
|
res = `echo #{Shellwords.escape(scannable)}|#{Prompt.fzf} #{fuzzy_filter_args(query, case_type).join(' ')}`
|
|
19
21
|
selected = Items.new
|
|
@@ -59,59 +61,58 @@ module Doing
|
|
|
59
61
|
## @option opt [Array] :val (nil) Array of tag value queries
|
|
60
62
|
##
|
|
61
63
|
def filter_items(items = Items.new, opt: {})
|
|
62
|
-
logger.
|
|
63
|
-
|
|
64
|
+
logger.measure(:filter_items) do
|
|
65
|
+
time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/i
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
if items.nil? || items.empty?
|
|
68
|
+
section = !opt[:section] || opt[:section].empty? ? 'All' : guess_section(opt[:section])
|
|
69
|
+
if section.is_a?(Array)
|
|
70
|
+
section.each do |s|
|
|
71
|
+
s = s[0] if s.is_a?(Array)
|
|
72
|
+
items.concat(s =~ /^all$/i ? @content.clone : @content.in_section(s))
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
items = section =~ /^all$/i ? @content.clone : @content.in_section(section)
|
|
71
76
|
end
|
|
72
|
-
else
|
|
73
|
-
items = section =~ /^all$/i ? @content.clone : @content.in_section(section)
|
|
74
77
|
end
|
|
75
|
-
end
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
unless opt[:time_filter]
|
|
80
|
+
opt[:time_filter] = [nil, nil]
|
|
81
|
+
if opt[:from] && !opt[:date_filter]
|
|
82
|
+
if opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
|
|
83
|
+
opt[:time_filter] = opt[:from]
|
|
84
|
+
elsif opt[:from][0].is_a?(Time)
|
|
85
|
+
opt[:date_filter] = opt[:from]
|
|
86
|
+
end
|
|
84
87
|
end
|
|
85
88
|
end
|
|
86
|
-
end
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if opt[:after].is_a?(String) && opt[:after] =~ time_rx
|
|
94
|
-
opt[:time_filter][0] = opt[:after]
|
|
95
|
-
opt[:after] = nil
|
|
96
|
-
end
|
|
90
|
+
if opt[:before].is_a?(String) && opt[:before] =~ time_rx
|
|
91
|
+
opt[:time_filter][1] = opt[:before]
|
|
92
|
+
opt[:before] = nil
|
|
93
|
+
end
|
|
97
94
|
|
|
98
|
-
|
|
95
|
+
if opt[:after].is_a?(String) && opt[:after] =~ time_rx
|
|
96
|
+
opt[:time_filter][0] = opt[:after]
|
|
97
|
+
opt[:after] = nil
|
|
98
|
+
end
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
items.sort_by! { |item| [item.date, item.title.downcase] }.reverse
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
filtered_items = items.select { |item| item.keep_item?(opt) }
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
count = opt[:count].to_i&.positive? ? opt[:count].to_i : filtered_items.count
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
output.concat(filtered_items.slice(0, count).reverse)
|
|
108
|
-
else
|
|
109
|
-
output.concat(filtered_items.reverse.slice(0, count))
|
|
110
|
-
end
|
|
106
|
+
output = Items.new
|
|
111
107
|
|
|
112
|
-
|
|
108
|
+
if opt[:age] && opt[:age].normalize_age == :oldest
|
|
109
|
+
output.concat(filtered_items.slice(0, count).reverse)
|
|
110
|
+
else
|
|
111
|
+
output.concat(filtered_items.reverse.slice(0, count))
|
|
112
|
+
end
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
output
|
|
115
|
+
end
|
|
115
116
|
end
|
|
116
117
|
end
|
|
117
118
|
end
|
data/lib/doing/wwid/guess.rb
CHANGED
|
@@ -9,9 +9,7 @@ module Doing
|
|
|
9
9
|
## @param guessed [Boolean] already guessed and failed
|
|
10
10
|
##
|
|
11
11
|
def guess_section(frag, guessed: false, suggest: false)
|
|
12
|
-
if frag.is_a?(Array) && frag.count == 1
|
|
13
|
-
frag = frag[0]
|
|
14
|
-
end
|
|
12
|
+
frag = frag[0] if frag.is_a?(Array) && frag.count == 1
|
|
15
13
|
|
|
16
14
|
frag = frag.split(/ *, */).map(&:strip) if frag.is_a?(String) && frag =~ /,/
|
|
17
15
|
|
|
@@ -25,7 +23,7 @@ module Doing
|
|
|
25
23
|
|
|
26
24
|
found = @content.guess_section(frag, distance: 2)
|
|
27
25
|
|
|
28
|
-
section = found
|
|
26
|
+
section = found&.title
|
|
29
27
|
|
|
30
28
|
if section && suggest
|
|
31
29
|
Doing.logger.debug('Match:', %(Assuming "#{section}" from "#{frag}"))
|
|
@@ -38,12 +36,12 @@ module Doing
|
|
|
38
36
|
prompt = Color.template("{bw}Did you mean `{xy}doing {by}view {xy}#{alt}`{bw}?{x}")
|
|
39
37
|
meant_view = Prompt.yn(prompt, default_response: 'n')
|
|
40
38
|
|
|
41
|
-
msg = format('%<y>srun with `%<w>sdoing view %<alt>s%<y>s`', w: boldwhite, y: yellow, alt: alt)
|
|
39
|
+
msg = format('%<y>srun with `%<w>sdoing view %<alt>s%<y>s`', w: Color.boldwhite, y: Color.yellow, alt: alt)
|
|
42
40
|
raise Errors::WrongCommand.new(msg, topic: 'Try again:') if meant_view
|
|
43
41
|
|
|
44
42
|
end
|
|
45
43
|
|
|
46
|
-
res = Prompt.yn("#{boldwhite}Section #{
|
|
44
|
+
res = Prompt.yn("#{Color.boldwhite}Section #{Color.yellow(frag)}#{Color.boldwhite} not found, create it", default_response: 'n')
|
|
47
45
|
|
|
48
46
|
if res
|
|
49
47
|
@content.add_section(frag.cap_first, log: true)
|
|
@@ -53,7 +51,7 @@ module Doing
|
|
|
53
51
|
|
|
54
52
|
raise Errors::InvalidSection.new("unknown section #{frag.bold.white}", topic: 'Missing:')
|
|
55
53
|
end
|
|
56
|
-
section
|
|
54
|
+
section&.cap_first
|
|
57
55
|
end
|
|
58
56
|
|
|
59
57
|
##
|
|
@@ -82,7 +80,7 @@ module Doing
|
|
|
82
80
|
meant_view = Prompt.yn(prompt, default_response: 'n')
|
|
83
81
|
|
|
84
82
|
if meant_view
|
|
85
|
-
msg = format('%<y>srun with `%<w>sdoing show %<alt>s%<y>s`', w: boldwhite, y: yellow, alt: alt)
|
|
83
|
+
msg = format('%<y>srun with `%<w>sdoing show %<alt>s%<y>s`', w: Color.boldwhite, y: Color.yellow, alt: alt)
|
|
86
84
|
raise Errors::WrongCommand.new(msg, topic: 'Try again:')
|
|
87
85
|
|
|
88
86
|
end
|
|
@@ -25,14 +25,14 @@ module Doing
|
|
|
25
25
|
opt[:query] = "!#{opt[:query]}" if opt[:query] && opt[:not]
|
|
26
26
|
opt[:multiple] = true
|
|
27
27
|
opt[:show_if_single] = true
|
|
28
|
-
filter_options = %i[after before case date_filter from fuzzy not search section val].each_with_object({})
|
|
28
|
+
filter_options = %i[after before case date_filter from fuzzy not search section val].each_with_object({}) do
|
|
29
29
|
|k, hsh| hsh[k] = opt[k]
|
|
30
|
-
|
|
30
|
+
end
|
|
31
31
|
items = filter_items(Items.new, opt: filter_options)
|
|
32
32
|
|
|
33
|
-
menu_options = %i[search query exact multiple show_if_single menu sort case].each_with_object({})
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
menu_options = %i[search query exact multiple show_if_single menu sort case].each_with_object({}) do |k, hsh|
|
|
34
|
+
hsh[k] = opt[k]
|
|
35
|
+
end
|
|
36
36
|
include_section = (opt[:section].is_a?(Array) && opt[:section][0] =~ /^all$/i) || (opt[:section].is_a?(String) && opt[:section] =~ /^all$/i)
|
|
37
37
|
|
|
38
38
|
selection = Prompt.choose_from_items(items, include_section: include_section, **menu_options)
|
|
@@ -118,7 +118,7 @@ module Doing
|
|
|
118
118
|
tags = type == 'add' ? all_tags(@content) : all_tags(items)
|
|
119
119
|
|
|
120
120
|
add_msg = type == 'add' ? ', include values with tag(value)' : ''
|
|
121
|
-
puts "#{yellow}Separate multiple tags with spaces, hit tab to complete known tags#{add_msg}"
|
|
121
|
+
puts "#{Color.yellow}Separate multiple tags with spaces, hit tab to complete known tags#{add_msg}"
|
|
122
122
|
puts "#{boldgreen}Available tags: #{boldwhite}#{tags.sort.map(&:add_at).join(', ')}" if type == 'remove'
|
|
123
123
|
tag = Prompt.read_line(prompt: "Tags to #{type}", completions: tags)
|
|
124
124
|
|
|
@@ -255,8 +255,8 @@ module Doing
|
|
|
255
255
|
|
|
256
256
|
if opt[:editor]
|
|
257
257
|
sleep 2 # This seems to be necessary between running fzf
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
# and forking the editor, otherwise vim gets all
|
|
259
|
+
# screwy and I can't figure out why
|
|
260
260
|
edit_items(items) # hooked
|
|
261
261
|
|
|
262
262
|
write(@doing_file)
|
|
@@ -365,9 +365,9 @@ module Doing
|
|
|
365
365
|
elapsed = finish_date - date
|
|
366
366
|
|
|
367
367
|
if max_elapsed.positive? && (elapsed > max_elapsed)
|
|
368
|
-
puts boldwhite(title) if title
|
|
368
|
+
puts Color.boldwhite(title) if title
|
|
369
369
|
human = elapsed.time_string(format: :natural)
|
|
370
|
-
res = Prompt.yn(yellow("Did this entry actually take #{human}"), default_response: true)
|
|
370
|
+
res = Prompt.yn(Color.yellow("Did this entry actually take #{human}"), default_response: true)
|
|
371
371
|
unless res
|
|
372
372
|
new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
|
|
373
373
|
raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
|
data/lib/doing/wwid/modify.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Doing
|
|
|
19
19
|
opt ||= {}
|
|
20
20
|
section ||= Doing.setting('current_section')
|
|
21
21
|
@content.add_section(section, log: false)
|
|
22
|
-
opt[:back] ||= opt[:date]
|
|
22
|
+
opt[:back] ||= opt[:date] || Time.now
|
|
23
23
|
opt[:date] ||= Time.now
|
|
24
24
|
note = Note.new
|
|
25
25
|
opt[:timed] ||= false
|
|
@@ -78,8 +78,8 @@ module Doing
|
|
|
78
78
|
if finish_date
|
|
79
79
|
item.tag('done', remove: true)
|
|
80
80
|
item.tag('done', value: finish_date.strftime('%F %R'))
|
|
81
|
-
|
|
82
|
-
item.tag('done', remove: true)
|
|
81
|
+
elsif resume
|
|
82
|
+
item.tag('done', remove: true)
|
|
83
83
|
end
|
|
84
84
|
logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
|
|
85
85
|
item
|
|
@@ -118,7 +118,7 @@ module Doing
|
|
|
118
118
|
note = opt[:note] || Note.new
|
|
119
119
|
|
|
120
120
|
if opt[:editor]
|
|
121
|
-
start = opt[:date]
|
|
121
|
+
start = opt[:date] || Time.now
|
|
122
122
|
to_edit = "#{start.strftime('%F %R')} | #{title}"
|
|
123
123
|
to_edit += "\n#{note.strip_lines.join("\n")}" unless note.empty?
|
|
124
124
|
new_item = fork_editor(to_edit)
|
|
@@ -168,7 +168,8 @@ module Doing
|
|
|
168
168
|
##
|
|
169
169
|
## @see #filter_items
|
|
170
170
|
##
|
|
171
|
-
|
|
171
|
+
# hooked
|
|
172
|
+
def tag_last(opt)
|
|
172
173
|
opt ||= {}
|
|
173
174
|
opt[:count] ||= 1
|
|
174
175
|
opt[:archive] ||= false
|
|
@@ -186,11 +187,11 @@ module Doing
|
|
|
186
187
|
|
|
187
188
|
if opt[:interactive]
|
|
188
189
|
items = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, menu: true,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
header: '',
|
|
191
|
+
prompt: 'Select entries to tag > ',
|
|
192
|
+
multiple: true,
|
|
193
|
+
sort: true,
|
|
194
|
+
show_if_single: true)
|
|
194
195
|
|
|
195
196
|
raise NoResults, 'no items selected' if items.empty?
|
|
196
197
|
|
|
@@ -201,9 +202,9 @@ module Doing
|
|
|
201
202
|
if opt[:tags].empty? && !opt[:autotag]
|
|
202
203
|
completions = opt[:remove] ? all_tags(items) : all_tags(@content)
|
|
203
204
|
if opt[:remove]
|
|
204
|
-
puts "#{yellow}Available tags: #{boldwhite}#{completions.map(&:add_at).join(', ')}"
|
|
205
|
+
puts "#{Color.yellow}Available tags: #{Color.boldwhite}#{completions.map(&:add_at).join(', ')}"
|
|
205
206
|
else
|
|
206
|
-
puts "#{yellow}Use tab to complete known tags"
|
|
207
|
+
puts "#{Color.yellow}Use tab to complete known tags"
|
|
207
208
|
end
|
|
208
209
|
opt[:tags] = Doing::Prompt.read_line(prompt: "Enter tag(s) to #{opt[:remove] ? 'remove' : 'add'}",
|
|
209
210
|
completions: completions,
|
|
@@ -265,12 +266,12 @@ module Doing
|
|
|
265
266
|
elapsed = done_date - item.date
|
|
266
267
|
|
|
267
268
|
if max_elapsed.positive? && (elapsed > max_elapsed) && !opt[:took]
|
|
268
|
-
puts boldwhite(item.title)
|
|
269
|
+
puts Color.boldwhite(item.title)
|
|
269
270
|
human = elapsed.time_string(format: :natural)
|
|
270
|
-
res = Prompt.yn(yellow("Did this actually take #{human}"), default_response: true)
|
|
271
|
+
res = Prompt.yn(Color.yellow("Did this actually take #{human}"), default_response: true)
|
|
271
272
|
unless res
|
|
272
273
|
new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
|
|
273
|
-
raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed
|
|
274
|
+
raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
|
|
274
275
|
|
|
275
276
|
opt[:took] = new_elapsed
|
|
276
277
|
done_date = item.calculate_end_date(opt) if opt[:took]
|
|
@@ -289,7 +290,8 @@ module Doing
|
|
|
289
290
|
end
|
|
290
291
|
old_title = item.title.dup
|
|
291
292
|
force = opt[:value].nil? ? false : true
|
|
292
|
-
item.title.tag!(tag, remove: opt[:remove], rename_to: rename_to, regex: opt[:regex], value: opt[:value],
|
|
293
|
+
item.title.tag!(tag, remove: opt[:remove], rename_to: rename_to, regex: opt[:regex], value: opt[:value],
|
|
294
|
+
force: force)
|
|
293
295
|
if old_title != item.title
|
|
294
296
|
removed << tag
|
|
295
297
|
added << rename_to if rename_to
|
|
@@ -310,7 +312,7 @@ module Doing
|
|
|
310
312
|
|
|
311
313
|
item.note.add(opt[:note]) if opt[:note]
|
|
312
314
|
|
|
313
|
-
if opt[:archive] && opt[:section] != 'Archive' &&
|
|
315
|
+
if opt[:archive] && opt[:section] != 'Archive' && opt[:count].positive?
|
|
314
316
|
item.move_to('Archive', label: true)
|
|
315
317
|
elsif opt[:archive] && opt[:count].zero?
|
|
316
318
|
logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
|
|
@@ -376,7 +378,6 @@ module Doing
|
|
|
376
378
|
Hooks.trigger :post_entry_updated, self, item, old_item
|
|
377
379
|
end
|
|
378
380
|
|
|
379
|
-
|
|
380
381
|
logger.debug('Skipped:', "No active @#{tag} tasks found.") if found_items.zero?
|
|
381
382
|
|
|
382
383
|
if opt[:new_item]
|
|
@@ -398,7 +399,7 @@ module Doing
|
|
|
398
399
|
##
|
|
399
400
|
def delete_items(items, force: false)
|
|
400
401
|
items.slice(0, 5).each { |i| puts i.to_pretty } unless force
|
|
401
|
-
puts softpurple("+ #{items.size - 5} additional #{'item'.to_p(items.size - 5)}") if items.size > 5 && !force
|
|
402
|
+
puts Color.softpurple("+ #{items.size - 5} additional #{'item'.to_p(items.size - 5)}") if items.size > 5 && !force
|
|
402
403
|
|
|
403
404
|
res = force ? true : Prompt.yn("Delete #{items.size} #{'item'.to_p(items.size)}?", default_response: 'y')
|
|
404
405
|
return unless res
|
|
@@ -431,12 +432,13 @@ module Doing
|
|
|
431
432
|
|
|
432
433
|
destination = guess_section(destination)
|
|
433
434
|
|
|
434
|
-
|
|
435
|
-
do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before], after: options[:after], from: options[:from] })
|
|
436
|
-
write(doing_file)
|
|
437
|
-
else
|
|
435
|
+
unless @content.section?(destination) && (@content.section?(section) || archive_all)
|
|
438
436
|
raise InvalidArgument, 'Either source or destination does not exist'
|
|
439
437
|
end
|
|
438
|
+
|
|
439
|
+
do_archive(section, destination,
|
|
440
|
+
{ count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before], after: options[:after], from: options[:from] })
|
|
441
|
+
write(doing_file)
|
|
440
442
|
end
|
|
441
443
|
|
|
442
444
|
##
|
|
@@ -483,43 +485,41 @@ module Doing
|
|
|
483
485
|
end
|
|
484
486
|
end
|
|
485
487
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
next unless tag =~ /\S+:\S+/
|
|
488
|
+
Doing.setting('autotag.transform')&.each do |tag|
|
|
489
|
+
next unless tag =~ /\S+:\S+/
|
|
489
490
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
491
|
+
if tag =~ /::/
|
|
492
|
+
rx, r = tag.split(/::/)
|
|
493
|
+
else
|
|
494
|
+
rx, r = tag.split(/:/)
|
|
495
|
+
end
|
|
495
496
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
497
|
+
flag_rx = %r{/(r+)$}
|
|
498
|
+
if r =~ flag_rx
|
|
499
|
+
flags = r.match(flag_rx)[1].split(//)
|
|
500
|
+
r.sub!(flag_rx, '')
|
|
501
|
+
end
|
|
502
|
+
r.gsub!(/\$/, '\\')
|
|
503
|
+
rx.sub!(/^@?/, '@')
|
|
504
|
+
regex = Regexp.new("(?<= |\\A)#{rx}(?= |\\Z)")
|
|
504
505
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
text.sub!(regex) do
|
|
507
|
+
m = Regexp.last_match
|
|
508
|
+
new_tag = r
|
|
508
509
|
|
|
509
|
-
|
|
510
|
-
|
|
510
|
+
m.to_a.slice(1, m.length - 1).each_with_index do |v, idx|
|
|
511
|
+
next if v.nil?
|
|
511
512
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
end
|
|
513
|
+
new_tag.gsub!("\\#{idx + 1}", v)
|
|
514
|
+
end
|
|
515
|
+
# Replace original tag if /r
|
|
516
|
+
if flags&.include?('r')
|
|
517
|
+
tagged[:replaced].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
|
|
518
|
+
new_tag.split(/ /).map { |t| t.sub(/^@?/, '@') }.join(' ')
|
|
519
|
+
else
|
|
520
|
+
tagged[:transformed].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
|
|
521
|
+
tagged[:transformed] = tagged[:transformed].uniq
|
|
522
|
+
m[0]
|
|
523
523
|
end
|
|
524
524
|
end
|
|
525
525
|
end
|
data/lib/doing/wwid/timers.rb
CHANGED
|
@@ -69,13 +69,11 @@ EOTAIL
|
|
|
69
69
|
pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
|
|
70
70
|
pad = 7 if pad < 7
|
|
71
71
|
output = <<~EOHEADER
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
| #{' ' * (pad - 7)}project | time |
|
|
73
|
+
| #{'-' * (pad - 1)}: | :------- |
|
|
74
74
|
EOHEADER
|
|
75
75
|
sorted_tags_data.reverse.each do |k, v|
|
|
76
|
-
if v.positive?
|
|
77
|
-
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
|
|
78
|
-
end
|
|
76
|
+
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n" if v.positive?
|
|
79
77
|
end
|
|
80
78
|
tail = '[Tag Totals]'
|
|
81
79
|
output + tail
|
|
@@ -173,6 +171,7 @@ EOTAIL
|
|
|
173
171
|
def record_tag_times(item, seconds)
|
|
174
172
|
item_hash = "#{item.date.strftime('%s')}#{item.title}#{item.section}"
|
|
175
173
|
return if @recorded_items.include?(item_hash)
|
|
174
|
+
|
|
176
175
|
item.title.scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
|
|
177
176
|
k = m[0] == 'done' ? 'All' : m[0].downcase
|
|
178
177
|
if @timers.key?(k)
|
data/lib/doing/wwid/wwid.rb
CHANGED
|
File without changes
|
data/lib/doing/wwid/wwidutil.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Doing
|
|
|
32
32
|
##
|
|
33
33
|
def import(paths, opt)
|
|
34
34
|
opt ||= {}
|
|
35
|
-
Plugins.plugins[:import].
|
|
35
|
+
Plugins.plugins[:import].each_value do |options|
|
|
36
36
|
next unless opt[:type] =~ /^(#{options[:trigger].normalize_trigger})$/i
|
|
37
37
|
|
|
38
38
|
if paths.count.positive?
|
|
@@ -54,17 +54,15 @@ module Doing
|
|
|
54
54
|
## alternative config file
|
|
55
55
|
##
|
|
56
56
|
def configure(filename = nil)
|
|
57
|
-
logger.
|
|
57
|
+
logger.measure(:configure) do
|
|
58
|
+
if filename
|
|
59
|
+
Doing.config_with(filename, { ignore_local: true })
|
|
60
|
+
elsif ENV['DOING_CONFIG']
|
|
61
|
+
Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
|
|
62
|
+
end
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
Doing.config_with(filename, { ignore_local: true })
|
|
61
|
-
elsif ENV['DOING_CONFIG']
|
|
62
|
-
Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
|
|
64
|
+
Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
|
|
63
65
|
end
|
|
64
|
-
|
|
65
|
-
logger.benchmark(:configure, :finish)
|
|
66
|
-
|
|
67
|
-
Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
|
|
68
66
|
end
|
|
69
67
|
|
|
70
68
|
##
|
|
@@ -50,7 +50,7 @@ module Doing
|
|
|
50
50
|
new_items = wwid.filter_items(new_items, opt: options)
|
|
51
51
|
|
|
52
52
|
skipped = total - new_items.count
|
|
53
|
-
Doing.logger.debug('Skipped:'
|
|
53
|
+
Doing.logger.debug('Skipped:', %(#{skipped} items that didn't match filter criteria)) if skipped.positive?
|
|
54
54
|
|
|
55
55
|
imported = []
|
|
56
56
|
|
|
@@ -81,7 +81,6 @@ module Doing
|
|
|
81
81
|
}
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
|
|
85
84
|
#-------------------------------------------------------
|
|
86
85
|
## Output a template. Only required if template(s) are
|
|
87
86
|
## included in settings. The method should return a
|
|
@@ -100,10 +99,10 @@ module Doing
|
|
|
100
99
|
##
|
|
101
100
|
def self.template(trigger)
|
|
102
101
|
return unless trigger =~ /^say(it)?$/
|
|
102
|
+
|
|
103
103
|
'On %date, you were %title, recorded in section %section%took'
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
|
|
107
106
|
##
|
|
108
107
|
## Render data received from an output
|
|
109
108
|
## command
|