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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +4 -4
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +283 -108
  6. data/Gemfile.lock +1 -1
  7. data/README.md +1 -1
  8. data/bin/commands/add_section.rb +13 -0
  9. data/bin/commands/again.rb +99 -0
  10. data/bin/commands/archive.rb +96 -0
  11. data/bin/commands/cancel.rb +102 -0
  12. data/bin/commands/changes.rb +42 -0
  13. data/bin/commands/choose.rb +9 -0
  14. data/bin/commands/colors.rb +19 -0
  15. data/bin/commands/commands.rb +87 -0
  16. data/bin/commands/commands_accepting.rb +25 -0
  17. data/bin/commands/completion.rb +24 -0
  18. data/bin/commands/config.rb +245 -0
  19. data/bin/commands/done.rb +249 -0
  20. data/bin/commands/finish.rb +149 -0
  21. data/bin/commands/flag.rb +126 -0
  22. data/bin/commands/grep.rb +124 -0
  23. data/bin/commands/import.rb +101 -0
  24. data/bin/commands/install_fzf.rb +17 -0
  25. data/bin/commands/last.rb +114 -0
  26. data/bin/commands/meanwhile.rb +86 -0
  27. data/bin/commands/note.rb +130 -0
  28. data/bin/commands/now.rb +151 -0
  29. data/bin/commands/on.rb +66 -0
  30. data/bin/commands/open.rb +53 -0
  31. data/bin/commands/plugins.rb +23 -0
  32. data/bin/commands/recent.rb +78 -0
  33. data/bin/commands/redo.rb +22 -0
  34. data/bin/commands/reset.rb +106 -0
  35. data/bin/commands/rotate.rb +73 -0
  36. data/bin/commands/sections.rb +11 -0
  37. data/bin/commands/select.rb +123 -0
  38. data/bin/commands/show.rb +231 -0
  39. data/bin/commands/since.rb +64 -0
  40. data/bin/commands/tag.rb +179 -0
  41. data/bin/commands/tag_dir.rb +29 -0
  42. data/bin/commands/tags.rb +93 -0
  43. data/bin/commands/template.rb +61 -0
  44. data/bin/commands/today.rb +65 -0
  45. data/bin/commands/undo.rb +49 -0
  46. data/bin/commands/view.rb +238 -0
  47. data/bin/commands/views.rb +11 -0
  48. data/bin/commands/yesterday.rb +73 -0
  49. data/bin/doing +39 -3641
  50. data/docs/doc/Array.html +1 -1
  51. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  52. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  53. data/docs/doc/BooleanTermParser/Query.html +1 -1
  54. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  55. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  56. data/docs/doc/BooleanTermParser.html +1 -1
  57. data/docs/doc/Doing/Color.html +1 -1
  58. data/docs/doc/Doing/Completion.html +1 -1
  59. data/docs/doc/Doing/Configuration.html +2 -1
  60. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  61. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  62. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  63. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  64. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  65. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  66. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  67. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  68. data/docs/doc/Doing/Errors.html +1 -1
  69. data/docs/doc/Doing/Hooks.html +1 -1
  70. data/docs/doc/Doing/Item.html +1 -1
  71. data/docs/doc/Doing/Items.html +1 -1
  72. data/docs/doc/Doing/LogAdapter.html +1 -1
  73. data/docs/doc/Doing/Note.html +1 -1
  74. data/docs/doc/Doing/Pager.html +1 -1
  75. data/docs/doc/Doing/Plugins.html +1 -1
  76. data/docs/doc/Doing/Prompt.html +46 -1
  77. data/docs/doc/Doing/Section.html +1 -1
  78. data/docs/doc/Doing/TemplateString.html +1 -1
  79. data/docs/doc/Doing/Types.html +1 -1
  80. data/docs/doc/Doing/Util/Backup.html +1 -1
  81. data/docs/doc/Doing/Util.html +1 -1
  82. data/docs/doc/Doing/WWID.html +1 -1
  83. data/docs/doc/Doing.html +2 -2
  84. data/docs/doc/FalseClass.html +201 -0
  85. data/docs/doc/GLI/Commands/Help.html +1 -1
  86. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  87. data/docs/doc/GLI/Commands.html +1 -1
  88. data/docs/doc/GLI.html +1 -1
  89. data/docs/doc/Hash.html +1 -1
  90. data/docs/doc/Numeric.html +1 -1
  91. data/docs/doc/Object.html +203 -0
  92. data/docs/doc/PhraseParser/Operator.html +1 -1
  93. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  94. data/docs/doc/PhraseParser/Query.html +1 -1
  95. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  96. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  97. data/docs/doc/PhraseParser/TermClause.html +1 -1
  98. data/docs/doc/PhraseParser.html +1 -1
  99. data/docs/doc/Status.html +1 -1
  100. data/docs/doc/String.html +1 -1
  101. data/docs/doc/Symbol.html +1 -1
  102. data/docs/doc/Time.html +1 -1
  103. data/docs/doc/TrueClass.html +201 -0
  104. data/docs/doc/_index.html +1 -1
  105. data/docs/doc/file.README.html +2 -2
  106. data/docs/doc/index.html +2 -2
  107. data/docs/doc/method_list.html +374 -366
  108. data/docs/doc/top-level-namespace.html +1 -1
  109. data/doing.rdoc +15 -5
  110. data/lib/completion/_doing.zsh +3 -3
  111. data/lib/completion/doing.fish +1 -1
  112. data/lib/doing/changelog/changes.rb +1 -1
  113. data/lib/doing/configuration.rb +1 -0
  114. data/lib/doing/pager.rb +1 -0
  115. data/lib/doing/prompt.rb +8 -0
  116. data/lib/doing/version.rb +1 -1
  117. data/lib/examples/commands/wiki.rb +6 -7
  118. data/lib/helpers/threaded_tests.rb +25 -19
  119. 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
@@ -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