doing 2.1.25 → 2.1.26
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 +4 -4
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +283 -108
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/commands/add_section.rb +13 -0
- data/bin/commands/again.rb +99 -0
- data/bin/commands/archive.rb +96 -0
- data/bin/commands/cancel.rb +102 -0
- data/bin/commands/changes.rb +42 -0
- data/bin/commands/choose.rb +9 -0
- data/bin/commands/colors.rb +19 -0
- data/bin/commands/commands.rb +87 -0
- data/bin/commands/commands_accepting.rb +25 -0
- data/bin/commands/completion.rb +24 -0
- data/bin/commands/config.rb +245 -0
- data/bin/commands/done.rb +249 -0
- data/bin/commands/finish.rb +149 -0
- data/bin/commands/flag.rb +126 -0
- data/bin/commands/grep.rb +124 -0
- data/bin/commands/import.rb +101 -0
- data/bin/commands/install_fzf.rb +17 -0
- data/bin/commands/last.rb +114 -0
- data/bin/commands/meanwhile.rb +86 -0
- data/bin/commands/note.rb +130 -0
- data/bin/commands/now.rb +151 -0
- data/bin/commands/on.rb +66 -0
- data/bin/commands/open.rb +53 -0
- data/bin/commands/plugins.rb +23 -0
- data/bin/commands/recent.rb +78 -0
- data/bin/commands/redo.rb +22 -0
- data/bin/commands/reset.rb +106 -0
- data/bin/commands/rotate.rb +73 -0
- data/bin/commands/sections.rb +11 -0
- data/bin/commands/select.rb +123 -0
- data/bin/commands/show.rb +231 -0
- data/bin/commands/since.rb +64 -0
- data/bin/commands/tag.rb +179 -0
- data/bin/commands/tag_dir.rb +29 -0
- data/bin/commands/tags.rb +93 -0
- data/bin/commands/template.rb +61 -0
- data/bin/commands/today.rb +65 -0
- data/bin/commands/undo.rb +49 -0
- data/bin/commands/view.rb +238 -0
- data/bin/commands/views.rb +11 -0
- data/bin/commands/yesterday.rb +73 -0
- data/bin/doing +39 -3641
- 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 +1 -1
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +2 -1
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +1 -1
- data/docs/doc/Doing/Items.html +1 -1
- data/docs/doc/Doing/LogAdapter.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 +46 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +1 -1
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +1 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +1 -1
- data/docs/doc/Doing.html +2 -2
- data/docs/doc/FalseClass.html +201 -0
- data/docs/doc/GLI/Commands/Help.html +1 -1
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +1 -1
- data/docs/doc/Object.html +203 -0
- 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 +201 -0
- data/docs/doc/_index.html +1 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +374 -366
- data/docs/doc/top-level-namespace.html +1 -1
- data/doing.rdoc +15 -5
- data/lib/completion/_doing.zsh +3 -3
- data/lib/completion/doing.fish +1 -1
- data/lib/doing/changelog/changes.rb +1 -1
- data/lib/doing/configuration.rb +1 -0
- data/lib/doing/pager.rb +1 -0
- data/lib/doing/prompt.rb +8 -0
- data/lib/doing/version.rb +1 -1
- data/lib/examples/commands/wiki.rb +6 -7
- data/lib/helpers/threaded_tests.rb +25 -19
- metadata +45 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @@mark @@flag
|
|
2
|
+
desc 'Mark last entry as flagged'
|
|
3
|
+
command %i[mark flag] do |c|
|
|
4
|
+
c.example 'doing flag', desc: 'Add @flagged to the last entry created'
|
|
5
|
+
c.example 'doing mark', desc: 'mark is an alias for flag'
|
|
6
|
+
c.example 'doing flag --tag project1 --count 2', desc: 'Add @flagged to the last 2 entries tagged @project1'
|
|
7
|
+
c.example 'doing flag --interactive --search "/(develop|cod)ing/"', desc: 'Find entries matching regular expression and create a menu allowing multiple selections, selected items will be @flagged'
|
|
8
|
+
|
|
9
|
+
c.desc 'Section'
|
|
10
|
+
c.arg_name 'SECTION_NAME'
|
|
11
|
+
c.flag %i[s section], default_value: 'All'
|
|
12
|
+
|
|
13
|
+
c.desc 'How many recent entries to tag (0 for all)'
|
|
14
|
+
c.arg_name 'COUNT'
|
|
15
|
+
c.flag %i[c count], default_value: 1, must_match: /^\d+$/, type: Integer
|
|
16
|
+
|
|
17
|
+
c.desc 'Don\'t ask permission to flag all entries when count is 0'
|
|
18
|
+
c.switch %i[force], negatable: false, default_value: false
|
|
19
|
+
|
|
20
|
+
c.desc 'Include current date/time with tag'
|
|
21
|
+
c.switch %i[d date], negatable: false, default_value: false
|
|
22
|
+
|
|
23
|
+
c.desc 'Remove flag'
|
|
24
|
+
c.switch %i[r remove], negatable: false, default_value: false
|
|
25
|
+
|
|
26
|
+
c.desc 'Flag last entry (or entries) not marked @done'
|
|
27
|
+
c.switch %i[u unfinished], negatable: false, default_value: false
|
|
28
|
+
|
|
29
|
+
c.desc 'Flag the last entry containing TAG.
|
|
30
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
|
31
|
+
c.arg_name 'TAG'
|
|
32
|
+
c.flag [:tag], type: TagArray
|
|
33
|
+
|
|
34
|
+
c.desc 'Flag the last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
|
|
35
|
+
c.arg_name 'QUERY'
|
|
36
|
+
c.flag [:search]
|
|
37
|
+
|
|
38
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
39
|
+
c.arg_name 'QUERY'
|
|
40
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
41
|
+
|
|
42
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
43
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
44
|
+
|
|
45
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
46
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
47
|
+
|
|
48
|
+
c.desc 'Flag items that *don\'t* match search/tag/date filters'
|
|
49
|
+
c.switch [:not], default_value: false, negatable: false
|
|
50
|
+
|
|
51
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
52
|
+
c.arg_name 'TYPE'
|
|
53
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
54
|
+
|
|
55
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
|
|
56
|
+
c.arg_name 'BOOLEAN'
|
|
57
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
58
|
+
|
|
59
|
+
c.desc 'Select item(s) to flag from a menu of matching entries'
|
|
60
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
61
|
+
|
|
62
|
+
c.action do |_global_options, options, _args|
|
|
63
|
+
options[:fuzzy] = false
|
|
64
|
+
mark = @settings['marker_tag'] || 'flagged'
|
|
65
|
+
|
|
66
|
+
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
|
67
|
+
|
|
68
|
+
section = 'All'
|
|
69
|
+
|
|
70
|
+
if options[:section]
|
|
71
|
+
section = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if options[:tag].nil?
|
|
75
|
+
search_tags = []
|
|
76
|
+
else
|
|
77
|
+
search_tags = options[:tag]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if options[:interactive]
|
|
81
|
+
count = 0
|
|
82
|
+
options[:force] = true
|
|
83
|
+
else
|
|
84
|
+
count = options[:count].to_i
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
options[:case] = options[:case].normalize_case
|
|
88
|
+
|
|
89
|
+
if options[:search]
|
|
90
|
+
search = options[:search]
|
|
91
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
92
|
+
options[:search] = search
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if count.zero? && !options[:force]
|
|
96
|
+
if options[:search]
|
|
97
|
+
section_q = ' matching your search terms'
|
|
98
|
+
elsif options[:tag]
|
|
99
|
+
section_q = ' matching your tag search'
|
|
100
|
+
elsif section == 'All'
|
|
101
|
+
section_q = ''
|
|
102
|
+
else
|
|
103
|
+
section_q = " in section #{section}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
question = if options[:remove]
|
|
108
|
+
"Are you sure you want to unflag all entries#{section_q}"
|
|
109
|
+
else
|
|
110
|
+
"Are you sure you want to flag all records#{section_q}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
res = Doing::Prompt.yn(question, default_response: false)
|
|
114
|
+
|
|
115
|
+
exit_now! 'Cancelled' unless res
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
options[:count] = count
|
|
119
|
+
options[:section] = section
|
|
120
|
+
options[:tag] = search_tags
|
|
121
|
+
options[:tags] = [mark]
|
|
122
|
+
options[:tag_bool] = options[:bool].normalize_bool
|
|
123
|
+
|
|
124
|
+
@wwid.tag_last(options)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
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 'Search entries older than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
|
20
|
+
c.arg_name 'DATE_STRING'
|
|
21
|
+
c.flag [:before], type: DateBeginString
|
|
22
|
+
|
|
23
|
+
c.desc 'Search entries newer than date. If this is only a time (8am, 1:30pm, 15:00), all dates will be included, but entries will be filtered by time of day'
|
|
24
|
+
c.arg_name 'DATE_STRING'
|
|
25
|
+
c.flag [:after], type: DateEndString
|
|
26
|
+
|
|
27
|
+
c.desc %(
|
|
28
|
+
Date range to show, or a single day to filter date on.
|
|
29
|
+
Date range argument should be quoted. Date specifications can be natural language.
|
|
30
|
+
To specify a range, use "to" or "through": `doing search --from "monday 8am to friday 5pm"`.
|
|
31
|
+
|
|
32
|
+
If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
|
|
33
|
+
by time of day.
|
|
34
|
+
)
|
|
35
|
+
c.arg_name 'DATE_OR_RANGE'
|
|
36
|
+
c.flag [:from], type: DateRangeString
|
|
37
|
+
|
|
38
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
39
|
+
c.arg_name 'FORMAT'
|
|
40
|
+
c.flag %i[o output]
|
|
41
|
+
|
|
42
|
+
c.desc "Output using a template from configuration"
|
|
43
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
44
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
|
45
|
+
|
|
46
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
47
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
48
|
+
c.flag [:template]
|
|
49
|
+
|
|
50
|
+
c.desc 'Show time intervals on @done tasks'
|
|
51
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
52
|
+
|
|
53
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
54
|
+
c.switch [:duration]
|
|
55
|
+
|
|
56
|
+
c.desc 'Show intervals with totals at the end of output'
|
|
57
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
58
|
+
|
|
59
|
+
c.desc 'Sort tags by (name|time)'
|
|
60
|
+
default = 'time'
|
|
61
|
+
default = @settings['tag_sort'] || 'name'
|
|
62
|
+
c.arg_name 'KEY'
|
|
63
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
|
64
|
+
|
|
65
|
+
c.desc 'Only show items with recorded time intervals'
|
|
66
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
|
67
|
+
|
|
68
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
69
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
70
|
+
|
|
71
|
+
c.desc 'Force exact string matching (case sensitive)'
|
|
72
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
73
|
+
|
|
74
|
+
c.desc 'Show items that *don\'t* match search string'
|
|
75
|
+
c.switch [:not], default_value: false, negatable: false
|
|
76
|
+
|
|
77
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
78
|
+
c.arg_name 'TYPE'
|
|
79
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
80
|
+
|
|
81
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
|
82
|
+
c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
|
|
83
|
+
|
|
84
|
+
c.desc "Edit matching entries with #{Doing::Util.default_editor}"
|
|
85
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
|
86
|
+
|
|
87
|
+
c.desc "Delete matching entries"
|
|
88
|
+
c.switch %i[d delete], negatable: false, default_value: false
|
|
89
|
+
|
|
90
|
+
c.desc 'Display an interactive menu of results to perform further operations'
|
|
91
|
+
c.switch %i[i interactive], default_value: false, negatable: false
|
|
92
|
+
|
|
93
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
94
|
+
c.arg_name 'QUERY'
|
|
95
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
96
|
+
|
|
97
|
+
c.desc 'Combine multiple tags or value queries using AND, OR, or NOT'
|
|
98
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
|
|
99
|
+
|
|
100
|
+
c.action do |_global_options, options, args|
|
|
101
|
+
options[:fuzzy] = false
|
|
102
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
103
|
+
|
|
104
|
+
template = @settings['templates'][options[:config_template]].deep_merge(@settings)
|
|
105
|
+
tags_color = template.key?('tags_color') ? template['tags_color'] : nil
|
|
106
|
+
|
|
107
|
+
section = @wwid.guess_section(options[:section]) if options[:section]
|
|
108
|
+
|
|
109
|
+
options[:case] = options[:case].normalize_case
|
|
110
|
+
options[:bool] = options[:bool].normalize_bool
|
|
111
|
+
|
|
112
|
+
search = args.join(' ')
|
|
113
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
114
|
+
|
|
115
|
+
options[:times] = true if options[:totals]
|
|
116
|
+
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
117
|
+
options[:highlight] = true
|
|
118
|
+
options[:search] = search
|
|
119
|
+
options[:section] = section
|
|
120
|
+
options[:tags_color] = tags_color
|
|
121
|
+
|
|
122
|
+
Doing::Pager.page @wwid.list_section(options)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
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 'Only import items matching search. Surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
|
15
|
+
c.arg_name 'QUERY'
|
|
16
|
+
c.flag [:search]
|
|
17
|
+
|
|
18
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
19
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
20
|
+
|
|
21
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
22
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
23
|
+
|
|
24
|
+
c.desc 'Import items that *don\'t* match search/tag/date filters'
|
|
25
|
+
c.switch [:not], default_value: false, negatable: false
|
|
26
|
+
|
|
27
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
28
|
+
c.arg_name 'TYPE'
|
|
29
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
30
|
+
|
|
31
|
+
c.desc 'Only import items with recorded time intervals'
|
|
32
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
|
33
|
+
|
|
34
|
+
c.desc 'Target section'
|
|
35
|
+
c.arg_name 'NAME'
|
|
36
|
+
c.flag %i[s section]
|
|
37
|
+
|
|
38
|
+
c.desc 'Tag all imported entries'
|
|
39
|
+
c.arg_name 'TAGS'
|
|
40
|
+
c.flag %i[t tag]
|
|
41
|
+
|
|
42
|
+
c.desc 'Autotag entries'
|
|
43
|
+
c.switch :autotag, negatable: true, default_value: true
|
|
44
|
+
|
|
45
|
+
c.desc 'Prefix entries with'
|
|
46
|
+
c.arg_name 'PREFIX'
|
|
47
|
+
c.flag :prefix
|
|
48
|
+
|
|
49
|
+
# TODO: Allow time range filtering
|
|
50
|
+
c.desc 'Import entries older than date'
|
|
51
|
+
c.arg_name 'DATE_STRING'
|
|
52
|
+
c.flag [:before], type: DateBeginString
|
|
53
|
+
|
|
54
|
+
c.desc 'Import entries newer than date'
|
|
55
|
+
c.arg_name 'DATE_STRING'
|
|
56
|
+
c.flag [:after], type: DateEndString
|
|
57
|
+
|
|
58
|
+
c.desc %(
|
|
59
|
+
Date range to import. Date range argument should be quoted. Date specifications can be natural language.
|
|
60
|
+
To specify a range, use "to" or "through": `--from "monday to friday"` or `--from 10/1 to 10/31`.
|
|
61
|
+
Has no effect unless the import plugin has implemented date range filtering.
|
|
62
|
+
)
|
|
63
|
+
c.arg_name 'DATE_OR_RANGE'
|
|
64
|
+
c.flag %i[f from], type: DateRangeString
|
|
65
|
+
|
|
66
|
+
c.desc 'Allow entries that overlap existing times'
|
|
67
|
+
c.switch [:overlap], negatable: true
|
|
68
|
+
|
|
69
|
+
c.action do |_global_options, options, args|
|
|
70
|
+
options[:fuzzy] = false
|
|
71
|
+
if options[:section]
|
|
72
|
+
options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if options[:search]
|
|
76
|
+
search = options[:search]
|
|
77
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
78
|
+
options[:search] = search
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if options[:from]
|
|
82
|
+
options[:date_filter] = options[:from]
|
|
83
|
+
|
|
84
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless options[:date_filter][0]
|
|
85
|
+
elsif options[:before] || options[:after]
|
|
86
|
+
options[:date_filter] = [nil, nil]
|
|
87
|
+
options[:date_filter][1] = options[:before] || Time.now + (1 << 64)
|
|
88
|
+
options[:date_filter][0] = options[:after] || Time.now - (1 << 64)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
options[:case] = options[:case].normalize_case
|
|
92
|
+
|
|
93
|
+
if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
|
|
94
|
+
options[:no_overlap] = !options[:overlap]
|
|
95
|
+
@wwid.import(args, options)
|
|
96
|
+
@wwid.write(@wwid.doing_file)
|
|
97
|
+
else
|
|
98
|
+
raise InvalidPluginType, "Invalid import type: #{options[:type]}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
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,114 @@
|
|
|
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 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
|
25
|
+
c.arg_name 'TAG'
|
|
26
|
+
c.flag [:tag], type: TagArray
|
|
27
|
+
|
|
28
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
|
|
29
|
+
c.arg_name 'BOOLEAN'
|
|
30
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
31
|
+
|
|
32
|
+
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
|
33
|
+
c.arg_name 'QUERY'
|
|
34
|
+
c.flag [:search]
|
|
35
|
+
|
|
36
|
+
c.desc "Output using a template from configuration"
|
|
37
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
38
|
+
c.flag [:config_template], type: TemplateName, default_value: 'last'
|
|
39
|
+
|
|
40
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
41
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
42
|
+
c.flag [:template]
|
|
43
|
+
|
|
44
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
|
45
|
+
c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
|
|
46
|
+
|
|
47
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
48
|
+
c.arg_name 'QUERY'
|
|
49
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
50
|
+
|
|
51
|
+
c.desc 'Show elapsed time if entry is not tagged @done'
|
|
52
|
+
c.switch [:duration]
|
|
53
|
+
|
|
54
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
55
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
56
|
+
|
|
57
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
58
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
59
|
+
|
|
60
|
+
c.desc 'Show items that *don\'t* match search string or tag filter'
|
|
61
|
+
c.switch [:not], default_value: false, negatable: false
|
|
62
|
+
|
|
63
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
64
|
+
c.arg_name 'TYPE'
|
|
65
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
66
|
+
|
|
67
|
+
c.action do |global_options, options, _args|
|
|
68
|
+
options[:fuzzy] = false
|
|
69
|
+
raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
|
|
70
|
+
|
|
71
|
+
if options[:tag].nil?
|
|
72
|
+
options[:tag] = []
|
|
73
|
+
else
|
|
74
|
+
options[:tag] = options[:tag]
|
|
75
|
+
options[:bool] = options[:bool].normalize_bool
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
options[:case] = options[:case].normalize_case
|
|
79
|
+
|
|
80
|
+
options[:search] = options[:search].sub(/^'?/, "'") if options[:search] && options[:exact]
|
|
81
|
+
|
|
82
|
+
if options[:editor]
|
|
83
|
+
@wwid.edit_last(section: options[:section],
|
|
84
|
+
options: {
|
|
85
|
+
search: options[:search],
|
|
86
|
+
fuzzy: options[:fuzzy],
|
|
87
|
+
case: options[:case],
|
|
88
|
+
tag: options[:tag],
|
|
89
|
+
tag_bool: options[:bool],
|
|
90
|
+
not: options[:not],
|
|
91
|
+
val: options[:val],
|
|
92
|
+
bool: options[:bool]
|
|
93
|
+
})
|
|
94
|
+
else
|
|
95
|
+
last = @wwid.last(times: true, section: options[:section],
|
|
96
|
+
options: {
|
|
97
|
+
config_template: options[:config_template],
|
|
98
|
+
template: options[:template],
|
|
99
|
+
duration: options[:duration],
|
|
100
|
+
search: options[:search],
|
|
101
|
+
fuzzy: options[:fuzzy],
|
|
102
|
+
case: options[:case],
|
|
103
|
+
hilite: options[:hilite],
|
|
104
|
+
negate: options[:not],
|
|
105
|
+
tag: options[:tag],
|
|
106
|
+
tag_bool: options[:bool],
|
|
107
|
+
delete: options[:delete],
|
|
108
|
+
bool: options[:bool],
|
|
109
|
+
val: options[:val]
|
|
110
|
+
})
|
|
111
|
+
Doing::Pager::page last.strip if last
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
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 "Edit entry with #{Doing::Util.default_editor}"
|
|
19
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
|
20
|
+
|
|
21
|
+
c.desc 'Archive previous @meanwhile entry'
|
|
22
|
+
c.switch %i[a archive], negatable: false, default_value: false
|
|
23
|
+
|
|
24
|
+
c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
|
|
25
|
+
c.arg_name 'DATE_STRING'
|
|
26
|
+
c.flag %i[b back started], type: DateBeginString
|
|
27
|
+
|
|
28
|
+
c.desc 'Note'
|
|
29
|
+
c.arg_name 'TEXT'
|
|
30
|
+
c.flag %i[n note]
|
|
31
|
+
|
|
32
|
+
c.desc 'Prompt for note via multi-line input'
|
|
33
|
+
c.switch %i[ask], negatable: false, default_value: false
|
|
34
|
+
|
|
35
|
+
c.action do |_global_options, options, args|
|
|
36
|
+
if options[:back]
|
|
37
|
+
date = options[:back]
|
|
38
|
+
|
|
39
|
+
raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
|
|
40
|
+
else
|
|
41
|
+
date = Time.now
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if options[:section]
|
|
45
|
+
section = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
46
|
+
else
|
|
47
|
+
section = @settings['current_section']
|
|
48
|
+
end
|
|
49
|
+
input = ''
|
|
50
|
+
|
|
51
|
+
ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : []
|
|
52
|
+
|
|
53
|
+
if options[:editor]
|
|
54
|
+
raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
|
|
55
|
+
input += date.strftime('%F %R | ')
|
|
56
|
+
input += args.join(' ') unless args.empty?
|
|
57
|
+
input += "\n#{options[:note]}" if options[:note]
|
|
58
|
+
input += "\n#{ask_note}" unless ask_note.good?
|
|
59
|
+
|
|
60
|
+
input = @wwid.fork_editor(input).strip
|
|
61
|
+
elsif !args.empty?
|
|
62
|
+
input = args.join(' ')
|
|
63
|
+
elsif $stdin.stat.size.positive?
|
|
64
|
+
input = $stdin.read.strip
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if input.good?
|
|
68
|
+
d, input, note = @wwid.format_input(input)
|
|
69
|
+
unless d.nil?
|
|
70
|
+
Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
|
|
71
|
+
date = d
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
input = nil
|
|
75
|
+
note = []
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
unless options[:editor]
|
|
79
|
+
note.add(options[:note]) if options[:note]
|
|
80
|
+
note.add(ask_note) if ask_note.good?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:archive], note: note })
|
|
84
|
+
@wwid.write(@wwid.doing_file)
|
|
85
|
+
end
|
|
86
|
+
end
|