doing 2.1.24 → 2.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +17 -21
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +325 -102
  6. data/Dockerfile +5 -5
  7. data/Dockerfile-2.6 +5 -5
  8. data/Dockerfile-2.7 +5 -4
  9. data/Dockerfile-3.0 +5 -4
  10. data/Gemfile.lock +1 -1
  11. data/README.md +1 -1
  12. data/Rakefile +3 -3
  13. data/bin/commands/add_section.rb +15 -0
  14. data/bin/commands/again.rb +57 -0
  15. data/bin/commands/archive.rb +55 -0
  16. data/bin/commands/cancel.rb +60 -0
  17. data/bin/commands/changes.rb +73 -0
  18. data/bin/commands/choose.rb +9 -0
  19. data/bin/commands/colors.rb +21 -0
  20. data/bin/commands/commands.rb +89 -0
  21. data/bin/commands/commands_accepting.rb +76 -0
  22. data/bin/commands/completion.rb +27 -0
  23. data/bin/commands/config.rb +245 -0
  24. data/bin/commands/done.rb +235 -0
  25. data/bin/commands/finish.rb +126 -0
  26. data/bin/commands/flag.rb +90 -0
  27. data/bin/commands/grep.rb +108 -0
  28. data/bin/commands/import.rb +71 -0
  29. data/bin/commands/install_fzf.rb +17 -0
  30. data/bin/commands/last.rb +81 -0
  31. data/bin/commands/meanwhile.rb +76 -0
  32. data/bin/commands/note.rb +91 -0
  33. data/bin/commands/now.rb +145 -0
  34. data/bin/commands/on.rb +65 -0
  35. data/bin/commands/open.rb +53 -0
  36. data/bin/commands/plugins.rb +23 -0
  37. data/bin/commands/recent.rb +77 -0
  38. data/bin/commands/redo.rb +26 -0
  39. data/bin/commands/reset.rb +73 -0
  40. data/bin/commands/rotate.rb +42 -0
  41. data/bin/commands/sections.rb +11 -0
  42. data/bin/commands/select.rb +105 -0
  43. data/bin/commands/show.rb +185 -0
  44. data/bin/commands/since.rb +63 -0
  45. data/bin/commands/tag.rb +149 -0
  46. data/bin/commands/tag_dir.rb +29 -0
  47. data/bin/commands/tags.rb +66 -0
  48. data/bin/commands/template.rb +61 -0
  49. data/bin/commands/today.rb +64 -0
  50. data/bin/commands/undo.rb +49 -0
  51. data/bin/commands/view.rb +201 -0
  52. data/bin/commands/views.rb +11 -0
  53. data/bin/commands/yesterday.rb +72 -0
  54. data/bin/doing +241 -3662
  55. data/docs/doc/Array.html +13 -449
  56. data/docs/doc/BooleanTermParser/Clause.html +5 -5
  57. data/docs/doc/BooleanTermParser/Operator.html +4 -4
  58. data/docs/doc/BooleanTermParser/Query.html +8 -8
  59. data/docs/doc/BooleanTermParser/QueryParser.html +2 -2
  60. data/docs/doc/BooleanTermParser/QueryTransformer.html +2 -2
  61. data/docs/doc/BooleanTermParser.html +1 -1
  62. data/docs/doc/Doing/Color.html +65 -59
  63. data/docs/doc/Doing/Completion.html +2 -2
  64. data/docs/doc/Doing/Configuration.html +49 -16
  65. data/docs/doc/Doing/Errors/DoingNoTraceError.html +2 -2
  66. data/docs/doc/Doing/Errors/DoingRuntimeError.html +2 -2
  67. data/docs/doc/Doing/Errors/DoingStandardError.html +2 -2
  68. data/docs/doc/Doing/Errors/EmptyInput.html +2 -2
  69. data/docs/doc/Doing/Errors/NoResults.html +2 -2
  70. data/docs/doc/Doing/Errors/PluginException.html +3 -3
  71. data/docs/doc/Doing/Errors/UserCancelled.html +2 -2
  72. data/docs/doc/Doing/Errors/WrongCommand.html +2 -2
  73. data/docs/doc/Doing/Errors.html +1 -1
  74. data/docs/doc/Doing/Hooks.html +6 -6
  75. data/docs/doc/Doing/Item.html +50 -16
  76. data/docs/doc/Doing/Items.html +10 -10
  77. data/docs/doc/Doing/LogAdapter.html +24 -24
  78. data/docs/doc/Doing/Note.html +7 -7
  79. data/docs/doc/Doing/Pager.html +4 -4
  80. data/docs/doc/Doing/Plugins.html +7 -7
  81. data/docs/doc/Doing/Prompt.html +59 -14
  82. data/docs/doc/Doing/Section.html +6 -6
  83. data/docs/doc/Doing/TemplateString.html +8 -8
  84. data/docs/doc/Doing/Types.html +46 -1
  85. data/docs/doc/Doing/Util/Backup.html +10 -10
  86. data/docs/doc/Doing/Util.html +15 -15
  87. data/docs/doc/Doing/WWID.html +73 -61
  88. data/docs/doc/Doing.html +3 -3
  89. data/docs/doc/FalseClass.html +235 -0
  90. data/docs/doc/GLI/Commands/Help.html +3 -3
  91. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +17 -17
  92. data/docs/doc/GLI/Commands.html +1 -1
  93. data/docs/doc/GLI.html +1 -1
  94. data/docs/doc/Hash.html +45 -11
  95. data/docs/doc/Numeric.html +5 -5
  96. data/docs/doc/Object.html +203 -0
  97. data/docs/doc/PhraseParser/Operator.html +4 -4
  98. data/docs/doc/PhraseParser/PhraseClause.html +5 -5
  99. data/docs/doc/PhraseParser/Query.html +10 -10
  100. data/docs/doc/PhraseParser/QueryParser.html +2 -2
  101. data/docs/doc/PhraseParser/QueryTransformer.html +2 -2
  102. data/docs/doc/PhraseParser/TermClause.html +5 -5
  103. data/docs/doc/PhraseParser.html +1 -1
  104. data/docs/doc/Status.html +7 -7
  105. data/docs/doc/String.html +306 -3111
  106. data/docs/doc/Symbol.html +45 -11
  107. data/docs/doc/Time.html +6 -6
  108. data/docs/doc/TrueClass.html +235 -0
  109. data/docs/doc/_index.html +37 -19
  110. data/docs/doc/class_list.html +1 -1
  111. data/docs/doc/file.README.html +2 -2
  112. data/docs/doc/index.html +2 -2
  113. data/docs/doc/method_list.html +240 -576
  114. data/docs/doc/top-level-namespace.html +2 -2
  115. data/doing.rdoc +297 -169
  116. data/example_plugin.rb +2 -2
  117. data/lib/completion/_doing.zsh +35 -31
  118. data/lib/completion/doing.bash +30 -19
  119. data/lib/completion/doing.fish +81 -67
  120. data/lib/doing/array/array.rb +4 -0
  121. data/lib/doing/array/nested_hash.rb +17 -0
  122. data/lib/doing/{array.rb → array/tags.rb} +7 -25
  123. data/lib/doing/changelog/change.rb +26 -11
  124. data/lib/doing/changelog/changes.rb +16 -4
  125. data/lib/doing/{array_chronify.rb → chronify/array.rb} +0 -0
  126. data/lib/doing/chronify/chronify.rb +5 -0
  127. data/lib/doing/{numeric_chronify.rb → chronify/numeric.rb} +0 -0
  128. data/lib/doing/{string_chronify.rb → chronify/string.rb} +0 -0
  129. data/lib/doing/colors.rb +115 -54
  130. data/lib/doing/configuration.rb +9 -6
  131. data/lib/doing/good.rb +72 -0
  132. data/lib/doing/hash.rb +4 -0
  133. data/lib/doing/help_monkey_patch.rb +6 -5
  134. data/lib/doing/hooks.rb +3 -3
  135. data/lib/doing/item.rb +19 -15
  136. data/lib/doing/items.rb +2 -2
  137. data/lib/doing/log_adapter.rb +35 -2
  138. data/lib/doing/normalize.rb +188 -0
  139. data/lib/doing/pager.rb +1 -0
  140. data/lib/doing/plugins/export/dayone_export.rb +1 -1
  141. data/lib/doing/plugins/export/html_export.rb +1 -1
  142. data/lib/doing/plugins/export/json_export.rb +1 -1
  143. data/lib/doing/plugins/export/markdown_export.rb +1 -1
  144. data/lib/doing/plugins/export/template_export.rb +3 -1
  145. data/lib/doing/plugins/import/calendar_import.rb +1 -1
  146. data/lib/doing/plugins/import/doing_import.rb +1 -1
  147. data/lib/doing/plugins/import/timing_import.rb +1 -1
  148. data/lib/doing/prompt.rb +9 -3
  149. data/lib/doing/string/highlight.rb +95 -0
  150. data/lib/doing/string/query.rb +129 -0
  151. data/lib/doing/string/string.rb +12 -0
  152. data/lib/doing/string/tags.rb +164 -0
  153. data/lib/doing/string/transform.rb +168 -0
  154. data/lib/doing/string/truncate.rb +75 -0
  155. data/lib/doing/string/url.rb +82 -0
  156. data/lib/doing/template_string.rb +2 -24
  157. data/lib/doing/types.rb +9 -0
  158. data/lib/doing/util.rb +20 -16
  159. data/lib/doing/version.rb +1 -1
  160. data/lib/doing/wwid.rb +91 -51
  161. data/lib/doing.rb +5 -6
  162. data/lib/examples/commands/wiki.rb +6 -7
  163. data/lib/examples/plugins/wiki_export/wiki_export.rb +1 -1
  164. data/lib/helpers/threaded_tests.rb +69 -79
  165. data/lib/helpers/threaded_tests_string.rb +50 -0
  166. data/scripts/deploy.rb +107 -0
  167. data/scripts/runtests.sh +4 -0
  168. metadata +65 -8
  169. data/lib/doing/string.rb +0 -765
  170. data/lib/doing/symbol.rb +0 -28
@@ -0,0 +1,149 @@
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 'Select item(s) to tag from a menu of matching entries'
59
+ c.switch %i[i interactive], negatable: false, default_value: false
60
+
61
+ add_options(:search, c)
62
+ add_options(:tag_filter, c)
63
+
64
+ c.action do |_global_options, options, args|
65
+ options[:fuzzy] = false
66
+ # raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
67
+
68
+ raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
69
+
70
+ section = 'All'
71
+
72
+ if options[:section]
73
+ section = @wwid.guess_section(options[:section]) || options[:section].cap_first
74
+ end
75
+
76
+
77
+ if options[:tag].nil?
78
+ search_tags = []
79
+ else
80
+ search_tags = options[:tag]
81
+ end
82
+
83
+ if options[:autotag]
84
+ tags = []
85
+ else
86
+ if args.empty?
87
+ tags = []
88
+ else
89
+ tags = if args.join('') =~ /,/
90
+ args.join('').split(/ *, */)
91
+ else
92
+ args.join(' ').split(' ') # in case tags are quoted as one arg
93
+ end
94
+ end
95
+
96
+ tags.map! { |tag| tag.sub(/^@/, '').strip }
97
+ end
98
+
99
+ if options[:interactive]
100
+ count = 0
101
+ options[:force] = true
102
+ else
103
+ count = options[:count].to_i
104
+ end
105
+
106
+ if options[:search]
107
+ search = options[:search]
108
+ search.sub!(/^'?/, "'") if options[:exact]
109
+ options[:search] = search
110
+ end
111
+
112
+ options[:count] = count
113
+ options[:section] = section
114
+ options[:tag] = search_tags
115
+ options[:tags] = tags
116
+ options[:tag_bool] = options[:bool]
117
+
118
+ if count.zero? && !options[:force]
119
+ matches = @wwid.filter_items([], opt: options).count
120
+
121
+ if matches > 5
122
+ if options[:search]
123
+ section_q = ' matching your search terms'
124
+ elsif options[:tag]
125
+ section_q = ' matching your tag search'
126
+ elsif section == 'All'
127
+ section_q = ''
128
+ else
129
+ section_q = " in section #{section}"
130
+ end
131
+
132
+
133
+ question = if options[:autotag]
134
+ "Are you sure you want to autotag #{matches} records#{section_q}"
135
+ elsif options[:remove]
136
+ "Are you sure you want to remove #{tags.join(' and ')} from #{matches} records#{section_q}"
137
+ else
138
+ "Are you sure you want to add #{tags.join(' and ')} to #{matches} records#{section_q}"
139
+ end
140
+
141
+ res = Doing::Prompt.yn(question, default_response: false)
142
+
143
+ raise UserCancelled unless res
144
+ end
145
+ end
146
+
147
+ @wwid.tag_last(options)
148
+ end
149
+ end
@@ -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,66 @@
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, type: OrderSymbol
22
+
23
+ c.desc 'Select items to scan from a menu of matching entries'
24
+ c.switch %i[i interactive], negatable: false, default_value: false
25
+
26
+ add_options(:search, c)
27
+ add_options(:tag_filter, c)
28
+
29
+ c.action do |_global, options, args|
30
+ section = @wwid.guess_section(options[:section]) || options[:section].cap_first
31
+ options[:count] = args.count.positive? ? args[0].to_i : 0
32
+
33
+ items = @wwid.filter_items([], opt: options)
34
+
35
+ if options[:interactive]
36
+ items = Doing::Prompt.choose_from_items(items, include_section: options[:section].nil?,
37
+ menu: true,
38
+ header: '',
39
+ prompt: 'Select entries to scan > ',
40
+ multiple: true,
41
+ sort: true,
42
+ show_if_single: true)
43
+ end
44
+
45
+ # items = @wwid.content.in_section(section)
46
+ tags = @wwid.all_tags(items, counts: true)
47
+
48
+ if options[:sort] =~ /^n/i
49
+ tags = tags.sort_by { |tag, count| tag }
50
+ else
51
+ tags = tags.sort_by { |tag, count| count }
52
+ end
53
+
54
+ tags.reverse! if options[:order] == :desc
55
+
56
+ if options[:counts]
57
+ tags.each { |t, c| puts "#{t} (#{c})" }
58
+ else
59
+ if options[:line]
60
+ puts tags.map { |t, c| t }.to_tags.join(' ')
61
+ else
62
+ tags.each { |t, c| puts "#{t}" }
63
+ end
64
+ end
65
+ end
66
+ 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,64 @@
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 = @settings['tag_sort'].normalize_tag_sort || :name
26
+ c.arg_name 'KEY'
27
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
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: 'today'
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.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
42
+ c.arg_name 'TIME_STRING'
43
+ c.flag [:before]
44
+
45
+ c.desc 'View entries after specified time (e.g. 8am, 12:30pm, 15:00)'
46
+ c.arg_name 'TIME_STRING'
47
+ c.flag [:after]
48
+
49
+ c.desc %(
50
+ Time range to show `doing today --from "12pm to 4pm"`
51
+ )
52
+ c.arg_name 'TIME_RANGE'
53
+ c.flag [:from], type: DateRangeString
54
+
55
+ c.action do |_global_options, options, _args|
56
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
57
+
58
+ options[:times] = true if options[:totals]
59
+ options[:sort_tags] = options[:tag_sort]
60
+ filter_options = %i[after before duration from section sort_tags totals template config_template].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
61
+
62
+ Doing::Pager.page @wwid.today(options[:times], options[:output], filter_options).chomp
63
+ end
64
+ 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,201 @@
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, type: AgeSymbol
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 "Highlight search matches in output. Only affects command line output"
39
+ c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
40
+
41
+ c.desc 'Sort tags by (name|time)'
42
+ c.arg_name 'KEY'
43
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, type: TagSortSymbol
44
+
45
+ c.desc 'Tag sort direction (asc|desc)'
46
+ c.arg_name 'DIRECTION'
47
+ c.flag [:tag_order], must_match: REGEX_SORT_ORDER, type: OrderSymbol
48
+
49
+ c.desc 'Only show items with recorded time intervals (override view settings)'
50
+ c.switch [:only_timed], default_value: false, negatable: false
51
+
52
+ c.desc 'Select from a menu of matching entries to perform additional operations'
53
+ c.switch %i[i interactive], negatable: false, default_value: false
54
+
55
+ add_options(:search, c)
56
+ add_options(:tag_filter, c)
57
+ add_options(:date_filter, c)
58
+
59
+ c.action do |global_options, options, args|
60
+ options[:fuzzy] = false
61
+ if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
62
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}")
63
+
64
+ end
65
+
66
+ raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
67
+
68
+ title = if args.empty?
69
+ @wwid.choose_view
70
+ else
71
+ begin
72
+ @wwid.guess_view(args[0])
73
+ rescue WrongCommand
74
+ cmd = commands[:show]
75
+ options[:sort] = :asc
76
+ options[:tag_order] = :asc
77
+ action = cmd.send(:get_action, nil)
78
+ return action.call(global_options, options, args)
79
+ end
80
+ end
81
+
82
+ section = if options[:section]
83
+ @wwid.guess_section(options[:section]) || options[:section].cap_first
84
+ else
85
+ @settings['current_section']
86
+ end
87
+
88
+ view = @wwid.get_view(title)
89
+
90
+ if view
91
+ page_title = view['title'] || title.cap_first
92
+ only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
93
+ true
94
+ else
95
+ false
96
+ end
97
+
98
+ template = view['template'] || nil
99
+ date_format = view['date_format'] || nil
100
+
101
+ tags_color = view['tags_color'] || nil
102
+ tag_filter = false
103
+ if options[:tag]
104
+ tag_filter = { 'tags' => [], 'bool' => 'OR' }
105
+ bool = options[:bool]
106
+ tag_filter['bool'] = bool
107
+ tag_filter['tags'] = if bool == :pattern
108
+ options[:tag]
109
+ else
110
+ options[:tag].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
111
+ end
112
+ elsif view.key?('tags') && view['tags'].good?
113
+ tag_filter = { 'tags' => [], 'bool' => 'OR' }
114
+ bool = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :pattern
115
+ tag_filter['bool'] = bool
116
+ tag_filter['tags'] = if view['tags'].instance_of?(Array)
117
+ bool == :pattern ? view['tags'].join(' ').strip : view['tags'].map(&:strip)
118
+ else
119
+ bool == :pattern ? view['tags'].strip : view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
120
+ end
121
+ end
122
+
123
+ # If the -o/--output flag was specified, override any default in the view template
124
+ options[:output] ||= view.key?('output_format') ? view['output_format'] : 'template'
125
+
126
+ count = if options[:count]
127
+ options[:count]
128
+ elsif view.key?('count')
129
+ view['count']
130
+ else
131
+ 10
132
+ end
133
+
134
+ section = if options[:section]
135
+ section
136
+ else
137
+ view['section'] || @settings['current_section']
138
+ end
139
+ order = if view.key?('order')
140
+ view['order'].normalize_order
141
+ else
142
+ :asc
143
+ end
144
+
145
+ totals = if options[:totals]
146
+ true
147
+ else
148
+ view['totals'] || false
149
+ end
150
+ tag_order = options[:tag_order] || view['tag_order']&.normalize_order || :asc
151
+
152
+ options[:times] = true if totals
153
+ output_format = options[:output]&.downcase || 'template'
154
+
155
+ options[:sort_tags] = if options[:tag_sort]
156
+ options[:tag_sort]
157
+ elsif view.key?('tag_sort')
158
+ view['tag_sort'].normalize_tag_sort
159
+ else
160
+ false
161
+ end
162
+
163
+ %w[before after from duration].each { |k| options[k.to_sym] = view[k] if view.key?(k) && !options[k.to_sym] }
164
+
165
+ search = nil
166
+
167
+ if options[:search]
168
+ search = options[:search]
169
+ search.sub!(/^'?/, "'") if options[:exact]
170
+ end
171
+
172
+ options[:age] ||= :newest
173
+
174
+ opts = options.clone
175
+ opts[:age] = options[:age]
176
+ opts[:count] = count
177
+ opts[:format] = date_format
178
+ opts[:highlight] = options[:color]
179
+ opts[:hilite] = options[:hilite]
180
+ opts[:only_timed] = only_timed
181
+ opts[:order] = order
182
+ opts[:output] = options[:interactive] ? nil : options[:output]
183
+ opts[:output] = output_format
184
+ opts[:page_title] = page_title
185
+ opts[:search] = search
186
+ opts[:section] = section
187
+ opts[:tag_filter] = tag_filter
188
+ opts[:tag_order] = tag_order
189
+ opts[:tags_color] = tags_color
190
+ opts[:template] = template
191
+ opts[:totals] = totals
192
+ opts[:view_template] = title
193
+
194
+ Doing::Pager.page @wwid.list_section(opts)
195
+ elsif title.instance_of?(FalseClass)
196
+ raise UserCancelled, 'Cancelled'
197
+ else
198
+ raise InvalidView, "View #{title} not found in config"
199
+ end
200
+ end
201
+ 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