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,29 @@
|
|
|
1
|
+
# @@tag_dir
|
|
2
|
+
desc 'Set the default tags for the current directory'
|
|
3
|
+
long_desc 'Adds default_tags to a .doingrc file in the current directory. Any entry created in this directory or its
|
|
4
|
+
subdirectories will be tagged with the default tags. You can modify these any time using the `config set` commnand or
|
|
5
|
+
manually editing the .doingrc file.'
|
|
6
|
+
arg_name 'TAG [TAG..]'
|
|
7
|
+
command :tag_dir do |c|
|
|
8
|
+
c.example 'doing tag_dir project1 project2', desc: 'Add @project1 and @project to to any entries in the current directory'
|
|
9
|
+
c.example 'doing tag_dir --remove', desc: 'Clear the default tags for the directory'
|
|
10
|
+
|
|
11
|
+
c.desc 'Remove all default_tags from the local .doingrc'
|
|
12
|
+
c.switch %i[r remove], negatable: false
|
|
13
|
+
|
|
14
|
+
c.action do |global, options, args|
|
|
15
|
+
tags = args.join(' ').gsub(/ *, */, ' ').split(' ')
|
|
16
|
+
|
|
17
|
+
cfg_cmd = commands[:config]
|
|
18
|
+
set_cmd = cfg_cmd.commands[:set]
|
|
19
|
+
set_options = {}
|
|
20
|
+
if options[:remove]
|
|
21
|
+
set_args = ['default_tags']
|
|
22
|
+
set_options[:remove] = true
|
|
23
|
+
else
|
|
24
|
+
set_args = ['default_tags', tags.join(',')]
|
|
25
|
+
end
|
|
26
|
+
action = set_cmd.send(:get_action, nil)
|
|
27
|
+
return action.call(global, set_options, set_args)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @@tags
|
|
2
|
+
desc 'List all tags in the current Doing file'
|
|
3
|
+
arg_name 'MAX_COUNT', optional: true, type: Integer
|
|
4
|
+
command :tags do |c|
|
|
5
|
+
c.desc 'Section'
|
|
6
|
+
c.arg_name 'SECTION_NAME'
|
|
7
|
+
c.flag %i[s section], default_value: 'All'
|
|
8
|
+
|
|
9
|
+
c.desc 'Show count of occurrences'
|
|
10
|
+
c.switch %i[c counts]
|
|
11
|
+
|
|
12
|
+
c.desc 'Output in a single line with @ symbols. Ignored if --counts is specified.'
|
|
13
|
+
c.switch %i[l line]
|
|
14
|
+
|
|
15
|
+
c.desc 'Sort by name or count'
|
|
16
|
+
c.arg_name 'SORT_ORDER'
|
|
17
|
+
c.flag %i[sort], default_value: 'name', must_match: /^(?:n(?:ame)?|c(?:ount)?)$/
|
|
18
|
+
|
|
19
|
+
c.desc 'Sort order (asc/desc)'
|
|
20
|
+
c.arg_name 'ORDER'
|
|
21
|
+
c.flag %i[o order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
|
22
|
+
|
|
23
|
+
c.desc 'Get tags for entries matching tags. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
|
24
|
+
c.arg_name 'TAG'
|
|
25
|
+
c.flag [:tag]
|
|
26
|
+
|
|
27
|
+
c.desc 'Get tags for items matching search. Surround with
|
|
28
|
+
slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
|
|
29
|
+
c.arg_name 'QUERY'
|
|
30
|
+
c.flag [:search]
|
|
31
|
+
|
|
32
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
33
|
+
c.arg_name 'QUERY'
|
|
34
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
35
|
+
|
|
36
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
37
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
38
|
+
|
|
39
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
40
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
41
|
+
|
|
42
|
+
c.desc 'Get tags from items that *don\'t* match search/tag filters'
|
|
43
|
+
c.switch [:not], default_value: false, negatable: false
|
|
44
|
+
|
|
45
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
46
|
+
c.arg_name 'TYPE'
|
|
47
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
48
|
+
|
|
49
|
+
c.desc 'Boolean used to combine multiple tags. Use PATTERN to parse + and - as booleans'
|
|
50
|
+
c.arg_name 'BOOLEAN'
|
|
51
|
+
c.flag [:bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
52
|
+
|
|
53
|
+
c.desc 'Select items to scan from a menu of matching entries'
|
|
54
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
55
|
+
|
|
56
|
+
c.action do |_global, options, args|
|
|
57
|
+
section = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
58
|
+
options[:count] = args.count.positive? ? args[0].to_i : 0
|
|
59
|
+
|
|
60
|
+
items = @wwid.filter_items([], opt: options)
|
|
61
|
+
|
|
62
|
+
if options[:interactive]
|
|
63
|
+
items = Doing::Prompt.choose_from_items(items, include_section: options[:section].nil?,
|
|
64
|
+
menu: true,
|
|
65
|
+
header: '',
|
|
66
|
+
prompt: 'Select entries to scan > ',
|
|
67
|
+
multiple: true,
|
|
68
|
+
sort: true,
|
|
69
|
+
show_if_single: true)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# items = @wwid.content.in_section(section)
|
|
73
|
+
tags = @wwid.all_tags(items, counts: true)
|
|
74
|
+
|
|
75
|
+
if options[:sort] =~ /^n/i
|
|
76
|
+
tags = tags.sort_by { |tag, count| tag }
|
|
77
|
+
else
|
|
78
|
+
tags = tags.sort_by { |tag, count| count }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
tags.reverse! if options[:order].normalize_order == 'desc'
|
|
82
|
+
|
|
83
|
+
if options[:counts]
|
|
84
|
+
tags.each { |t, c| puts "#{t} (#{c})" }
|
|
85
|
+
else
|
|
86
|
+
if options[:line]
|
|
87
|
+
puts tags.map { |t, c| t }.to_tags.join(' ')
|
|
88
|
+
else
|
|
89
|
+
tags.each { |t, c| puts "#{t}" }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @@template
|
|
2
|
+
desc 'Output HTML, CSS, and Markdown (ERB) templates for customization'
|
|
3
|
+
long_desc %(
|
|
4
|
+
Templates are printed to STDOUT for piping to a file.
|
|
5
|
+
Save them and use them in the configuration file under export_templates.
|
|
6
|
+
)
|
|
7
|
+
arg_name 'TYPE', must_match: Doing::Plugins.template_regex
|
|
8
|
+
command :template do |c|
|
|
9
|
+
c.example 'doing template haml > ~/styles/my_doing.haml', desc: 'Output the haml template and save it to a file'
|
|
10
|
+
|
|
11
|
+
c.desc 'List all available templates'
|
|
12
|
+
c.switch %i[l list], negatable: false
|
|
13
|
+
|
|
14
|
+
c.desc 'List in single column for completion'
|
|
15
|
+
c.switch %i[c column]
|
|
16
|
+
|
|
17
|
+
c.desc 'Save template to file instead of STDOUT'
|
|
18
|
+
c.switch %i[s save], default_value: false, negatable: false
|
|
19
|
+
|
|
20
|
+
c.desc 'Save template to alternate location'
|
|
21
|
+
c.arg_name 'DIRECTORY'
|
|
22
|
+
c.flag %i[p path], default_value: File.join(Doing::Util.user_home, '.config', 'doing', 'templates')
|
|
23
|
+
|
|
24
|
+
c.action do |_global_options, options, args|
|
|
25
|
+
if options[:list] || options[:column]
|
|
26
|
+
if options[:column]
|
|
27
|
+
$stdout.print Doing::Plugins.plugin_templates.join("\n")
|
|
28
|
+
else
|
|
29
|
+
$stdout.puts "Available templates: #{Doing::Plugins.plugin_templates.join(', ')}"
|
|
30
|
+
end
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if args.empty?
|
|
35
|
+
type = Doing::Prompt.choose_from(Doing::Plugins.plugin_templates, sorted: false, prompt: 'Select template type > ')
|
|
36
|
+
type.sub!(/ \(.*?\)$/, '').strip!
|
|
37
|
+
options[:save] = Doing::Prompt.yn("Save to #{options[:path]}? (No outputs to STDOUT)", default_response: false)
|
|
38
|
+
else
|
|
39
|
+
type = args[0]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
raise InvalidPluginType, "No type specified, use `doing template [#{Doing::Plugins.plugin_templates.join('|')}]`" unless type
|
|
43
|
+
|
|
44
|
+
if options[:save]
|
|
45
|
+
Doing::Plugins.template_for_trigger(type, save_to: options[:path])
|
|
46
|
+
else
|
|
47
|
+
$stdout.puts Doing::Plugins.template_for_trigger(type, save_to: nil)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# case args[0]
|
|
51
|
+
# when /html|haml/i
|
|
52
|
+
# $stdout.puts @wwid.haml_template
|
|
53
|
+
# when /css/i
|
|
54
|
+
# $stdout.puts @wwid.css_template
|
|
55
|
+
# when /markdown|md|erb/i
|
|
56
|
+
# $stdout.puts @wwid.markdown_template
|
|
57
|
+
# else
|
|
58
|
+
# exit_now! 'Invalid type specified, must be HAML or CSS'
|
|
59
|
+
# end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @@today
|
|
2
|
+
desc 'List entries from today'
|
|
3
|
+
long_desc 'List entries from the current day. Use --before, --after, and
|
|
4
|
+
--from to specify time ranges.'
|
|
5
|
+
command :today do |c|
|
|
6
|
+
c.example 'doing today', desc: 'List all entries with start dates between 12am and 11:59PM for the current day'
|
|
7
|
+
c.example 'doing today --section Later', desc: 'List today\'s entries in the Later section'
|
|
8
|
+
c.example 'doing today --before 3pm --after 12pm', desc: 'List entries with start dates between 12pm and 3pm today'
|
|
9
|
+
c.example 'doing today --output json', desc: 'Output entries from today in JSON format'
|
|
10
|
+
|
|
11
|
+
c.desc 'Specify a section'
|
|
12
|
+
c.arg_name 'NAME'
|
|
13
|
+
c.flag %i[s section], default_value: 'All'
|
|
14
|
+
|
|
15
|
+
c.desc 'Show time intervals on @done tasks'
|
|
16
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
17
|
+
|
|
18
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
19
|
+
c.switch [:duration]
|
|
20
|
+
|
|
21
|
+
c.desc 'Show time totals at the end of output'
|
|
22
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
23
|
+
|
|
24
|
+
c.desc 'Sort tags by (name|time)'
|
|
25
|
+
default = 'time'
|
|
26
|
+
default = @settings['tag_sort'] || 'name'
|
|
27
|
+
c.arg_name 'KEY'
|
|
28
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
|
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: 'today'
|
|
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.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
|
|
43
|
+
c.arg_name 'TIME_STRING'
|
|
44
|
+
c.flag [:before]
|
|
45
|
+
|
|
46
|
+
c.desc 'View entries after specified time (e.g. 8am, 12:30pm, 15:00)'
|
|
47
|
+
c.arg_name 'TIME_STRING'
|
|
48
|
+
c.flag [:after]
|
|
49
|
+
|
|
50
|
+
c.desc %(
|
|
51
|
+
Time range to show `doing today --from "12pm to 4pm"`
|
|
52
|
+
)
|
|
53
|
+
c.arg_name 'TIME_RANGE'
|
|
54
|
+
c.flag [:from], type: DateRangeString
|
|
55
|
+
|
|
56
|
+
c.action do |_global_options, options, _args|
|
|
57
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
58
|
+
|
|
59
|
+
options[:times] = true if options[:totals]
|
|
60
|
+
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
61
|
+
filter_options = %i[after before duration from section sort_tags totals template config_template].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
|
|
62
|
+
|
|
63
|
+
Doing::Pager.page @wwid.today(options[:times], options[:output], filter_options).chomp
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @@undo
|
|
2
|
+
desc 'Undo the last X changes to the Doing file'
|
|
3
|
+
long_desc 'Reverts the last X commands that altered the doing file.
|
|
4
|
+
All changes performed by a single command are undone at once.
|
|
5
|
+
|
|
6
|
+
Specify a number to jump back multiple revisions, or use --select for an interactive menu.'
|
|
7
|
+
arg_name 'COUNT'
|
|
8
|
+
command :undo do |c|
|
|
9
|
+
c.example 'doing undo', desc: 'Undo the most recent change to the doing file'
|
|
10
|
+
c.example 'doing undo 5', desc: 'Undo the last 5 changes to the doing file'
|
|
11
|
+
c.example 'doing undo --interactive', desc: 'Select from a menu of available revisions'
|
|
12
|
+
c.example 'doing undo --redo', desc: 'Undo the last undo command'
|
|
13
|
+
|
|
14
|
+
c.desc 'Specify alternate doing file'
|
|
15
|
+
c.arg_name 'PATH'
|
|
16
|
+
c.flag %i[f file], default_value: @wwid.doing_file
|
|
17
|
+
|
|
18
|
+
c.desc 'Select from recent backups'
|
|
19
|
+
c.switch %i[i interactive], negatable: false
|
|
20
|
+
|
|
21
|
+
c.desc 'Remove old backups, retaining X files'
|
|
22
|
+
c.arg_name 'COUNT'
|
|
23
|
+
c.flag %i[p prune], type: Integer
|
|
24
|
+
|
|
25
|
+
c.desc 'Redo last undo. Note: you cannot undo a redo'
|
|
26
|
+
c.switch %i[r redo]
|
|
27
|
+
|
|
28
|
+
c.action do |_global_options, options, args|
|
|
29
|
+
file = options[:file] || @wwid.doing_file
|
|
30
|
+
count = args.empty? ? 1 : args[0].to_i
|
|
31
|
+
raise InvalidArgument, "Invalid count specified for undo" unless count&.positive?
|
|
32
|
+
|
|
33
|
+
if options[:prune]
|
|
34
|
+
Doing::Util::Backup.prune_backups(file, options[:prune])
|
|
35
|
+
elsif options[:redo]
|
|
36
|
+
if options[:interactive]
|
|
37
|
+
Doing::Util::Backup.select_redo(file)
|
|
38
|
+
else
|
|
39
|
+
Doing::Util::Backup.redo_backup(file, count: count)
|
|
40
|
+
end
|
|
41
|
+
else
|
|
42
|
+
if options[:interactive]
|
|
43
|
+
Doing::Util::Backup.select_backup(file)
|
|
44
|
+
else
|
|
45
|
+
Doing::Util::Backup.restore_last_backup(file, count: count)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# @@view
|
|
2
|
+
desc 'Display a user-created view'
|
|
3
|
+
long_desc 'Views are defined in your configuration (use `doing config` to edit).
|
|
4
|
+
Command line options override view configuration.'
|
|
5
|
+
arg_name 'VIEW_NAME'
|
|
6
|
+
command :view do |c|
|
|
7
|
+
c.example 'doing view color', desc: 'Display entries according to config for view "color"'
|
|
8
|
+
c.example 'doing view color --section Archive --count 10', desc: 'Display view "color", overriding some configured settings'
|
|
9
|
+
|
|
10
|
+
c.desc 'Section'
|
|
11
|
+
c.arg_name 'NAME'
|
|
12
|
+
c.flag %i[s section]
|
|
13
|
+
|
|
14
|
+
c.desc 'Count to display'
|
|
15
|
+
c.arg_name 'COUNT'
|
|
16
|
+
c.flag %i[c count], must_match: /^\d+$/, type: Integer
|
|
17
|
+
|
|
18
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
19
|
+
c.arg_name 'FORMAT'
|
|
20
|
+
c.flag %i[o output]
|
|
21
|
+
|
|
22
|
+
c.desc 'Age (oldest|newest)'
|
|
23
|
+
c.arg_name 'AGE'
|
|
24
|
+
c.flag %i[age], default_value: 'newest'
|
|
25
|
+
|
|
26
|
+
c.desc 'Show time intervals on @done tasks'
|
|
27
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
28
|
+
|
|
29
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
30
|
+
c.switch [:duration]
|
|
31
|
+
|
|
32
|
+
c.desc 'Show intervals with totals at the end of output'
|
|
33
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
34
|
+
|
|
35
|
+
c.desc 'Include colors in output'
|
|
36
|
+
c.switch [:color], default_value: true, negatable: true
|
|
37
|
+
|
|
38
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
|
39
|
+
c.arg_name 'TAG'
|
|
40
|
+
c.flag [:tag]
|
|
41
|
+
|
|
42
|
+
c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
|
|
43
|
+
c.arg_name 'QUERY'
|
|
44
|
+
c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
|
|
45
|
+
|
|
46
|
+
c.desc 'Tag boolean (AND,OR,NOT). Use PATTERN to parse + and - as booleans'
|
|
47
|
+
c.arg_name 'BOOLEAN'
|
|
48
|
+
c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'PATTERN'
|
|
49
|
+
|
|
50
|
+
c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
|
|
51
|
+
c.arg_name 'QUERY'
|
|
52
|
+
c.flag [:search]
|
|
53
|
+
|
|
54
|
+
c.desc "Highlight search matches in output. Only affects command line output"
|
|
55
|
+
c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
|
|
56
|
+
|
|
57
|
+
# c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
|
|
58
|
+
# c.switch [:fuzzy], default_value: false, negatable: false
|
|
59
|
+
|
|
60
|
+
c.desc 'Force exact search string matching (case sensitive)'
|
|
61
|
+
c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
|
|
62
|
+
|
|
63
|
+
c.desc 'Show items that *don\'t* match search string'
|
|
64
|
+
c.switch [:not], default_value: false, negatable: false
|
|
65
|
+
|
|
66
|
+
c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
|
|
67
|
+
c.arg_name 'TYPE'
|
|
68
|
+
c.flag [:case], must_match: /^[csi]/, default_value: @settings.dig('search', 'case')
|
|
69
|
+
|
|
70
|
+
c.desc 'Sort tags by (name|time)'
|
|
71
|
+
c.arg_name 'KEY'
|
|
72
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i
|
|
73
|
+
|
|
74
|
+
c.desc 'Tag sort direction (asc|desc)'
|
|
75
|
+
c.arg_name 'DIRECTION'
|
|
76
|
+
c.flag [:tag_order], must_match: REGEX_SORT_ORDER
|
|
77
|
+
|
|
78
|
+
c.desc 'View 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'
|
|
79
|
+
c.arg_name 'DATE_STRING'
|
|
80
|
+
c.flag [:before], type: DateBeginString
|
|
81
|
+
|
|
82
|
+
c.desc 'View 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'
|
|
83
|
+
c.arg_name 'DATE_STRING'
|
|
84
|
+
c.flag [:after], type: DateEndString
|
|
85
|
+
|
|
86
|
+
c.desc %(
|
|
87
|
+
Date range to show, or a single day to filter date on.
|
|
88
|
+
Date range argument should be quoted. Date specifications can be natural language.
|
|
89
|
+
To specify a range, use "to" or "through": `doing view --from "monday 8am to friday 5pm" view_name`.
|
|
90
|
+
|
|
91
|
+
If values are only time(s) (6am to noon) all dates will be included, but entries will be filtered
|
|
92
|
+
by time of day.
|
|
93
|
+
)
|
|
94
|
+
c.arg_name 'DATE_OR_RANGE'
|
|
95
|
+
c.flag [:from], type: DateRangeString
|
|
96
|
+
|
|
97
|
+
c.desc 'Only show items with recorded time intervals (override view settings)'
|
|
98
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
|
99
|
+
|
|
100
|
+
c.desc 'Select from a menu of matching entries to perform additional operations'
|
|
101
|
+
c.switch %i[i interactive], negatable: false, default_value: false
|
|
102
|
+
|
|
103
|
+
c.action do |global_options, options, args|
|
|
104
|
+
options[:fuzzy] = false
|
|
105
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
106
|
+
|
|
107
|
+
raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
|
|
108
|
+
|
|
109
|
+
title = if args.empty?
|
|
110
|
+
@wwid.choose_view
|
|
111
|
+
else
|
|
112
|
+
begin
|
|
113
|
+
@wwid.guess_view(args[0])
|
|
114
|
+
rescue WrongCommand => exception
|
|
115
|
+
cmd = commands[:show]
|
|
116
|
+
options[:sort] = 'asc'
|
|
117
|
+
options[:tag_order] = 'asc'
|
|
118
|
+
action = cmd.send(:get_action, nil)
|
|
119
|
+
return action.call(global_options, options, args)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if options[:section]
|
|
124
|
+
section = @wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
125
|
+
else
|
|
126
|
+
section = @settings['current_section']
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
view = @wwid.get_view(title)
|
|
130
|
+
|
|
131
|
+
if view
|
|
132
|
+
page_title = view['title'] || title.cap_first
|
|
133
|
+
only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
|
|
134
|
+
true
|
|
135
|
+
else
|
|
136
|
+
false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
template = view['template'] || nil
|
|
140
|
+
date_format = view['date_format'] || nil
|
|
141
|
+
|
|
142
|
+
tags_color = view['tags_color'] || nil
|
|
143
|
+
tag_filter = false
|
|
144
|
+
if options[:tag]
|
|
145
|
+
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
|
146
|
+
bool = options[:bool].normalize_bool
|
|
147
|
+
tag_filter['bool'] = bool
|
|
148
|
+
tag_filter['tags'] = if bool == :pattern
|
|
149
|
+
options[:tag]
|
|
150
|
+
else
|
|
151
|
+
options[:tag].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
|
152
|
+
end
|
|
153
|
+
elsif view.key?('tags') && view['tags'].good?
|
|
154
|
+
tag_filter = { 'tags' => [], 'bool' => 'OR' }
|
|
155
|
+
bool = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :pattern
|
|
156
|
+
tag_filter['bool'] = bool
|
|
157
|
+
tag_filter['tags'] = if view['tags'].instance_of?(Array)
|
|
158
|
+
bool == :pattern ? view['tags'].join(' ').strip : view['tags'].map(&:strip)
|
|
159
|
+
else
|
|
160
|
+
bool == :pattern ? view['tags'].strip : view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# If the -o/--output flag was specified, override any default in the view template
|
|
165
|
+
options[:output] ||= view.key?('output_format') ? view['output_format'] : 'template'
|
|
166
|
+
|
|
167
|
+
count = options[:count] ? options[:count] : view.key?('count') ? view['count'] : 10
|
|
168
|
+
|
|
169
|
+
section = if options[:section]
|
|
170
|
+
section
|
|
171
|
+
else
|
|
172
|
+
view['section'] || @settings['current_section']
|
|
173
|
+
end
|
|
174
|
+
order = view['order']&.normalize_order || 'asc'
|
|
175
|
+
|
|
176
|
+
totals = if options[:totals]
|
|
177
|
+
true
|
|
178
|
+
else
|
|
179
|
+
view['totals'] || false
|
|
180
|
+
end
|
|
181
|
+
tag_order = if options[:tag_order]
|
|
182
|
+
options[:tag_order].normalize_order
|
|
183
|
+
else
|
|
184
|
+
view['tag_order']&.normalize_order || 'asc'
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
options[:times] = true if totals
|
|
188
|
+
output_format = options[:output]&.downcase || 'template'
|
|
189
|
+
|
|
190
|
+
options[:sort_tags] = if options[:tag_sort]
|
|
191
|
+
options[:tag_sort] =~ /^n/i ? true : false
|
|
192
|
+
elsif view.key?('tag_sort')
|
|
193
|
+
view['tag_sort'] =~ /^n/i ? true : false
|
|
194
|
+
else
|
|
195
|
+
false
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
%w[before after from duration].each { |k| options[k.to_sym] = view[k] if view.key?(k) && !options[k.to_sym] }
|
|
199
|
+
|
|
200
|
+
options[:case] = options[:case].normalize_case
|
|
201
|
+
|
|
202
|
+
search = nil
|
|
203
|
+
|
|
204
|
+
if options[:search]
|
|
205
|
+
search = options[:search]
|
|
206
|
+
search.sub!(/^'?/, "'") if options[:exact]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
options[:age] ||= :newest
|
|
210
|
+
|
|
211
|
+
opts = options.clone
|
|
212
|
+
opts[:age] = options[:age].normalize_age(:newest)
|
|
213
|
+
opts[:count] = count
|
|
214
|
+
opts[:format] = date_format
|
|
215
|
+
opts[:highlight] = options[:color]
|
|
216
|
+
opts[:hilite] = options[:hilite]
|
|
217
|
+
opts[:only_timed] = only_timed
|
|
218
|
+
opts[:order] = order
|
|
219
|
+
opts[:output] = options[:interactive] ? nil : options[:output]
|
|
220
|
+
opts[:output] = output_format
|
|
221
|
+
opts[:page_title] = page_title
|
|
222
|
+
opts[:search] = search
|
|
223
|
+
opts[:section] = section
|
|
224
|
+
opts[:tag_filter] = tag_filter
|
|
225
|
+
opts[:tag_order] = tag_order
|
|
226
|
+
opts[:tags_color] = tags_color
|
|
227
|
+
opts[:template] = template
|
|
228
|
+
opts[:totals] = totals
|
|
229
|
+
opts[:view_template] = title
|
|
230
|
+
|
|
231
|
+
Doing::Pager.page @wwid.list_section(opts)
|
|
232
|
+
elsif title.instance_of?(FalseClass)
|
|
233
|
+
raise UserCancelled, 'Cancelled'
|
|
234
|
+
else
|
|
235
|
+
raise InvalidView, "View #{title} not found in config"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @@views
|
|
2
|
+
desc 'List available custom views'
|
|
3
|
+
command :views do |c|
|
|
4
|
+
c.desc 'List in single column'
|
|
5
|
+
c.switch %i[c column], default_value: false
|
|
6
|
+
|
|
7
|
+
c.action do |_global_options, options, _args|
|
|
8
|
+
joiner = options[:column] ? "\n" : "\t"
|
|
9
|
+
print @wwid.views.join(joiner)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @@yesterday
|
|
2
|
+
desc 'List entries from yesterday'
|
|
3
|
+
long_desc 'Show only entries with start times within the previous 24 hour period. Use --before, --after, and --from to limit to
|
|
4
|
+
time spans within the day.'
|
|
5
|
+
command :yesterday do |c|
|
|
6
|
+
c.example 'doing yesterday', desc: 'List all entries from the previous day'
|
|
7
|
+
c.example 'doing yesterday --after 8am --before 5pm', desc: 'List entries from the previous day between 8am and 5pm'
|
|
8
|
+
c.example 'doing yesterday --totals', desc: 'List entries from previous day, including tag timers'
|
|
9
|
+
|
|
10
|
+
c.desc 'Specify a section'
|
|
11
|
+
c.arg_name 'NAME'
|
|
12
|
+
c.flag %i[s section], default_value: 'All'
|
|
13
|
+
|
|
14
|
+
c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
|
|
15
|
+
c.arg_name 'FORMAT'
|
|
16
|
+
c.flag %i[o output]
|
|
17
|
+
|
|
18
|
+
c.desc "Output using a template from configuration"
|
|
19
|
+
c.arg_name 'TEMPLATE_KEY'
|
|
20
|
+
c.flag [:config_template], type: TemplateName, default_value: 'today'
|
|
21
|
+
|
|
22
|
+
c.desc 'Override output format with a template string containing %placeholders'
|
|
23
|
+
c.arg_name 'TEMPLATE_STRING'
|
|
24
|
+
c.flag [:template]
|
|
25
|
+
|
|
26
|
+
c.desc 'Show time intervals on @done tasks'
|
|
27
|
+
c.switch %i[t times], default_value: true, negatable: true
|
|
28
|
+
|
|
29
|
+
c.desc 'Show elapsed time on entries without @done tag'
|
|
30
|
+
c.switch [:duration]
|
|
31
|
+
|
|
32
|
+
c.desc 'Show time totals at the end of output'
|
|
33
|
+
c.switch [:totals], default_value: false, negatable: false
|
|
34
|
+
|
|
35
|
+
c.desc 'Sort tags by (name|time)'
|
|
36
|
+
default = @settings['tag_sort'] || 'name'
|
|
37
|
+
c.arg_name 'KEY'
|
|
38
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
|
39
|
+
|
|
40
|
+
c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
|
|
41
|
+
c.arg_name 'TIME_STRING'
|
|
42
|
+
c.flag [:before]
|
|
43
|
+
|
|
44
|
+
c.desc 'View entries after specified time (e.g. 8am, 12:30pm, 15:00)'
|
|
45
|
+
c.arg_name 'TIME_STRING'
|
|
46
|
+
c.flag [:after]
|
|
47
|
+
|
|
48
|
+
c.desc 'Time range to show, e.g. `doing yesterday --from "1am to 8am"`'
|
|
49
|
+
c.arg_name 'TIME_RANGE'
|
|
50
|
+
c.flag [:from], must_match: REGEX_TIME_RANGE
|
|
51
|
+
|
|
52
|
+
c.desc 'Tag sort direction (asc|desc)'
|
|
53
|
+
c.arg_name 'DIRECTION'
|
|
54
|
+
c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
|
|
55
|
+
|
|
56
|
+
c.action do |_global_options, options, _args|
|
|
57
|
+
raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
|
|
58
|
+
|
|
59
|
+
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
60
|
+
|
|
61
|
+
if options[:from]
|
|
62
|
+
options[:from] = options[:from].split(/#{REGEX_RANGE_INDICATOR}/).map do |time|
|
|
63
|
+
"yesterday #{time.sub(/(?mi)(^.*?(?=\d+)|(?<=[ap]m).*?$)/, '')}"
|
|
64
|
+
end.join(' to ').split_date_range
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
opt = options.clone
|
|
68
|
+
opt[:tag_order] = options[:tag_order].normalize_order
|
|
69
|
+
opt[:order] = @settings.dig('templates', options[:config_template], 'order')
|
|
70
|
+
|
|
71
|
+
Doing::Pager.page @wwid.yesterday(options[:section], options[:times], options[:output], opt).chomp
|
|
72
|
+
end
|
|
73
|
+
end
|