doing 2.1.24 → 2.1.28
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/.yardoc/checksums +17 -21
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +325 -102
- data/Dockerfile +5 -5
- data/Dockerfile-2.6 +5 -5
- data/Dockerfile-2.7 +5 -4
- data/Dockerfile-3.0 +5 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +3 -3
- data/bin/commands/add_section.rb +15 -0
- data/bin/commands/again.rb +57 -0
- data/bin/commands/archive.rb +55 -0
- data/bin/commands/cancel.rb +60 -0
- data/bin/commands/changes.rb +73 -0
- data/bin/commands/choose.rb +9 -0
- data/bin/commands/colors.rb +21 -0
- data/bin/commands/commands.rb +89 -0
- data/bin/commands/commands_accepting.rb +76 -0
- data/bin/commands/completion.rb +27 -0
- data/bin/commands/config.rb +245 -0
- data/bin/commands/done.rb +235 -0
- data/bin/commands/finish.rb +126 -0
- data/bin/commands/flag.rb +90 -0
- data/bin/commands/grep.rb +108 -0
- data/bin/commands/import.rb +71 -0
- data/bin/commands/install_fzf.rb +17 -0
- data/bin/commands/last.rb +81 -0
- data/bin/commands/meanwhile.rb +76 -0
- data/bin/commands/note.rb +91 -0
- data/bin/commands/now.rb +145 -0
- data/bin/commands/on.rb +65 -0
- data/bin/commands/open.rb +53 -0
- data/bin/commands/plugins.rb +23 -0
- data/bin/commands/recent.rb +77 -0
- data/bin/commands/redo.rb +26 -0
- data/bin/commands/reset.rb +73 -0
- data/bin/commands/rotate.rb +42 -0
- data/bin/commands/sections.rb +11 -0
- data/bin/commands/select.rb +105 -0
- data/bin/commands/show.rb +185 -0
- data/bin/commands/since.rb +63 -0
- data/bin/commands/tag.rb +149 -0
- data/bin/commands/tag_dir.rb +29 -0
- data/bin/commands/tags.rb +66 -0
- data/bin/commands/template.rb +61 -0
- data/bin/commands/today.rb +64 -0
- data/bin/commands/undo.rb +49 -0
- data/bin/commands/view.rb +201 -0
- data/bin/commands/views.rb +11 -0
- data/bin/commands/yesterday.rb +72 -0
- data/bin/doing +241 -3662
- data/docs/doc/Array.html +13 -449
- data/docs/doc/BooleanTermParser/Clause.html +5 -5
- data/docs/doc/BooleanTermParser/Operator.html +4 -4
- data/docs/doc/BooleanTermParser/Query.html +8 -8
- data/docs/doc/BooleanTermParser/QueryParser.html +2 -2
- data/docs/doc/BooleanTermParser/QueryTransformer.html +2 -2
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +65 -59
- data/docs/doc/Doing/Completion.html +2 -2
- data/docs/doc/Doing/Configuration.html +49 -16
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +2 -2
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +2 -2
- data/docs/doc/Doing/Errors/DoingStandardError.html +2 -2
- data/docs/doc/Doing/Errors/EmptyInput.html +2 -2
- data/docs/doc/Doing/Errors/NoResults.html +2 -2
- data/docs/doc/Doing/Errors/PluginException.html +3 -3
- data/docs/doc/Doing/Errors/UserCancelled.html +2 -2
- data/docs/doc/Doing/Errors/WrongCommand.html +2 -2
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/Hooks.html +6 -6
- data/docs/doc/Doing/Item.html +50 -16
- data/docs/doc/Doing/Items.html +10 -10
- data/docs/doc/Doing/LogAdapter.html +24 -24
- data/docs/doc/Doing/Note.html +7 -7
- data/docs/doc/Doing/Pager.html +4 -4
- data/docs/doc/Doing/Plugins.html +7 -7
- data/docs/doc/Doing/Prompt.html +59 -14
- data/docs/doc/Doing/Section.html +6 -6
- data/docs/doc/Doing/TemplateString.html +8 -8
- data/docs/doc/Doing/Types.html +46 -1
- data/docs/doc/Doing/Util/Backup.html +10 -10
- data/docs/doc/Doing/Util.html +15 -15
- data/docs/doc/Doing/WWID.html +73 -61
- data/docs/doc/Doing.html +3 -3
- data/docs/doc/FalseClass.html +235 -0
- data/docs/doc/GLI/Commands/Help.html +3 -3
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +17 -17
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +45 -11
- data/docs/doc/Numeric.html +5 -5
- data/docs/doc/Object.html +203 -0
- data/docs/doc/PhraseParser/Operator.html +4 -4
- data/docs/doc/PhraseParser/PhraseClause.html +5 -5
- data/docs/doc/PhraseParser/Query.html +10 -10
- data/docs/doc/PhraseParser/QueryParser.html +2 -2
- data/docs/doc/PhraseParser/QueryTransformer.html +2 -2
- data/docs/doc/PhraseParser/TermClause.html +5 -5
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +7 -7
- data/docs/doc/String.html +306 -3111
- data/docs/doc/Symbol.html +45 -11
- data/docs/doc/Time.html +6 -6
- data/docs/doc/TrueClass.html +235 -0
- data/docs/doc/_index.html +37 -19
- 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 +240 -576
- data/docs/doc/top-level-namespace.html +2 -2
- data/doing.rdoc +297 -169
- data/example_plugin.rb +2 -2
- data/lib/completion/_doing.zsh +35 -31
- data/lib/completion/doing.bash +30 -19
- data/lib/completion/doing.fish +81 -67
- data/lib/doing/array/array.rb +4 -0
- data/lib/doing/array/nested_hash.rb +17 -0
- data/lib/doing/{array.rb → array/tags.rb} +7 -25
- data/lib/doing/changelog/change.rb +26 -11
- data/lib/doing/changelog/changes.rb +16 -4
- data/lib/doing/{array_chronify.rb → chronify/array.rb} +0 -0
- data/lib/doing/chronify/chronify.rb +5 -0
- data/lib/doing/{numeric_chronify.rb → chronify/numeric.rb} +0 -0
- data/lib/doing/{string_chronify.rb → chronify/string.rb} +0 -0
- data/lib/doing/colors.rb +115 -54
- data/lib/doing/configuration.rb +9 -6
- data/lib/doing/good.rb +72 -0
- data/lib/doing/hash.rb +4 -0
- data/lib/doing/help_monkey_patch.rb +6 -5
- data/lib/doing/hooks.rb +3 -3
- data/lib/doing/item.rb +19 -15
- data/lib/doing/items.rb +2 -2
- data/lib/doing/log_adapter.rb +35 -2
- data/lib/doing/normalize.rb +188 -0
- data/lib/doing/pager.rb +1 -0
- data/lib/doing/plugins/export/dayone_export.rb +1 -1
- data/lib/doing/plugins/export/html_export.rb +1 -1
- data/lib/doing/plugins/export/json_export.rb +1 -1
- data/lib/doing/plugins/export/markdown_export.rb +1 -1
- data/lib/doing/plugins/export/template_export.rb +3 -1
- data/lib/doing/plugins/import/calendar_import.rb +1 -1
- data/lib/doing/plugins/import/doing_import.rb +1 -1
- data/lib/doing/plugins/import/timing_import.rb +1 -1
- data/lib/doing/prompt.rb +9 -3
- data/lib/doing/string/highlight.rb +95 -0
- data/lib/doing/string/query.rb +129 -0
- data/lib/doing/string/string.rb +12 -0
- data/lib/doing/string/tags.rb +164 -0
- data/lib/doing/string/transform.rb +168 -0
- data/lib/doing/string/truncate.rb +75 -0
- data/lib/doing/string/url.rb +82 -0
- data/lib/doing/template_string.rb +2 -24
- data/lib/doing/types.rb +9 -0
- data/lib/doing/util.rb +20 -16
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +91 -51
- data/lib/doing.rb +5 -6
- data/lib/examples/commands/wiki.rb +6 -7
- data/lib/examples/plugins/wiki_export/wiki_export.rb +1 -1
- data/lib/helpers/threaded_tests.rb +69 -79
- data/lib/helpers/threaded_tests_string.rb +50 -0
- data/scripts/deploy.rb +107 -0
- data/scripts/runtests.sh +4 -0
- metadata +65 -8
- data/lib/doing/string.rb +0 -765
- data/lib/doing/symbol.rb +0 -28
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @@grep @@search
|
|
2
|
+
desc 'Search for entries'
|
|
3
|
+
long_desc %(
|
|
4
|
+
Search all sections (or limit to a single section) for entries matching text or regular expression. Normal strings are fuzzy matched.
|
|
5
|
+
|
|
6
|
+
To search with regular expressions, single quote the string and surround with slashes: `doing search '/\bm.*?x\b/'`
|
|
7
|
+
)
|
|
8
|
+
arg_name 'SEARCH_PATTERN'
|
|
9
|
+
command %i[grep search] do |c|
|
|
10
|
+
c.example 'doing grep "doing wiki"', desc: 'Find entries containing "doing wiki" using fuzzy matching'
|
|
11
|
+
c.example 'doing search "\'search command"', desc: 'Find entries containing "search command" using exact matching (search is an alias for grep)'
|
|
12
|
+
c.example 'doing grep "/do.*?wiki.*?@done/"', desc: 'Find entries matching regular expression'
|
|
13
|
+
c.example 'doing search --before 12/21 "doing wiki"', desc: 'Find entries containing "doing wiki" with entry dates before 12/21 of the current year'
|
|
14
|
+
|
|
15
|
+
c.desc 'Section'
|
|
16
|
+
c.arg_name 'NAME'
|
|
17
|
+
c.flag %i[s section], default_value: 'All'
|
|
18
|
+
|
|
19
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
20
|
+
c.arg_name 'FORMAT'
|
|
21
|
+
c.flag %i[o output]
|
|
22
|
+
|
|
23
|
+
c.desc "Output using a template from configuration"
|
|
24
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
25
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
|
26
|
+
|
|
27
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
28
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
29
|
+
c.flag [:template]
|
|
30
|
+
|
|
31
|
+
c.desc 'Show time intervals on @done tasks'
|
|
32
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
33
|
+
|
|
34
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
35
|
+
c.switch [:duration]
|
|
36
|
+
|
|
37
|
+
c.desc 'Show intervals with totals at the end of output'
|
|
38
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
39
|
+
|
|
40
|
+
c.desc 'Sort tags by (name|time)'
|
|
41
|
+
default = @settings['tag_sort'].normalize_tag_sort || :name
|
|
42
|
+
c.arg_name 'KEY'
|
|
43
|
+
c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
|
|
44
|
+
|
|
45
|
+
c.desc 'Only show items with recorded time intervals'
|
|
46
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
|
47
|
+
|
|
48
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
49
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
50
|
+
|
|
51
|
+
c.desc 'Force exact string matching (case sensitive)'
|
|
52
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
53
|
+
|
|
54
|
+
c.desc 'Show items that *don\'t* match search string'
|
|
55
|
+
c.switch [:not], default_value: false, negatable: false
|
|
56
|
+
|
|
57
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
58
|
+
c.arg_name 'TYPE'
|
|
59
|
+
c.flag [:case], must_match: REGEX_CASE,
|
|
60
|
+
default_value: @settings.dig('search', 'case').normalize_case,
|
|
61
|
+
type: CaseSymbol
|
|
62
|
+
|
|
63
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
|
64
|
+
c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
|
|
65
|
+
|
|
66
|
+
c.desc "Edit matching entries with #{Doing::Util.default_editor}"
|
|
67
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
|
68
|
+
|
|
69
|
+
c.desc "Delete matching entries"
|
|
70
|
+
c.switch %i[d delete], negatable: false, default_value: false
|
|
71
|
+
|
|
72
|
+
c.desc 'Display an interactive menu of results to perform further operations'
|
|
73
|
+
c.switch %i[i interactive], default_value: false, negatable: false
|
|
74
|
+
|
|
75
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
76
|
+
c.arg_name 'QUERY'
|
|
77
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
78
|
+
|
|
79
|
+
c.desc 'Combine multiple tags or value queries using AND, OR, or NOT'
|
|
80
|
+
c.arg_name 'BOOLEAN'
|
|
81
|
+
c.flag [:bool], must_match: REGEX_BOOL,
|
|
82
|
+
default_value: :pattern,
|
|
83
|
+
type: BooleanSymbol
|
|
84
|
+
|
|
85
|
+
add_options(:date_filter, c)
|
|
86
|
+
|
|
87
|
+
c.action do |_global_options, options, args|
|
|
88
|
+
options[:fuzzy] = false
|
|
89
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
90
|
+
|
|
91
|
+
template = @settings['templates'][options[:config_template]].deep_merge(@settings)
|
|
92
|
+
tags_color = template.key?('tags_color') ? template['tags_color'] : nil
|
|
93
|
+
|
|
94
|
+
section = @wwid.guess_section(options[:section]) if options[:section]
|
|
95
|
+
|
|
96
|
+
search = args.join(' ')
|
|
97
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
98
|
+
|
|
99
|
+
options[:times] = true if options[:totals]
|
|
100
|
+
options[:sort_tags] = options[:tag_sort]
|
|
101
|
+
options[:highlight] = true
|
|
102
|
+
options[:search] = search
|
|
103
|
+
options[:section] = section
|
|
104
|
+
options[:tags_color] = tags_color
|
|
105
|
+
|
|
106
|
+
Doing::Pager.page @wwid.list_section(options)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @@import
|
|
2
|
+
desc 'Import entries from an external source'
|
|
3
|
+
long_desc "Imports entries from other sources. Available plugins: #{Doing::Plugins.plugin_names(type: :import, separator: ', ')}"
|
|
4
|
+
arg_name 'PATH'
|
|
5
|
+
command :import do |c|
|
|
6
|
+
c.example 'doing import --type timing "~/Desktop/All Activities.json"', desc: 'Import a Timing.app JSON report'
|
|
7
|
+
c.example 'doing import --type doing --tag imported --no-autotag ~/doing_backup.md', desc: 'Import an Doing archive, tag all entries with @imported, skip autotagging'
|
|
8
|
+
c.example 'doing import --type doing --from "10/1 to 10/15" ~/doing_backup.md', desc: 'Import a Doing archive, only importing entries between two dates'
|
|
9
|
+
|
|
10
|
+
c.desc "Import type (#{Doing::Plugins.plugin_names(type: :import)})"
|
|
11
|
+
c.arg_name 'TYPE'
|
|
12
|
+
c.flag :type, default_value: 'doing'
|
|
13
|
+
|
|
14
|
+
c.desc 'Import items that *don\'t* match search/tag/date filters'
|
|
15
|
+
c.switch [:not], default_value: false, negatable: false
|
|
16
|
+
|
|
17
|
+
c.desc 'Only import items with recorded time intervals'
|
|
18
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
|
19
|
+
|
|
20
|
+
c.desc 'Target section'
|
|
21
|
+
c.arg_name 'NAME'
|
|
22
|
+
c.flag %i[s section]
|
|
23
|
+
|
|
24
|
+
c.desc 'Tag all imported entries'
|
|
25
|
+
c.arg_name 'TAGS'
|
|
26
|
+
c.flag %i[t tag]
|
|
27
|
+
|
|
28
|
+
c.desc 'Autotag entries'
|
|
29
|
+
c.switch :autotag, negatable: true, default_value: true
|
|
30
|
+
|
|
31
|
+
c.desc 'Prefix entries with'
|
|
32
|
+
c.arg_name 'PREFIX'
|
|
33
|
+
c.flag :prefix
|
|
34
|
+
|
|
35
|
+
c.desc 'Allow entries that overlap existing times'
|
|
36
|
+
c.switch [:overlap], negatable: true
|
|
37
|
+
|
|
38
|
+
add_options(:search, c)
|
|
39
|
+
add_options(:date_filter, c)
|
|
40
|
+
|
|
41
|
+
c.action do |_global_options, options, args|
|
|
42
|
+
options[:fuzzy] = false
|
|
43
|
+
if options[:section]
|
|
44
|
+
options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if options[:search]
|
|
48
|
+
search = options[:search]
|
|
49
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
50
|
+
options[:search] = search
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if options[:from]
|
|
54
|
+
options[:date_filter] = options[:from]
|
|
55
|
+
|
|
56
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless options[:date_filter][0]
|
|
57
|
+
elsif options[:before] || options[:after]
|
|
58
|
+
options[:date_filter] = [nil, nil]
|
|
59
|
+
options[:date_filter][1] = options[:before] || Time.now + (1 << 64)
|
|
60
|
+
options[:date_filter][0] = options[:after] || Time.now - (1 << 64)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
|
|
64
|
+
options[:no_overlap] = !options[:overlap]
|
|
65
|
+
@wwid.import(args, options)
|
|
66
|
+
@wwid.write(@wwid.doing_file)
|
|
67
|
+
else
|
|
68
|
+
raise InvalidPluginType, "Invalid import type: #{options[:type]}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @@install_fzf
|
|
2
|
+
command :install_fzf do |c|
|
|
3
|
+
c.desc 'Force reinstall'
|
|
4
|
+
c.switch %i[r reinstall], default_value: false
|
|
5
|
+
|
|
6
|
+
c.desc 'Uninstall'
|
|
7
|
+
c.switch %i[u uninstall], default_value: false, negatable: false
|
|
8
|
+
|
|
9
|
+
c.action do |g, o, a|
|
|
10
|
+
if o[:uninstall]
|
|
11
|
+
Doing::Prompt.uninstall_fzf
|
|
12
|
+
else
|
|
13
|
+
Doing.logger.warn('fzf:', 'force reinstall') if o[:reinstall]
|
|
14
|
+
res = Doing::Prompt.install_fzf(force: o[:reinstall])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @@last
|
|
2
|
+
desc 'Show the last entry, optionally edit'
|
|
3
|
+
long_desc 'Shows the last entry. Using --search and --tag filters, you can view/edit the last entry matching a filter,
|
|
4
|
+
allowing `doing last` to target historical entries.'
|
|
5
|
+
command :last do |c|
|
|
6
|
+
c.example 'doing last', desc: 'Show the most recent entry in all sections'
|
|
7
|
+
c.example 'doing last -s Later', desc: 'Show the most recent entry in the Later section'
|
|
8
|
+
c.example 'doing last --tag project1,work --bool AND', desc: 'Show most recent entry tagged @project1 and @work'
|
|
9
|
+
c.example 'doing last --search "side hustle"', desc: 'Show most recent entry containing "side hustle" (fuzzy matching)'
|
|
10
|
+
c.example 'doing last --search "\'side hustle"', desc: 'Show most recent entry containing "side hustle" (exact match)'
|
|
11
|
+
c.example 'doing last --edit', desc: 'Open the most recent entry in an editor for modifications'
|
|
12
|
+
c.example 'doing last --search "\'side hustle" --edit', desc: 'Open most recent entry containing "side hustle" (exact match) in editor'
|
|
13
|
+
|
|
14
|
+
c.desc 'Specify a section'
|
|
15
|
+
c.arg_name 'NAME'
|
|
16
|
+
c.flag %i[s section], default_value: 'All'
|
|
17
|
+
|
|
18
|
+
c.desc "Edit entry with #{Doing::Util.default_editor}"
|
|
19
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
|
20
|
+
|
|
21
|
+
c.desc "Delete the last entry"
|
|
22
|
+
c.switch %i[d delete], negatable: false, default_value: false
|
|
23
|
+
|
|
24
|
+
c.desc "Output using a template from configuration"
|
|
25
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
26
|
+
c.flag [:config_template], type: TemplateName, default_value: 'last'
|
|
27
|
+
|
|
28
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
29
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
30
|
+
c.flag [:template]
|
|
31
|
+
|
|
32
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
|
33
|
+
c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
|
|
34
|
+
|
|
35
|
+
c.desc 'Show elapsed time if entry is not tagged @done'
|
|
36
|
+
c.switch [:duration]
|
|
37
|
+
|
|
38
|
+
add_options(:search, c)
|
|
39
|
+
add_options(:tag_filter, c)
|
|
40
|
+
|
|
41
|
+
c.action do |global_options, options, _args|
|
|
42
|
+
options[:fuzzy] = false
|
|
43
|
+
raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
|
|
44
|
+
|
|
45
|
+
options[:tag] ||= []
|
|
46
|
+
|
|
47
|
+
options[:search] = options[:search].sub(/^'?/, "'") if options[:search] && options[:exact]
|
|
48
|
+
|
|
49
|
+
if options[:editor]
|
|
50
|
+
@wwid.edit_last(section: options[:section],
|
|
51
|
+
options: {
|
|
52
|
+
search: options[:search],
|
|
53
|
+
fuzzy: options[:fuzzy],
|
|
54
|
+
case: options[:case],
|
|
55
|
+
tag: options[:tag],
|
|
56
|
+
tag_bool: options[:bool],
|
|
57
|
+
not: options[:not],
|
|
58
|
+
val: options[:val],
|
|
59
|
+
bool: options[:bool]
|
|
60
|
+
})
|
|
61
|
+
else
|
|
62
|
+
last = @wwid.last(times: true, section: options[:section],
|
|
63
|
+
options: {
|
|
64
|
+
config_template: options[:config_template],
|
|
65
|
+
template: options[:template],
|
|
66
|
+
duration: options[:duration],
|
|
67
|
+
search: options[:search],
|
|
68
|
+
fuzzy: options[:fuzzy],
|
|
69
|
+
case: options[:case],
|
|
70
|
+
hilite: options[:hilite],
|
|
71
|
+
negate: options[:not],
|
|
72
|
+
tag: options[:tag],
|
|
73
|
+
tag_bool: options[:bool],
|
|
74
|
+
delete: options[:delete],
|
|
75
|
+
bool: options[:bool],
|
|
76
|
+
val: options[:val]
|
|
77
|
+
})
|
|
78
|
+
Doing::Pager::page last.strip if last
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @@meanwhile
|
|
2
|
+
desc 'Finish any running @meanwhile tasks and optionally create a new one'
|
|
3
|
+
long_desc 'The @meanwhile tag allows you to have long-running entries that encompass smaller entries.
|
|
4
|
+
This command makes it easy to start and stop these overarching entries. Just run `doing meanwhile Starting work on this
|
|
5
|
+
big project` to start a @meanwhile entry, add other entries as you work on the project, then use `doing meanwhile` by
|
|
6
|
+
itself to mark the entry as @done.'
|
|
7
|
+
arg_name 'ENTRY', optional: true
|
|
8
|
+
command :meanwhile do |c|
|
|
9
|
+
c.example 'doing meanwhile "Long task that will have others after it before it\'s done"', desc: 'Add a new long-running entry, completing any current @meanwhile entry'
|
|
10
|
+
c.example 'doing meanwhile', desc: 'Finish any open @meanwhile entry'
|
|
11
|
+
c.example 'doing meanwhile --archive', desc: 'Finish any open @meanwhile entry and archive it'
|
|
12
|
+
c.example 'doing meanwhile --back 2h "Something I\'ve been working on for a while', desc: 'Add a @meanwhile entry with a start date 2 hours ago'
|
|
13
|
+
|
|
14
|
+
c.desc 'Section'
|
|
15
|
+
c.arg_name 'NAME'
|
|
16
|
+
c.flag %i[s section]
|
|
17
|
+
|
|
18
|
+
c.desc 'Archive previous @meanwhile entry'
|
|
19
|
+
c.switch %i[a archive], negatable: false, default_value: false
|
|
20
|
+
|
|
21
|
+
add_options(:add_entry, c)
|
|
22
|
+
|
|
23
|
+
c.action do |_global_options, options, args|
|
|
24
|
+
@wwid.auto_tag = !options[:noauto]
|
|
25
|
+
|
|
26
|
+
if options[:back]
|
|
27
|
+
date = options[:back]
|
|
28
|
+
|
|
29
|
+
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
|
30
|
+
else
|
|
31
|
+
date = Time.now
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if options[:section]
|
|
35
|
+
section = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
36
|
+
else
|
|
37
|
+
section = @settings['current_section']
|
|
38
|
+
end
|
|
39
|
+
input = ''
|
|
40
|
+
|
|
41
|
+
ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : []
|
|
42
|
+
|
|
43
|
+
if options[:editor]
|
|
44
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
|
45
|
+
input += date.strftime('%F %R | ')
|
|
46
|
+
input += args.join(' ') unless args.empty?
|
|
47
|
+
input += "\n#{options[:note]}" if options[:note]
|
|
48
|
+
input += "\n#{ask_note}" unless ask_note.good?
|
|
49
|
+
|
|
50
|
+
input = @wwid.fork_editor(input).strip
|
|
51
|
+
elsif !args.empty?
|
|
52
|
+
input = args.join(' ')
|
|
53
|
+
elsif $stdin.stat.size.positive?
|
|
54
|
+
input = $stdin.read.strip
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if input.good?
|
|
58
|
+
d, input, note = @wwid.format_input(input)
|
|
59
|
+
unless d.nil?
|
|
60
|
+
Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
|
|
61
|
+
date = d
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
input = nil
|
|
65
|
+
note = []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless options[:editor]
|
|
69
|
+
note.add(options[:note]) if options[:note]
|
|
70
|
+
note.add(ask_note) if ask_note.good?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:archive], note: note })
|
|
74
|
+
@wwid.write(@wwid.doing_file)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @@note
|
|
2
|
+
desc 'Add a note to the last entry'
|
|
3
|
+
long_desc %(
|
|
4
|
+
If -r is provided with no other arguments, the last note is removed.
|
|
5
|
+
If new content is specified through arguments or STDIN, any previous
|
|
6
|
+
note will be replaced with the new one.
|
|
7
|
+
|
|
8
|
+
Use -e to load the last entry in a text editor where you can append a note.
|
|
9
|
+
)
|
|
10
|
+
arg_name 'NOTE_TEXT', optional: true
|
|
11
|
+
command :note do |c|
|
|
12
|
+
c.example 'doing note', desc: 'Open the last entry in $EDITOR to append a note'
|
|
13
|
+
c.example 'doing note "Just a quick annotation"', desc: 'Add a quick note to the last entry'
|
|
14
|
+
c.example 'doing note --tag done "Keeping it real or something"', desc: 'Add a note to the last item tagged @done'
|
|
15
|
+
c.example 'doing note --search "late night" -e', desc: 'Open $EDITOR to add a note to the last item containing "late night" (fuzzy matched)'
|
|
16
|
+
|
|
17
|
+
c.desc 'Section'
|
|
18
|
+
c.arg_name 'NAME'
|
|
19
|
+
c.flag %i[s section], default_value: 'All'
|
|
20
|
+
|
|
21
|
+
c.desc "Edit entry with #{Doing::Util.default_editor}"
|
|
22
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
|
23
|
+
|
|
24
|
+
c.desc "Replace/Remove last entry's note (default append)"
|
|
25
|
+
c.switch %i[r remove], negatable: false, default_value: false
|
|
26
|
+
|
|
27
|
+
c.desc 'Select item for new note from a menu of matching entries'
|
|
28
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
29
|
+
|
|
30
|
+
c.desc 'Prompt for note via multi-line input'
|
|
31
|
+
c.switch %i[ask], negatable: false, default_value: false
|
|
32
|
+
|
|
33
|
+
add_options(:search, c)
|
|
34
|
+
add_options(:tag_filter, c)
|
|
35
|
+
|
|
36
|
+
c.action do |_global_options, options, args|
|
|
37
|
+
options[:fuzzy] = false
|
|
38
|
+
options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first if options[:section]
|
|
39
|
+
options[:tag_bool] = options[:bool]
|
|
40
|
+
|
|
41
|
+
if options[:search]
|
|
42
|
+
search = options[:search]
|
|
43
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
44
|
+
options[:search] = search
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
last_entry = @wwid.last_entry(options)
|
|
48
|
+
old_entry = last_entry.clone
|
|
49
|
+
|
|
50
|
+
raise NoResults, 'No entry matching parameters was found.' unless last_entry
|
|
51
|
+
|
|
52
|
+
last_note = last_entry.note || Doing::Note.new
|
|
53
|
+
new_note = Doing::Note.new
|
|
54
|
+
|
|
55
|
+
new_note.add($stdin.read.strip) if $stdin.stat.size.positive?
|
|
56
|
+
new_note.add(args.join(' ')) unless args.empty?
|
|
57
|
+
|
|
58
|
+
if options[:editor]
|
|
59
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
|
60
|
+
|
|
61
|
+
input = if options[:remove]
|
|
62
|
+
Doing::Note.new
|
|
63
|
+
else
|
|
64
|
+
last_entry.note || Doing::Note.new
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
input.add(new_note)
|
|
68
|
+
|
|
69
|
+
new_note = Doing::Note.new(@wwid.fork_editor(input.strip_lines.join("\n"), message: nil).strip)
|
|
70
|
+
options[:remove] = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if (new_note.empty? && !options[:remove]) || options[:ask]
|
|
74
|
+
$stderr.puts last_note if last_note.good?
|
|
75
|
+
$stderr.puts new_note if new_note.good?
|
|
76
|
+
new_note.add(Doing::Prompt.read_lines(prompt: 'Add a note'))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || new_note.good?
|
|
80
|
+
|
|
81
|
+
if last_note.equal?(new_note)
|
|
82
|
+
Doing.logger.debug('Skipped:', 'No note change')
|
|
83
|
+
else
|
|
84
|
+
last_note.add(new_note, replace: options[:remove])
|
|
85
|
+
Doing.logger.info('Entry updated:', last_entry.title)
|
|
86
|
+
Doing::Hooks.trigger :post_entry_updated, @wwid, last_entry, old_entry
|
|
87
|
+
end
|
|
88
|
+
# new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
|
|
89
|
+
@wwid.write(@wwid.doing_file)
|
|
90
|
+
end
|
|
91
|
+
end
|
data/bin/commands/now.rb
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @@now @@next
|
|
2
|
+
desc 'Add an entry'
|
|
3
|
+
long_desc %(Record what you're starting now, or backdate the start time using natural language.
|
|
4
|
+
|
|
5
|
+
A parenthetical at the end of the entry will be converted to a note.
|
|
6
|
+
|
|
7
|
+
Run without arguments to create a new entry interactively.
|
|
8
|
+
|
|
9
|
+
Run with --editor to create a new entry using #{Doing::Util.default_editor}.)
|
|
10
|
+
arg_name 'ENTRY'
|
|
11
|
+
command %i[now next] do |c|
|
|
12
|
+
c.example 'doing now', desc: 'Create a new entry with interactive prompts'
|
|
13
|
+
c.example 'doing now -e', desc: "Open #{Doing::Util.default_editor} to input an entry and optional note"
|
|
14
|
+
c.example 'doing now working on a new project', desc: 'Add a new entry at the current time'
|
|
15
|
+
c.example 'doing now debugging @project2', desc: 'Add an entry with a tag'
|
|
16
|
+
c.example 'doing now adding an entry (with a note)', desc: 'Parenthetical at end is converted to note'
|
|
17
|
+
c.example 'doing now --back 2pm A thing I started at 2:00 and am still doing...', desc: 'Backdate an entry'
|
|
18
|
+
|
|
19
|
+
c.desc 'Section'
|
|
20
|
+
c.arg_name 'NAME'
|
|
21
|
+
c.flag %i[s section]
|
|
22
|
+
|
|
23
|
+
c.desc %(
|
|
24
|
+
Set a start and optionally end time as a date range ("from 1pm to 2:30pm").
|
|
25
|
+
If an end time is provided, a dated @done tag will be added
|
|
26
|
+
)
|
|
27
|
+
c.arg_name 'TIME_RANGE'
|
|
28
|
+
c.flag [:from], type: DateRangeString
|
|
29
|
+
|
|
30
|
+
c.desc 'Timed entry, marks last entry in section as @done'
|
|
31
|
+
c.switch %i[f finish_last], negatable: false, default_value: false
|
|
32
|
+
|
|
33
|
+
add_options(:add_entry, c)
|
|
34
|
+
|
|
35
|
+
# c.desc "Edit entry with specified app"
|
|
36
|
+
# c.arg_name 'editor_app'
|
|
37
|
+
# # c.flag [:a, :app]
|
|
38
|
+
|
|
39
|
+
c.action do |_global_options, options, args|
|
|
40
|
+
@wwid.auto_tag = !options[:noauto]
|
|
41
|
+
|
|
42
|
+
raise InvalidArgument, '--back and --from cannot be used together' if options[:back] && options[:from]
|
|
43
|
+
|
|
44
|
+
if options[:back]
|
|
45
|
+
date = options[:back]
|
|
46
|
+
elsif options[:from]
|
|
47
|
+
date, finish_date = options[:from]
|
|
48
|
+
options[:done] = finish_date
|
|
49
|
+
else
|
|
50
|
+
date = Time.now
|
|
51
|
+
end
|
|
52
|
+
raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
|
|
53
|
+
|
|
54
|
+
section = if options[:section]
|
|
55
|
+
@wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
56
|
+
else
|
|
57
|
+
@settings['current_section']
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ask_note = if options[:ask] && !options[:editor] && args.count.positive?
|
|
61
|
+
Doing::Prompt.read_lines(prompt: 'Add a note')
|
|
62
|
+
else
|
|
63
|
+
''
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if options[:editor]
|
|
67
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
|
68
|
+
|
|
69
|
+
input = date.strftime('%F %R | ')
|
|
70
|
+
input += args.join(' ') unless args.empty?
|
|
71
|
+
input += " @done(#{options[:done].strftime('%F %R')})" if options[:done]
|
|
72
|
+
input += "\n#{options[:note]}" if options[:note]
|
|
73
|
+
input += "\n#{ask_note}" if ask_note.good?
|
|
74
|
+
input = @wwid.fork_editor(input).strip
|
|
75
|
+
|
|
76
|
+
d, title, note = @wwid.format_input(input)
|
|
77
|
+
raise EmptyInput, 'No content' unless title.good?
|
|
78
|
+
|
|
79
|
+
if ask_note.empty? && options[:ask]
|
|
80
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
|
81
|
+
note.add(ask_note) if ask_note.good?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
date = d.nil? ? date : d
|
|
85
|
+
@wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
|
86
|
+
@wwid.write(@wwid.doing_file)
|
|
87
|
+
elsif args.length.positive?
|
|
88
|
+
d, title, note = @wwid.format_input(args.join(' '))
|
|
89
|
+
date = d.nil? ? date : d
|
|
90
|
+
note.add(options[:note]) if options[:note]
|
|
91
|
+
note.add(ask_note) if ask_note.good?
|
|
92
|
+
entry = @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
|
93
|
+
if options[:done] && entry.should_finish?
|
|
94
|
+
if entry.should_time?
|
|
95
|
+
entry.tag('done', value: options[:done])
|
|
96
|
+
else
|
|
97
|
+
entry.tag('done')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
@wwid.write(@wwid.doing_file)
|
|
101
|
+
elsif $stdin.stat.size.positive?
|
|
102
|
+
input = $stdin.read.strip
|
|
103
|
+
d, title, note = @wwid.format_input(input)
|
|
104
|
+
unless d.nil?
|
|
105
|
+
Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
|
|
106
|
+
date = d
|
|
107
|
+
end
|
|
108
|
+
note.add(options[:note]) if options[:note]
|
|
109
|
+
if ask_note.empty? && options[:ask]
|
|
110
|
+
ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
|
|
111
|
+
note.add(ask_note) if ask_note.good?
|
|
112
|
+
end
|
|
113
|
+
entry = @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
|
114
|
+
if options[:done] && entry.should_finish?
|
|
115
|
+
if entry.should_time?
|
|
116
|
+
entry.tag('done', value: options[:done])
|
|
117
|
+
else
|
|
118
|
+
entry.tag('done')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
@wwid.write(@wwid.doing_file)
|
|
122
|
+
else
|
|
123
|
+
tags = @wwid.all_tags(@wwid.content)
|
|
124
|
+
$stderr.puts Doing::Color.boldgreen("Add a new entry. Tab will autocomplete known tags. Ctrl-c to cancel.")
|
|
125
|
+
title = Doing::Prompt.read_line(prompt: 'Entry content', completions: tags)
|
|
126
|
+
raise EmptyInput, 'You must provide content when creating a new entry' unless title.good?
|
|
127
|
+
|
|
128
|
+
note = Doing::Note.new
|
|
129
|
+
note.add(options[:note]) if options[:note]
|
|
130
|
+
res = Doing::Prompt.yn('Add a note', default_response: false)
|
|
131
|
+
ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
|
|
132
|
+
note.add(ask_note)
|
|
133
|
+
|
|
134
|
+
entry = @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
|
|
135
|
+
if options[:done] && entry.should_finish?
|
|
136
|
+
if entry.should_time?
|
|
137
|
+
entry.tag('done', value: options[:done])
|
|
138
|
+
else
|
|
139
|
+
entry.tag('done')
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
@wwid.write(@wwid.doing_file)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
data/bin/commands/on.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @@on
|
|
2
|
+
desc 'List entries for a date'
|
|
3
|
+
long_desc %(Date argument can be natural language. "thursday" would be interpreted as "last thursday,"
|
|
4
|
+
and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates,
|
|
5
|
+
it will create a range.)
|
|
6
|
+
arg_name 'DATE_STRING'
|
|
7
|
+
command :on do |c|
|
|
8
|
+
c.example 'doing on friday', desc: 'List entries between 12am and 11:59PM last Friday'
|
|
9
|
+
c.example 'doing on 12/21/2020', desc: 'List entries from Dec 21, 2020'
|
|
10
|
+
c.example 'doing on "3d to 1d"', desc: 'List entries added between 3 days ago and 1 day ago'
|
|
11
|
+
|
|
12
|
+
c.desc 'Section'
|
|
13
|
+
c.arg_name 'NAME'
|
|
14
|
+
c.flag %i[s section], default_value: 'All'
|
|
15
|
+
|
|
16
|
+
c.desc 'Show time intervals on @done tasks'
|
|
17
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
18
|
+
|
|
19
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
20
|
+
c.switch [:duration]
|
|
21
|
+
|
|
22
|
+
c.desc 'Show time totals at the end of output'
|
|
23
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
24
|
+
|
|
25
|
+
c.desc 'Sort tags by (name|time)'
|
|
26
|
+
default = @settings['tag_sort'].normalize_tag_sort || :name
|
|
27
|
+
c.arg_name 'KEY'
|
|
28
|
+
c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
|
|
29
|
+
|
|
30
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
31
|
+
c.arg_name 'FORMAT'
|
|
32
|
+
c.flag %i[o output]
|
|
33
|
+
|
|
34
|
+
c.desc "Output using a template from configuration"
|
|
35
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
36
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
|
37
|
+
|
|
38
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
39
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
40
|
+
c.flag [:template]
|
|
41
|
+
|
|
42
|
+
c.action do |_global_options, options, args|
|
|
43
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
44
|
+
|
|
45
|
+
raise MissingArgument, 'Missing date argument' if args.empty?
|
|
46
|
+
|
|
47
|
+
date_string = args.join(' ').strip
|
|
48
|
+
if date_string =~ /^tod(?:ay)?/i
|
|
49
|
+
date_string = 'today to tomorrow 12am'
|
|
50
|
+
end
|
|
51
|
+
start, finish = date_string.split_date_range
|
|
52
|
+
|
|
53
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
|
54
|
+
|
|
55
|
+
message = "date interpreted as #{start}"
|
|
56
|
+
message += " to #{finish}" if finish
|
|
57
|
+
Doing.logger.debug('Interpreter:', message)
|
|
58
|
+
|
|
59
|
+
options[:times] = true if options[:totals]
|
|
60
|
+
options[:sort_tags] = options[:tag_sort]
|
|
61
|
+
|
|
62
|
+
Doing::Pager.page @wwid.list_date([start, finish], options[:section], options[:times], options[:output],
|
|
63
|
+
{ template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
|
64
|
+
end
|
|
65
|
+
end
|