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,73 @@
|
|
|
1
|
+
# @@rotate
|
|
2
|
+
desc 'Move entries to archive file'
|
|
3
|
+
long_desc 'As your doing file grows, commands can get slow. Given that your historical data (and your archive section)
|
|
4
|
+
probably aren\'t providing any useful insights a year later, use this command to "rotate" old entries out to an archive
|
|
5
|
+
file. You\'ll still have access to all historical data, but it won\'t be slowing down daily operation.'
|
|
6
|
+
command :rotate do |c|
|
|
7
|
+
c.example 'doing rotate', desc: 'Move all entries in doing file to a dated secondary file'
|
|
8
|
+
c.example 'doing rotate --section Archive --keep 10', desc: 'Move entries in the Archive section to a secondary file, keeping the most recent 10 entries'
|
|
9
|
+
c.example 'doing rotate --tag project1,done --bool AND', desc: 'Move entries tagged @project1 and @done to a secondary file'
|
|
10
|
+
|
|
11
|
+
c.desc 'How many items to keep in each section (most recent)'
|
|
12
|
+
c.arg_name 'X'
|
|
13
|
+
c.flag %i[k keep], must_match: /^\d+$/, type: Integer
|
|
14
|
+
|
|
15
|
+
c.desc 'Section to rotate'
|
|
16
|
+
c.arg_name 'SECTION_NAME'
|
|
17
|
+
c.flag %i[s section], default_value: 'All'
|
|
18
|
+
|
|
19
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?). Added for compatibility with other commands'
|
|
20
|
+
c.arg_name 'TAG'
|
|
21
|
+
c.flag [:tag]
|
|
22
|
+
|
|
23
|
+
c.desc 'Tag boolean (AND|OR|NOT). Use PATTERN to parse + and - as booleans'
|
|
24
|
+
c.arg_name 'BOOLEAN'
|
|
25
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
26
|
+
|
|
27
|
+
c.desc 'Search filter'
|
|
28
|
+
c.arg_name 'QUERY'
|
|
29
|
+
c.flag [:search]
|
|
30
|
+
|
|
31
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
32
|
+
c.arg_name 'QUERY'
|
|
33
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
34
|
+
|
|
35
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
36
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
37
|
+
|
|
38
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
39
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
40
|
+
|
|
41
|
+
c.desc 'Rotate items that *don\'t* match search string or tag filter'
|
|
42
|
+
c.switch [:not], default_value: false, negatable: false
|
|
43
|
+
|
|
44
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
45
|
+
c.arg_name 'TYPE'
|
|
46
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
47
|
+
|
|
48
|
+
c.desc 'Rotate entries older than date
|
|
49
|
+
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
|
50
|
+
c.arg_name 'DATE_STRING'
|
|
51
|
+
c.flag [:before]
|
|
52
|
+
|
|
53
|
+
c.action do |_global_options, options, args|
|
|
54
|
+
options[:fuzzy] = false
|
|
55
|
+
if options[:section] && options[:section] !~ /^all$/i
|
|
56
|
+
options[:section] = @wwid.guess_section(options[:section])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
options[:bool] = options[:bool].normalize_bool
|
|
60
|
+
|
|
61
|
+
options[:case] = options[:case].normalize_case
|
|
62
|
+
|
|
63
|
+
search = nil
|
|
64
|
+
|
|
65
|
+
if options[:search]
|
|
66
|
+
search = options[:search]
|
|
67
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
68
|
+
options[:search] = search
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@wwid.rotate(options)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @@sections
|
|
2
|
+
desc 'List sections'
|
|
3
|
+
command :sections do |c|
|
|
4
|
+
c.desc 'List in single column'
|
|
5
|
+
c.switch %i[c column], negatable: false, default_value: false
|
|
6
|
+
|
|
7
|
+
c.action do |_global_options, options, _args|
|
|
8
|
+
joiner = options[:column] ? "\n" : "\t"
|
|
9
|
+
print @wwid.content.section_titles.join(joiner)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# @@select
|
|
2
|
+
desc 'Display an interactive menu to perform operations'
|
|
3
|
+
long_desc 'List all entries and select with typeahead fuzzy matching.
|
|
4
|
+
|
|
5
|
+
Multiple selections are allowed, hit tab to add the highlighted entry to the
|
|
6
|
+
selection, and use ctrl-a to select all visible items. Return processes the
|
|
7
|
+
selected entries.
|
|
8
|
+
|
|
9
|
+
Search in the menu by typing:
|
|
10
|
+
|
|
11
|
+
sbtrkt fuzzy-match Items that match s*b*t*r*k*t
|
|
12
|
+
|
|
13
|
+
\'wild exact-match (quoted) Items that include wild
|
|
14
|
+
|
|
15
|
+
!fire inverse-exact-match Items that do not include fire'
|
|
16
|
+
command :select do |c|
|
|
17
|
+
c.example 'doing select', desc: 'Select from all entries. A menu of available actions will be presented after confirming the selection.'
|
|
18
|
+
c.example 'doing select --editor', desc: 'Select entries from a menu and batch edit them in your default editor'
|
|
19
|
+
c.example 'doing select --after "yesterday 12pm" --tag project1', desc: 'Display a menu of entries created after noon yesterday, add @project1 to selected entries'
|
|
20
|
+
c.desc 'Select from a specific section'
|
|
21
|
+
c.arg_name 'SECTION'
|
|
22
|
+
c.flag %i[s section]
|
|
23
|
+
|
|
24
|
+
c.desc 'Tag selected entries'
|
|
25
|
+
c.arg_name 'TAG'
|
|
26
|
+
c.flag %i[t tag]
|
|
27
|
+
|
|
28
|
+
c.desc 'Reverse -c, -f, --flag, and -t (remove instead of adding)'
|
|
29
|
+
c.switch %i[r remove], negatable: false
|
|
30
|
+
|
|
31
|
+
# c.desc 'Add @done to selected item(s), using start time of next item as the finish time'
|
|
32
|
+
# c.switch %i[a auto], negatable: false, default_value: false
|
|
33
|
+
|
|
34
|
+
c.desc 'Archive selected items'
|
|
35
|
+
c.switch %i[a archive], negatable: false, default_value: false
|
|
36
|
+
|
|
37
|
+
c.desc 'Move selected items to section'
|
|
38
|
+
c.arg_name 'SECTION'
|
|
39
|
+
c.flag %i[m move]
|
|
40
|
+
|
|
41
|
+
c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote, e.g. `--query "\'search"'
|
|
42
|
+
c.arg_name 'QUERY'
|
|
43
|
+
c.flag %i[q query]
|
|
44
|
+
|
|
45
|
+
c.desc 'Select from entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
|
|
46
|
+
c.arg_name 'QUERY'
|
|
47
|
+
c.flag [:search]
|
|
48
|
+
|
|
49
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
50
|
+
c.arg_name 'QUERY'
|
|
51
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
52
|
+
|
|
53
|
+
c.desc 'Select from 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'
|
|
54
|
+
c.arg_name 'DATE_STRING'
|
|
55
|
+
c.flag [:before], type: DateBeginString
|
|
56
|
+
|
|
57
|
+
c.desc 'Select from 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'
|
|
58
|
+
c.arg_name 'DATE_STRING'
|
|
59
|
+
c.flag [:after], type: DateEndString
|
|
60
|
+
|
|
61
|
+
c.desc %(
|
|
62
|
+
Date range to show, or a single day to filter date on.
|
|
63
|
+
Date range argument should be quoted. Date specifications can be natural language.
|
|
64
|
+
To specify a range, use "to" or "through": `doing select --from "monday 8am to friday 5pm"`.
|
|
65
|
+
|
|
66
|
+
If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
|
|
67
|
+
by time of day.
|
|
68
|
+
)
|
|
69
|
+
c.arg_name 'DATE_OR_RANGE'
|
|
70
|
+
c.flag [:from], type: DateRangeString
|
|
71
|
+
|
|
72
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
73
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
74
|
+
|
|
75
|
+
c.desc 'Select items that *don\'t* match search/tag filters'
|
|
76
|
+
c.switch [:not], default_value: false, negatable: false
|
|
77
|
+
|
|
78
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
79
|
+
c.arg_name 'TYPE'
|
|
80
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
81
|
+
|
|
82
|
+
c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches'
|
|
83
|
+
c.switch %i[menu], negatable: true, default_value: true
|
|
84
|
+
|
|
85
|
+
c.desc 'Cancel selected items (add @done without timestamp)'
|
|
86
|
+
c.switch %i[c cancel], negatable: false, default_value: false
|
|
87
|
+
|
|
88
|
+
c.desc 'Delete selected items'
|
|
89
|
+
c.switch %i[d delete], negatable: false, default_value: false
|
|
90
|
+
|
|
91
|
+
c.desc 'Edit selected item(s)'
|
|
92
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
|
93
|
+
|
|
94
|
+
c.desc 'Add @done with current time to selected item(s)'
|
|
95
|
+
c.switch %i[f finish], negatable: false, default_value: false
|
|
96
|
+
|
|
97
|
+
c.desc 'Add flag to selected item(s)'
|
|
98
|
+
c.switch %i[flag], negatable: false, default_value: false
|
|
99
|
+
|
|
100
|
+
c.desc 'Perform action without confirmation'
|
|
101
|
+
c.switch %i[force], negatable: false, default_value: false
|
|
102
|
+
|
|
103
|
+
c.desc 'Save selected entries to file using --output format'
|
|
104
|
+
c.arg_name 'FILE'
|
|
105
|
+
c.flag %i[save_to]
|
|
106
|
+
|
|
107
|
+
c.desc "Output entries to format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
108
|
+
c.arg_name 'FORMAT'
|
|
109
|
+
c.flag %i[o output]
|
|
110
|
+
|
|
111
|
+
c.desc "Copy selection as a new entry with current time and no @done tag. Only works with single selections. Can be combined with --editor."
|
|
112
|
+
c.switch %i[again resume], negatable: false, default_value: false
|
|
113
|
+
|
|
114
|
+
c.action do |_global_options, options, args|
|
|
115
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
116
|
+
|
|
117
|
+
raise InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
|
|
118
|
+
|
|
119
|
+
options[:case] = options[:case].normalize_case
|
|
120
|
+
|
|
121
|
+
@wwid.interactive(options) # hooked
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# @@show
|
|
2
|
+
desc 'List all entries'
|
|
3
|
+
long_desc %(
|
|
4
|
+
The argument can be a section name, @tag(s) or both.
|
|
5
|
+
"pick" or "choose" as an argument will offer a section menu. Run with `--menu` to get a menu of available tags.
|
|
6
|
+
|
|
7
|
+
Show tags by passing @tagname arguments. Multiple tags can be combined, and you can specify the boolean used to
|
|
8
|
+
combine them with `--bool (AND|OR|NOT)`. You can also use @+tagname to require a tag to match, or @-tagname to ignore
|
|
9
|
+
entries containing tagname. +/- operators require `--bool PATTERN` (which is the default).
|
|
10
|
+
)
|
|
11
|
+
arg_name '[SECTION|@TAGS]'
|
|
12
|
+
command :show do |c|
|
|
13
|
+
c.example 'doing show Currently', desc: 'Show entries in the Currently section'
|
|
14
|
+
c.example 'doing show @project1', desc: 'Show entries tagged @project1'
|
|
15
|
+
c.example 'doing show Later @doing', desc: 'Show entries from the Later section tagged @doing'
|
|
16
|
+
c.example 'doing show @oracle @writing --bool and', desc: 'Show entries tagged both @oracle and @writing'
|
|
17
|
+
c.example 'doing show Currently @devo --bool not', desc: 'Show entries in Currently NOT tagged @devo'
|
|
18
|
+
c.example 'doing show Ideas @doing --from "mon to fri"', desc: 'Show entries tagged @doing from the Ideas section added between monday and friday of the current week.'
|
|
19
|
+
c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
|
|
20
|
+
|
|
21
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Use `--tag pick` for a menu of available tags. Wildcards allowed (*, ?). Added for compatibility with other commands'
|
|
22
|
+
c.arg_name 'TAG'
|
|
23
|
+
c.flag [:tag], type: TagArray
|
|
24
|
+
|
|
25
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
26
|
+
c.arg_name 'QUERY'
|
|
27
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
28
|
+
|
|
29
|
+
c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans'
|
|
30
|
+
c.arg_name 'BOOLEAN'
|
|
31
|
+
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
32
|
+
|
|
33
|
+
c.desc 'Max count to show'
|
|
34
|
+
c.arg_name 'MAX'
|
|
35
|
+
c.flag %i[c count], default_value: 0, must_match: /^\d+$/, type: Integer
|
|
36
|
+
|
|
37
|
+
c.desc 'Age (oldest|newest)'
|
|
38
|
+
c.arg_name 'AGE'
|
|
39
|
+
c.flag %i[a age], default_value: 'newest'
|
|
40
|
+
|
|
41
|
+
c.desc 'Show 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'
|
|
42
|
+
c.arg_name 'DATE_STRING'
|
|
43
|
+
c.flag [:before], type: DateBeginString
|
|
44
|
+
|
|
45
|
+
c.desc 'Show 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'
|
|
46
|
+
c.arg_name 'DATE_STRING'
|
|
47
|
+
c.flag [:after], type: DateEndString
|
|
48
|
+
|
|
49
|
+
c.desc %(
|
|
50
|
+
Date range to show, or a single day to filter date on.
|
|
51
|
+
Date range argument should be quoted. Date specifications can be natural language.
|
|
52
|
+
To specify a range, use "to" or "through": `doing show --from "monday 8am to friday 5pm"`.
|
|
53
|
+
|
|
54
|
+
If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
|
|
55
|
+
by time of day.
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
c.arg_name 'DATE_OR_RANGE'
|
|
59
|
+
c.flag [:from], type: DateRangeString
|
|
60
|
+
|
|
61
|
+
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
|
62
|
+
c.arg_name 'QUERY'
|
|
63
|
+
c.flag [:search]
|
|
64
|
+
|
|
65
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
|
66
|
+
c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
|
|
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 search 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/tag/date filters'
|
|
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 'Sort order (asc/desc)'
|
|
82
|
+
c.arg_name 'ORDER'
|
|
83
|
+
c.flag %i[s sort], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
|
84
|
+
|
|
85
|
+
c.desc 'Show time intervals on @done tasks'
|
|
86
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
87
|
+
|
|
88
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
89
|
+
c.switch [:duration]
|
|
90
|
+
|
|
91
|
+
c.desc 'Show intervals with totals at the end of output'
|
|
92
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
93
|
+
|
|
94
|
+
c.desc 'Sort tags by (name|time)'
|
|
95
|
+
default = 'time'
|
|
96
|
+
default = @settings['tag_sort'] || 'name'
|
|
97
|
+
c.arg_name 'KEY'
|
|
98
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)/i, default_value: default
|
|
99
|
+
|
|
100
|
+
c.desc 'Tag sort direction (asc|desc)'
|
|
101
|
+
c.arg_name 'DIRECTION'
|
|
102
|
+
c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
|
103
|
+
|
|
104
|
+
c.desc 'Only show items with recorded time intervals'
|
|
105
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
|
106
|
+
|
|
107
|
+
c.desc "Output using a template from configuration"
|
|
108
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
109
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
|
110
|
+
|
|
111
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
112
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
113
|
+
c.flag [:template]
|
|
114
|
+
|
|
115
|
+
c.desc 'Select section or tag to display from a menu'
|
|
116
|
+
c.switch %i[m menu], negatable: false, default_value: false
|
|
117
|
+
|
|
118
|
+
c.desc 'Select from a menu of matching entries to perform additional operations'
|
|
119
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
120
|
+
|
|
121
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
122
|
+
c.arg_name 'FORMAT'
|
|
123
|
+
c.flag %i[o output]
|
|
124
|
+
c.action do |global_options, options, args|
|
|
125
|
+
options[:fuzzy] = false
|
|
126
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
127
|
+
|
|
128
|
+
tag_filter = false
|
|
129
|
+
tags = []
|
|
130
|
+
|
|
131
|
+
if args.length.positive?
|
|
132
|
+
case args[0]
|
|
133
|
+
when /^all$/i
|
|
134
|
+
section = 'All'
|
|
135
|
+
args.shift
|
|
136
|
+
when /^(choose|pick)$/i
|
|
137
|
+
section = @wwid.choose_section(include_all: true)
|
|
138
|
+
|
|
139
|
+
args.shift
|
|
140
|
+
when /^[@+-]/
|
|
141
|
+
section = 'All'
|
|
142
|
+
else
|
|
143
|
+
begin
|
|
144
|
+
section = @wwid.guess_section(args[0])
|
|
145
|
+
rescue WrongCommand => exception
|
|
146
|
+
cmd = commands[:view]
|
|
147
|
+
action = cmd.send(:get_action, nil)
|
|
148
|
+
return action.call(global_options, options, args)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
raise InvalidSection, "No such section: #{args[0]}" unless section
|
|
152
|
+
|
|
153
|
+
args.shift
|
|
154
|
+
end
|
|
155
|
+
if args.length.positive?
|
|
156
|
+
args.each do |arg|
|
|
157
|
+
arg.split(/,/).each do |tag|
|
|
158
|
+
tags.push(tag.strip.sub(/^@/, ''))
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
section = options[:menu] ? @wwid.choose_section(include_all: true) : @settings['current_section']
|
|
164
|
+
section ||= 'All'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
tags.concat(options[:tag]) if options[:tag]
|
|
168
|
+
|
|
169
|
+
options[:times] = true if options[:totals]
|
|
170
|
+
|
|
171
|
+
template = @settings['templates'][options[:config_template]].deep_merge({
|
|
172
|
+
'wrap_width' => @settings['wrap_width'] || 0,
|
|
173
|
+
'date_format' => @settings['default_date_format'],
|
|
174
|
+
'order' => @settings['order'] || 'asc',
|
|
175
|
+
'tags_color' => @settings['tags_color']
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
options[:case] = options[:case].normalize_case
|
|
179
|
+
|
|
180
|
+
if options[:search]
|
|
181
|
+
search = options[:search]
|
|
182
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
183
|
+
options[:search] = search
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
options[:section] = section
|
|
187
|
+
|
|
188
|
+
if tags.good?
|
|
189
|
+
tag_filter = {
|
|
190
|
+
'tags' => tags,
|
|
191
|
+
'bool' => options[:bool].normalize_bool
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
options[:tag_filter] = tag_filter
|
|
196
|
+
options[:tag] = nil
|
|
197
|
+
|
|
198
|
+
items = @wwid.filter_items([], opt: options)
|
|
199
|
+
|
|
200
|
+
if options[:menu]
|
|
201
|
+
tag = @wwid.choose_tag(section, items: items, include_all: true)
|
|
202
|
+
raise UserCancelled unless tag
|
|
203
|
+
|
|
204
|
+
# options[:bool] = :and unless tags.empty?
|
|
205
|
+
|
|
206
|
+
tags = tag.split(/ +/).map { |t| t.strip.sub(/^@?/, '') } if tag =~ /^@/
|
|
207
|
+
if tags.good?
|
|
208
|
+
tag_filter = {
|
|
209
|
+
'tags' => tags,
|
|
210
|
+
'bool' => options[:bool].normalize_bool
|
|
211
|
+
}
|
|
212
|
+
options[:tag_filter] = tag_filter
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
options[:age] ||= :newest
|
|
217
|
+
|
|
218
|
+
opt = options.clone
|
|
219
|
+
opt[:age] = options[:age].normalize_age(:newest) if options[:age]
|
|
220
|
+
opt[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
221
|
+
opt[:count] = options[:count].to_i
|
|
222
|
+
opt[:highlight] = true
|
|
223
|
+
opt[:hilite] = options[:hilite]
|
|
224
|
+
opt[:order] = options[:sort].normalize_order
|
|
225
|
+
opt[:tag] = nil
|
|
226
|
+
opt[:tag_order] = options[:tag_order].normalize_order
|
|
227
|
+
opt[:tags_color] = template['tags_color']
|
|
228
|
+
|
|
229
|
+
Doing::Pager.page @wwid.list_section(opt, items: items)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @since
|
|
2
|
+
desc 'List entries since a date'
|
|
3
|
+
long_desc %(Date argument can be natural language and are always interpreted as being in the past. "thursday" would be interpreted as "last thursday,"
|
|
4
|
+
and "2d" would be interpreted as "two days ago.")
|
|
5
|
+
arg_name 'DATE_STRING'
|
|
6
|
+
command :since do |c|
|
|
7
|
+
c.example 'doing since 7/30', desc: 'List all entries created since 12am on 7/30 of the current year'
|
|
8
|
+
c.example 'doing since "monday 3pm" --output json', desc: 'Show entries since 3pm on Monday of the current week, output in JSON format'
|
|
9
|
+
|
|
10
|
+
c.desc 'Section'
|
|
11
|
+
c.arg_name 'NAME'
|
|
12
|
+
c.flag %i[s section], default_value: 'All'
|
|
13
|
+
|
|
14
|
+
c.desc 'Show time intervals on @done tasks'
|
|
15
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
16
|
+
|
|
17
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
18
|
+
c.switch [:duration]
|
|
19
|
+
|
|
20
|
+
c.desc 'Show time totals at the end of output'
|
|
21
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
22
|
+
|
|
23
|
+
c.desc 'Sort tags by (name|time)'
|
|
24
|
+
default = 'time'
|
|
25
|
+
default = @settings['tag_sort'] || 'name'
|
|
26
|
+
c.arg_name 'KEY'
|
|
27
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
|
28
|
+
|
|
29
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
30
|
+
c.arg_name 'FORMAT'
|
|
31
|
+
c.flag %i[o output]
|
|
32
|
+
|
|
33
|
+
c.desc "Output using a template from configuration"
|
|
34
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
35
|
+
c.flag [:config_template], type: TemplateName, default_value: 'default'
|
|
36
|
+
|
|
37
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
38
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
39
|
+
c.flag [:template]
|
|
40
|
+
|
|
41
|
+
c.action do |_global_options, options, args|
|
|
42
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
43
|
+
|
|
44
|
+
raise MissingArgument, 'Missing date argument' if args.empty?
|
|
45
|
+
|
|
46
|
+
date_string = args.join(' ')
|
|
47
|
+
|
|
48
|
+
date_string.sub!(/(day) (\d)/, '\1 at \2')
|
|
49
|
+
date_string.sub!(/(\d+)d( ago)?/, '\1 days ago')
|
|
50
|
+
|
|
51
|
+
start = date_string.chronify(guess: :begin)
|
|
52
|
+
finish = Time.now
|
|
53
|
+
|
|
54
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
|
55
|
+
|
|
56
|
+
Doing.logger.debug('Interpreter:', "date interpreted as #{start} through the current time")
|
|
57
|
+
|
|
58
|
+
options[:times] = true if options[:totals]
|
|
59
|
+
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
60
|
+
|
|
61
|
+
Doing::Pager.page @wwid.list_date([start, finish], options[:section], options[:times], options[:output],
|
|
62
|
+
{ template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
|
63
|
+
end
|
|
64
|
+
end
|
data/bin/commands/tag.rb
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @@tag
|
|
2
|
+
desc 'Add tag(s) to last entry'
|
|
3
|
+
long_desc 'Add (or remove) tags from the last entry, or from multiple entries
|
|
4
|
+
(with `--count`), entries matching a search (with `--search`), or entries
|
|
5
|
+
containing another tag (with `--tag`).
|
|
6
|
+
|
|
7
|
+
When removing tags with `-r`, wildcards are allowed (`*` to match
|
|
8
|
+
multiple characters, `?` to match a single character). With `--regex`,
|
|
9
|
+
regular expressions will be interpreted instead of wildcards.
|
|
10
|
+
|
|
11
|
+
For all tag removals the match is case insensitive by default, but if
|
|
12
|
+
the tag search string contains any uppercase letters, the match will
|
|
13
|
+
become case sensitive automatically.
|
|
14
|
+
|
|
15
|
+
Tag name arguments do not need to be prefixed with @.'
|
|
16
|
+
arg_name 'TAG', :multiple
|
|
17
|
+
command :tag do |c|
|
|
18
|
+
c.example 'doing tag mytag', desc: 'Add @mytag to the last entry created'
|
|
19
|
+
c.example 'doing tag --remove mytag', desc: 'Remove @mytag from the last entry created'
|
|
20
|
+
c.example 'doing tag --rename "other*" --count 10 newtag', desc: 'Rename tags beginning with "other" (wildcard) to @newtag on the last 10 entries'
|
|
21
|
+
c.example 'doing tag --search "developing" coding', desc: 'Add @coding to the last entry containing string "developing" (fuzzy matching)'
|
|
22
|
+
c.example 'doing tag --interactive --tag project1 coding', desc: 'Create an interactive menu from entries tagged @project1, selection(s) will be tagged with @coding'
|
|
23
|
+
|
|
24
|
+
c.desc 'Section'
|
|
25
|
+
c.arg_name 'SECTION_NAME'
|
|
26
|
+
c.flag %i[s section], default_value: 'All'
|
|
27
|
+
|
|
28
|
+
c.desc 'How many recent entries to tag (0 for all)'
|
|
29
|
+
c.arg_name 'COUNT'
|
|
30
|
+
c.flag %i[c count], default_value: 1, must_match: /^\d+$/, type: Integer
|
|
31
|
+
|
|
32
|
+
c.desc 'Replace existing tag with tag argument, wildcards (*,?) allowed, or use with --regex'
|
|
33
|
+
c.arg_name 'ORIG_TAG'
|
|
34
|
+
c.flag %i[rename]
|
|
35
|
+
|
|
36
|
+
c.desc 'Include a value, e.g. @tag(value)'
|
|
37
|
+
c.arg_name 'VALUE'
|
|
38
|
+
c.flag %i[v value]
|
|
39
|
+
|
|
40
|
+
c.desc 'Don\'t ask permission to tag all entries when count is 0'
|
|
41
|
+
c.switch %i[force], negatable: false, default_value: false
|
|
42
|
+
|
|
43
|
+
c.desc 'Include current date/time with tag'
|
|
44
|
+
c.switch %i[d date], negatable: false, default_value: false
|
|
45
|
+
|
|
46
|
+
c.desc 'Remove given tag(s)'
|
|
47
|
+
c.switch %i[r remove], negatable: false, default_value: false
|
|
48
|
+
|
|
49
|
+
c.desc 'Interpret tag string as regular expression (with --remove)'
|
|
50
|
+
c.switch %i[regex], negatable: false, default_value: false
|
|
51
|
+
|
|
52
|
+
c.desc 'Tag last entry (or entries) not marked @done'
|
|
53
|
+
c.switch %i[u unfinished], negatable: false, default_value: false
|
|
54
|
+
|
|
55
|
+
c.desc 'Autotag entries based on autotag configuration in ~/.config/doing/config.yml'
|
|
56
|
+
c.switch %i[a autotag], negatable: false, default_value: false
|
|
57
|
+
|
|
58
|
+
c.desc 'Tag the last X entries containing TAG.
|
|
59
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool. Wildcards allowed (*, ?).'
|
|
60
|
+
c.arg_name 'TAG'
|
|
61
|
+
c.flag [:tag], type: TagArray
|
|
62
|
+
|
|
63
|
+
c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
|
|
64
|
+
c.arg_name 'QUERY'
|
|
65
|
+
c.flag [:search]
|
|
66
|
+
|
|
67
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
68
|
+
c.arg_name 'QUERY'
|
|
69
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
70
|
+
|
|
71
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
72
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
73
|
+
|
|
74
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
75
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
76
|
+
|
|
77
|
+
c.desc 'Tag items that *don\'t* match search/tag filters'
|
|
78
|
+
c.switch [:not], default_value: false, negatable: false
|
|
79
|
+
|
|
80
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
81
|
+
c.arg_name 'TYPE'
|
|
82
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
83
|
+
|
|
84
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters. Use PATTERN to parse + and - as booleans'
|
|
85
|
+
c.arg_name 'BOOLEAN'
|
|
86
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
87
|
+
|
|
88
|
+
c.desc 'Select item(s) to tag from a menu of matching entries'
|
|
89
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
90
|
+
|
|
91
|
+
c.action do |_global_options, options, args|
|
|
92
|
+
options[:fuzzy] = false
|
|
93
|
+
# raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
|
|
94
|
+
|
|
95
|
+
raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
|
|
96
|
+
|
|
97
|
+
section = 'All'
|
|
98
|
+
|
|
99
|
+
if options[:section]
|
|
100
|
+
section = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if options[:tag].nil?
|
|
105
|
+
search_tags = []
|
|
106
|
+
else
|
|
107
|
+
search_tags = options[:tag]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if options[:autotag]
|
|
111
|
+
tags = []
|
|
112
|
+
else
|
|
113
|
+
if args.empty?
|
|
114
|
+
tags = []
|
|
115
|
+
else
|
|
116
|
+
tags = if args.join('') =~ /,/
|
|
117
|
+
args.join('').split(/ *, */)
|
|
118
|
+
else
|
|
119
|
+
args.join(' ').split(' ') # in case tags are quoted as one arg
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
tags.map! { |tag| tag.sub(/^@/, '').strip }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if options[:interactive]
|
|
127
|
+
count = 0
|
|
128
|
+
options[:force] = true
|
|
129
|
+
else
|
|
130
|
+
count = options[:count].to_i
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
options[:case] ||= :smart
|
|
134
|
+
options[:case] = options[:case].normalize_case
|
|
135
|
+
|
|
136
|
+
if options[:search]
|
|
137
|
+
search = options[:search]
|
|
138
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
139
|
+
options[:search] = search
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
options[:count] = count
|
|
143
|
+
options[:section] = section
|
|
144
|
+
options[:tag] = search_tags
|
|
145
|
+
options[:tags] = tags
|
|
146
|
+
options[:tag_bool] = options[:bool].normalize_bool
|
|
147
|
+
|
|
148
|
+
if count.zero? && !options[:force]
|
|
149
|
+
matches = @wwid.filter_items([], opt: options).count
|
|
150
|
+
|
|
151
|
+
if matches > 5
|
|
152
|
+
if options[:search]
|
|
153
|
+
section_q = ' matching your search terms'
|
|
154
|
+
elsif options[:tag]
|
|
155
|
+
section_q = ' matching your tag search'
|
|
156
|
+
elsif section == 'All'
|
|
157
|
+
section_q = ''
|
|
158
|
+
else
|
|
159
|
+
section_q = " in section #{section}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
question = if options[:autotag]
|
|
164
|
+
"Are you sure you want to autotag #{matches} records#{section_q}"
|
|
165
|
+
elsif options[:remove]
|
|
166
|
+
"Are you sure you want to remove #{tags.join(' and ')} from #{matches} records#{section_q}"
|
|
167
|
+
else
|
|
168
|
+
"Are you sure you want to add #{tags.join(' and ')} to #{matches} records#{section_q}"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
res = Doing::Prompt.yn(question, default_response: false)
|
|
172
|
+
|
|
173
|
+
raise UserCancelled unless res
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
@wwid.tag_last(options)
|
|
178
|
+
end
|
|
179
|
+
end
|