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,108 @@
1
+ # @@grep @@search
2
+ desc 'Search for entries'
3
+ long_desc %(
4
+ Search all sections (or limit to a single section) for entries matching text or regular expression. Normal strings are fuzzy matched.
5
+
6
+ To search with regular expressions, single quote the string and surround with slashes: `doing search '/\bm.*?x\b/'`
7
+ )
8
+ arg_name 'SEARCH_PATTERN'
9
+ command %i[grep search] do |c|
10
+ c.example 'doing grep "doing wiki"', desc: 'Find entries containing "doing wiki" using fuzzy matching'
11
+ c.example 'doing search "\'search command"', desc: 'Find entries containing "search command" using exact matching (search is an alias for grep)'
12
+ c.example 'doing grep "/do.*?wiki.*?@done/"', desc: 'Find entries matching regular expression'
13
+ c.example 'doing search --before 12/21 "doing wiki"', desc: 'Find entries containing "doing wiki" with entry dates before 12/21 of the current year'
14
+
15
+ c.desc 'Section'
16
+ c.arg_name 'NAME'
17
+ c.flag %i[s section], default_value: 'All'
18
+
19
+ c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
20
+ c.arg_name 'FORMAT'
21
+ c.flag %i[o output]
22
+
23
+ c.desc "Output using a template from configuration"
24
+ c.arg_name 'TEMPLATE_KEY'
25
+ c.flag [:config_template], type: TemplateName, default_value: 'default'
26
+
27
+ c.desc 'Override output format with a template string containing %placeholders'
28
+ c.arg_name 'TEMPLATE_STRING'
29
+ c.flag [:template]
30
+
31
+ c.desc 'Show time intervals on @done tasks'
32
+ c.switch %i[t times], default_value: true, negatable: true
33
+
34
+ c.desc 'Show elapsed time on entries without @done tag'
35
+ c.switch [:duration]
36
+
37
+ c.desc 'Show intervals with totals at the end of output'
38
+ c.switch [:totals], default_value: false, negatable: false
39
+
40
+ c.desc 'Sort tags by (name|time)'
41
+ default = @settings['tag_sort'].normalize_tag_sort || :name
42
+ c.arg_name 'KEY'
43
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
44
+
45
+ c.desc 'Only show items with recorded time intervals'
46
+ c.switch [:only_timed], default_value: false, negatable: false
47
+
48
+ # c.desc '[DEPRECATED] Use alternative fuzzy matching for search string'
49
+ # c.switch [:fuzzy], default_value: false, negatable: false
50
+
51
+ c.desc 'Force exact string matching (case sensitive)'
52
+ c.switch %i[x exact], default_value: @config.exact_match?, negatable: @config.exact_match?
53
+
54
+ c.desc 'Show items that *don\'t* match search string'
55
+ c.switch [:not], default_value: false, negatable: false
56
+
57
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
58
+ c.arg_name 'TYPE'
59
+ c.flag [:case], must_match: REGEX_CASE,
60
+ default_value: @settings.dig('search', 'case').normalize_case,
61
+ type: CaseSymbol
62
+
63
+ c.desc "Highlight search matches in output. Only affects command line output"
64
+ c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
65
+
66
+ c.desc "Edit matching entries with #{Doing::Util.default_editor}"
67
+ c.switch %i[e editor], negatable: false, default_value: false
68
+
69
+ c.desc "Delete matching entries"
70
+ c.switch %i[d delete], negatable: false, default_value: false
71
+
72
+ c.desc 'Display an interactive menu of results to perform further operations'
73
+ c.switch %i[i interactive], default_value: false, negatable: false
74
+
75
+ c.desc 'Perform a tag value query ("@done > two hours ago" or "@progress < 50"). May be used multiple times, combined with --bool'
76
+ c.arg_name 'QUERY'
77
+ c.flag [:val], multiple: true, must_match: REGEX_VALUE_QUERY
78
+
79
+ c.desc 'Combine multiple tags or value queries using AND, OR, or NOT'
80
+ c.arg_name 'BOOLEAN'
81
+ c.flag [:bool], must_match: REGEX_BOOL,
82
+ default_value: :pattern,
83
+ type: BooleanSymbol
84
+
85
+ add_options(:date_filter, c)
86
+
87
+ c.action do |_global_options, options, args|
88
+ options[:fuzzy] = false
89
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
90
+
91
+ template = @settings['templates'][options[:config_template]].deep_merge(@settings)
92
+ tags_color = template.key?('tags_color') ? template['tags_color'] : nil
93
+
94
+ section = @wwid.guess_section(options[:section]) if options[:section]
95
+
96
+ search = args.join(' ')
97
+ search.sub!(/^'?/, "'") if options[:exact]
98
+
99
+ options[:times] = true if options[:totals]
100
+ options[:sort_tags] = options[:tag_sort]
101
+ options[:highlight] = true
102
+ options[:search] = search
103
+ options[:section] = section
104
+ options[:tags_color] = tags_color
105
+
106
+ Doing::Pager.page @wwid.list_section(options)
107
+ end
108
+ end
@@ -0,0 +1,71 @@
1
+ # @@import
2
+ desc 'Import entries from an external source'
3
+ long_desc "Imports entries from other sources. Available plugins: #{Doing::Plugins.plugin_names(type: :import, separator: ', ')}"
4
+ arg_name 'PATH'
5
+ command :import do |c|
6
+ c.example 'doing import --type timing "~/Desktop/All Activities.json"', desc: 'Import a Timing.app JSON report'
7
+ c.example 'doing import --type doing --tag imported --no-autotag ~/doing_backup.md', desc: 'Import an Doing archive, tag all entries with @imported, skip autotagging'
8
+ c.example 'doing import --type doing --from "10/1 to 10/15" ~/doing_backup.md', desc: 'Import a Doing archive, only importing entries between two dates'
9
+
10
+ c.desc "Import type (#{Doing::Plugins.plugin_names(type: :import)})"
11
+ c.arg_name 'TYPE'
12
+ c.flag :type, default_value: 'doing'
13
+
14
+ c.desc 'Import items that *don\'t* match search/tag/date filters'
15
+ c.switch [:not], default_value: false, negatable: false
16
+
17
+ c.desc 'Only import items with recorded time intervals'
18
+ c.switch [:only_timed], default_value: false, negatable: false
19
+
20
+ c.desc 'Target section'
21
+ c.arg_name 'NAME'
22
+ c.flag %i[s section]
23
+
24
+ c.desc 'Tag all imported entries'
25
+ c.arg_name 'TAGS'
26
+ c.flag %i[t tag]
27
+
28
+ c.desc 'Autotag entries'
29
+ c.switch :autotag, negatable: true, default_value: true
30
+
31
+ c.desc 'Prefix entries with'
32
+ c.arg_name 'PREFIX'
33
+ c.flag :prefix
34
+
35
+ c.desc 'Allow entries that overlap existing times'
36
+ c.switch [:overlap], negatable: true
37
+
38
+ add_options(:search, c)
39
+ add_options(:date_filter, c)
40
+
41
+ c.action do |_global_options, options, args|
42
+ options[:fuzzy] = false
43
+ if options[:section]
44
+ options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first
45
+ end
46
+
47
+ if options[:search]
48
+ search = options[:search]
49
+ search.sub!(/^'?/, "'") if options[:exact]
50
+ options[:search] = search
51
+ end
52
+
53
+ if options[:from]
54
+ options[:date_filter] = options[:from]
55
+
56
+ raise InvalidTimeExpression, 'Unrecognized date string' unless options[:date_filter][0]
57
+ elsif options[:before] || options[:after]
58
+ options[:date_filter] = [nil, nil]
59
+ options[:date_filter][1] = options[:before] || Time.now + (1 << 64)
60
+ options[:date_filter][0] = options[:after] || Time.now - (1 << 64)
61
+ end
62
+
63
+ if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
64
+ options[:no_overlap] = !options[:overlap]
65
+ @wwid.import(args, options)
66
+ @wwid.write(@wwid.doing_file)
67
+ else
68
+ raise InvalidPluginType, "Invalid import type: #{options[:type]}"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,17 @@
1
+ # @@install_fzf
2
+ command :install_fzf do |c|
3
+ c.desc 'Force reinstall'
4
+ c.switch %i[r reinstall], default_value: false
5
+
6
+ c.desc 'Uninstall'
7
+ c.switch %i[u uninstall], default_value: false, negatable: false
8
+
9
+ c.action do |g, o, a|
10
+ if o[:uninstall]
11
+ Doing::Prompt.uninstall_fzf
12
+ else
13
+ Doing.logger.warn('fzf:', 'force reinstall') if o[:reinstall]
14
+ res = Doing::Prompt.install_fzf(force: o[:reinstall])
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,81 @@
1
+ # @@last
2
+ desc 'Show the last entry, optionally edit'
3
+ long_desc 'Shows the last entry. Using --search and --tag filters, you can view/edit the last entry matching a filter,
4
+ allowing `doing last` to target historical entries.'
5
+ command :last do |c|
6
+ c.example 'doing last', desc: 'Show the most recent entry in all sections'
7
+ c.example 'doing last -s Later', desc: 'Show the most recent entry in the Later section'
8
+ c.example 'doing last --tag project1,work --bool AND', desc: 'Show most recent entry tagged @project1 and @work'
9
+ c.example 'doing last --search "side hustle"', desc: 'Show most recent entry containing "side hustle" (fuzzy matching)'
10
+ c.example 'doing last --search "\'side hustle"', desc: 'Show most recent entry containing "side hustle" (exact match)'
11
+ c.example 'doing last --edit', desc: 'Open the most recent entry in an editor for modifications'
12
+ c.example 'doing last --search "\'side hustle" --edit', desc: 'Open most recent entry containing "side hustle" (exact match) in editor'
13
+
14
+ c.desc 'Specify a section'
15
+ c.arg_name 'NAME'
16
+ c.flag %i[s section], default_value: 'All'
17
+
18
+ c.desc "Edit entry with #{Doing::Util.default_editor}"
19
+ c.switch %i[e editor], negatable: false, default_value: false
20
+
21
+ c.desc "Delete the last entry"
22
+ c.switch %i[d delete], negatable: false, default_value: false
23
+
24
+ c.desc "Output using a template from configuration"
25
+ c.arg_name 'TEMPLATE_KEY'
26
+ c.flag [:config_template], type: TemplateName, default_value: 'last'
27
+
28
+ c.desc 'Override output format with a template string containing %placeholders'
29
+ c.arg_name 'TEMPLATE_STRING'
30
+ c.flag [:template]
31
+
32
+ c.desc "Highlight search matches in output. Only affects command line output"
33
+ c.switch %i[h hilite], default_value: @settings.dig('search', 'highlight')
34
+
35
+ c.desc 'Show elapsed time if entry is not tagged @done'
36
+ c.switch [:duration]
37
+
38
+ add_options(:search, c)
39
+ add_options(:tag_filter, c)
40
+
41
+ c.action do |global_options, options, _args|
42
+ options[:fuzzy] = false
43
+ raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
44
+
45
+ options[:tag] ||= []
46
+
47
+ options[:search] = options[:search].sub(/^'?/, "'") if options[:search] && options[:exact]
48
+
49
+ if options[:editor]
50
+ @wwid.edit_last(section: options[:section],
51
+ options: {
52
+ search: options[:search],
53
+ fuzzy: options[:fuzzy],
54
+ case: options[:case],
55
+ tag: options[:tag],
56
+ tag_bool: options[:bool],
57
+ not: options[:not],
58
+ val: options[:val],
59
+ bool: options[:bool]
60
+ })
61
+ else
62
+ last = @wwid.last(times: true, section: options[:section],
63
+ options: {
64
+ config_template: options[:config_template],
65
+ template: options[:template],
66
+ duration: options[:duration],
67
+ search: options[:search],
68
+ fuzzy: options[:fuzzy],
69
+ case: options[:case],
70
+ hilite: options[:hilite],
71
+ negate: options[:not],
72
+ tag: options[:tag],
73
+ tag_bool: options[:bool],
74
+ delete: options[:delete],
75
+ bool: options[:bool],
76
+ val: options[:val]
77
+ })
78
+ Doing::Pager::page last.strip if last
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,76 @@
1
+ # @@meanwhile
2
+ desc 'Finish any running @meanwhile tasks and optionally create a new one'
3
+ long_desc 'The @meanwhile tag allows you to have long-running entries that encompass smaller entries.
4
+ This command makes it easy to start and stop these overarching entries. Just run `doing meanwhile Starting work on this
5
+ big project` to start a @meanwhile entry, add other entries as you work on the project, then use `doing meanwhile` by
6
+ itself to mark the entry as @done.'
7
+ arg_name 'ENTRY', optional: true
8
+ command :meanwhile do |c|
9
+ c.example 'doing meanwhile "Long task that will have others after it before it\'s done"', desc: 'Add a new long-running entry, completing any current @meanwhile entry'
10
+ c.example 'doing meanwhile', desc: 'Finish any open @meanwhile entry'
11
+ c.example 'doing meanwhile --archive', desc: 'Finish any open @meanwhile entry and archive it'
12
+ c.example 'doing meanwhile --back 2h "Something I\'ve been working on for a while', desc: 'Add a @meanwhile entry with a start date 2 hours ago'
13
+
14
+ c.desc 'Section'
15
+ c.arg_name 'NAME'
16
+ c.flag %i[s section]
17
+
18
+ c.desc 'Archive previous @meanwhile entry'
19
+ c.switch %i[a archive], negatable: false, default_value: false
20
+
21
+ add_options(:add_entry, c)
22
+
23
+ c.action do |_global_options, options, args|
24
+ @wwid.auto_tag = !options[:noauto]
25
+
26
+ if options[:back]
27
+ date = options[:back]
28
+
29
+ raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
30
+ else
31
+ date = Time.now
32
+ end
33
+
34
+ if options[:section]
35
+ section = @wwid.guess_section(options[:section]) || options[:section].cap_first
36
+ else
37
+ section = @settings['current_section']
38
+ end
39
+ input = ''
40
+
41
+ ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : []
42
+
43
+ if options[:editor]
44
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
45
+ input += date.strftime('%F %R | ')
46
+ input += args.join(' ') unless args.empty?
47
+ input += "\n#{options[:note]}" if options[:note]
48
+ input += "\n#{ask_note}" unless ask_note.good?
49
+
50
+ input = @wwid.fork_editor(input).strip
51
+ elsif !args.empty?
52
+ input = args.join(' ')
53
+ elsif $stdin.stat.size.positive?
54
+ input = $stdin.read.strip
55
+ end
56
+
57
+ if input.good?
58
+ d, input, note = @wwid.format_input(input)
59
+ unless d.nil?
60
+ Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
61
+ date = d
62
+ end
63
+ else
64
+ input = nil
65
+ note = []
66
+ end
67
+
68
+ unless options[:editor]
69
+ note.add(options[:note]) if options[:note]
70
+ note.add(ask_note) if ask_note.good?
71
+ end
72
+
73
+ @wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:archive], note: note })
74
+ @wwid.write(@wwid.doing_file)
75
+ end
76
+ end
@@ -0,0 +1,91 @@
1
+ # @@note
2
+ desc 'Add a note to the last entry'
3
+ long_desc %(
4
+ If -r is provided with no other arguments, the last note is removed.
5
+ If new content is specified through arguments or STDIN, any previous
6
+ note will be replaced with the new one.
7
+
8
+ Use -e to load the last entry in a text editor where you can append a note.
9
+ )
10
+ arg_name 'NOTE_TEXT', optional: true
11
+ command :note do |c|
12
+ c.example 'doing note', desc: 'Open the last entry in $EDITOR to append a note'
13
+ c.example 'doing note "Just a quick annotation"', desc: 'Add a quick note to the last entry'
14
+ c.example 'doing note --tag done "Keeping it real or something"', desc: 'Add a note to the last item tagged @done'
15
+ c.example 'doing note --search "late night" -e', desc: 'Open $EDITOR to add a note to the last item containing "late night" (fuzzy matched)'
16
+
17
+ c.desc 'Section'
18
+ c.arg_name 'NAME'
19
+ c.flag %i[s section], default_value: 'All'
20
+
21
+ c.desc "Edit entry with #{Doing::Util.default_editor}"
22
+ c.switch %i[e editor], negatable: false, default_value: false
23
+
24
+ c.desc "Replace/Remove last entry's note (default append)"
25
+ c.switch %i[r remove], negatable: false, default_value: false
26
+
27
+ c.desc 'Select item for new note from a menu of matching entries'
28
+ c.switch %i[i interactive], negatable: false, default_value: false
29
+
30
+ c.desc 'Prompt for note via multi-line input'
31
+ c.switch %i[ask], negatable: false, default_value: false
32
+
33
+ add_options(:search, c)
34
+ add_options(:tag_filter, c)
35
+
36
+ c.action do |_global_options, options, args|
37
+ options[:fuzzy] = false
38
+ options[:section] = @wwid.guess_section(options[:section]) || options[:section].cap_first if options[:section]
39
+ options[:tag_bool] = options[:bool]
40
+
41
+ if options[:search]
42
+ search = options[:search]
43
+ search.sub!(/^'?/, "'") if options[:exact]
44
+ options[:search] = search
45
+ end
46
+
47
+ last_entry = @wwid.last_entry(options)
48
+ old_entry = last_entry.clone
49
+
50
+ raise NoResults, 'No entry matching parameters was found.' unless last_entry
51
+
52
+ last_note = last_entry.note || Doing::Note.new
53
+ new_note = Doing::Note.new
54
+
55
+ new_note.add($stdin.read.strip) if $stdin.stat.size.positive?
56
+ new_note.add(args.join(' ')) unless args.empty?
57
+
58
+ if options[:editor]
59
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
60
+
61
+ input = if options[:remove]
62
+ Doing::Note.new
63
+ else
64
+ last_entry.note || Doing::Note.new
65
+ end
66
+
67
+ input.add(new_note)
68
+
69
+ new_note = Doing::Note.new(@wwid.fork_editor(input.strip_lines.join("\n"), message: nil).strip)
70
+ options[:remove] = true
71
+ end
72
+
73
+ if (new_note.empty? && !options[:remove]) || options[:ask]
74
+ $stderr.puts last_note if last_note.good?
75
+ $stderr.puts new_note if new_note.good?
76
+ new_note.add(Doing::Prompt.read_lines(prompt: 'Add a note'))
77
+ end
78
+
79
+ raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || new_note.good?
80
+
81
+ if last_note.equal?(new_note)
82
+ Doing.logger.debug('Skipped:', 'No note change')
83
+ else
84
+ last_note.add(new_note, replace: options[:remove])
85
+ Doing.logger.info('Entry updated:', last_entry.title)
86
+ Doing::Hooks.trigger :post_entry_updated, @wwid, last_entry, old_entry
87
+ end
88
+ # new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
89
+ @wwid.write(@wwid.doing_file)
90
+ end
91
+ end
@@ -0,0 +1,145 @@
1
+ # @@now @@next
2
+ desc 'Add an entry'
3
+ long_desc %(Record what you're starting now, or backdate the start time using natural language.
4
+
5
+ A parenthetical at the end of the entry will be converted to a note.
6
+
7
+ Run without arguments to create a new entry interactively.
8
+
9
+ Run with --editor to create a new entry using #{Doing::Util.default_editor}.)
10
+ arg_name 'ENTRY'
11
+ command %i[now next] do |c|
12
+ c.example 'doing now', desc: 'Create a new entry with interactive prompts'
13
+ c.example 'doing now -e', desc: "Open #{Doing::Util.default_editor} to input an entry and optional note"
14
+ c.example 'doing now working on a new project', desc: 'Add a new entry at the current time'
15
+ c.example 'doing now debugging @project2', desc: 'Add an entry with a tag'
16
+ c.example 'doing now adding an entry (with a note)', desc: 'Parenthetical at end is converted to note'
17
+ c.example 'doing now --back 2pm A thing I started at 2:00 and am still doing...', desc: 'Backdate an entry'
18
+
19
+ c.desc 'Section'
20
+ c.arg_name 'NAME'
21
+ c.flag %i[s section]
22
+
23
+ c.desc %(
24
+ Set a start and optionally end time as a date range ("from 1pm to 2:30pm").
25
+ If an end time is provided, a dated @done tag will be added
26
+ )
27
+ c.arg_name 'TIME_RANGE'
28
+ c.flag [:from], type: DateRangeString
29
+
30
+ c.desc 'Timed entry, marks last entry in section as @done'
31
+ c.switch %i[f finish_last], negatable: false, default_value: false
32
+
33
+ add_options(:add_entry, c)
34
+
35
+ # c.desc "Edit entry with specified app"
36
+ # c.arg_name 'editor_app'
37
+ # # c.flag [:a, :app]
38
+
39
+ c.action do |_global_options, options, args|
40
+ @wwid.auto_tag = !options[:noauto]
41
+
42
+ raise InvalidArgument, '--back and --from cannot be used together' if options[:back] && options[:from]
43
+
44
+ if options[:back]
45
+ date = options[:back]
46
+ elsif options[:from]
47
+ date, finish_date = options[:from]
48
+ options[:done] = finish_date
49
+ else
50
+ date = Time.now
51
+ end
52
+ raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
53
+
54
+ section = if options[:section]
55
+ @wwid.guess_section(options[:section]) || options[:section].cap_first
56
+ else
57
+ @settings['current_section']
58
+ end
59
+
60
+ ask_note = if options[:ask] && !options[:editor] && args.count.positive?
61
+ Doing::Prompt.read_lines(prompt: 'Add a note')
62
+ else
63
+ ''
64
+ end
65
+
66
+ if options[:editor]
67
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
68
+
69
+ input = date.strftime('%F %R | ')
70
+ input += args.join(' ') unless args.empty?
71
+ input += " @done(#{options[:done].strftime('%F %R')})" if options[:done]
72
+ input += "\n#{options[:note]}" if options[:note]
73
+ input += "\n#{ask_note}" if ask_note.good?
74
+ input = @wwid.fork_editor(input).strip
75
+
76
+ d, title, note = @wwid.format_input(input)
77
+ raise EmptyInput, 'No content' unless title.good?
78
+
79
+ if ask_note.empty? && options[:ask]
80
+ ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
81
+ note.add(ask_note) if ask_note.good?
82
+ end
83
+
84
+ date = d.nil? ? date : d
85
+ @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
86
+ @wwid.write(@wwid.doing_file)
87
+ elsif args.length.positive?
88
+ d, title, note = @wwid.format_input(args.join(' '))
89
+ date = d.nil? ? date : d
90
+ note.add(options[:note]) if options[:note]
91
+ note.add(ask_note) if ask_note.good?
92
+ entry = @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
93
+ if options[:done] && entry.should_finish?
94
+ if entry.should_time?
95
+ entry.tag('done', value: options[:done])
96
+ else
97
+ entry.tag('done')
98
+ end
99
+ end
100
+ @wwid.write(@wwid.doing_file)
101
+ elsif $stdin.stat.size.positive?
102
+ input = $stdin.read.strip
103
+ d, title, note = @wwid.format_input(input)
104
+ unless d.nil?
105
+ Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
106
+ date = d
107
+ end
108
+ note.add(options[:note]) if options[:note]
109
+ if ask_note.empty? && options[:ask]
110
+ ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
111
+ note.add(ask_note) if ask_note.good?
112
+ end
113
+ entry = @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
114
+ if options[:done] && entry.should_finish?
115
+ if entry.should_time?
116
+ entry.tag('done', value: options[:done])
117
+ else
118
+ entry.tag('done')
119
+ end
120
+ end
121
+ @wwid.write(@wwid.doing_file)
122
+ else
123
+ tags = @wwid.all_tags(@wwid.content)
124
+ $stderr.puts Doing::Color.boldgreen("Add a new entry. Tab will autocomplete known tags. Ctrl-c to cancel.")
125
+ title = Doing::Prompt.read_line(prompt: 'Entry content', completions: tags)
126
+ raise EmptyInput, 'You must provide content when creating a new entry' unless title.good?
127
+
128
+ note = Doing::Note.new
129
+ note.add(options[:note]) if options[:note]
130
+ res = Doing::Prompt.yn('Add a note', default_response: false)
131
+ ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
132
+ note.add(ask_note)
133
+
134
+ entry = @wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
135
+ if options[:done] && entry.should_finish?
136
+ if entry.should_time?
137
+ entry.tag('done', value: options[:done])
138
+ else
139
+ entry.tag('done')
140
+ end
141
+ end
142
+ @wwid.write(@wwid.doing_file)
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,65 @@
1
+ # @@on
2
+ desc 'List entries for a date'
3
+ long_desc %(Date argument can be natural language. "thursday" would be interpreted as "last thursday,"
4
+ and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates,
5
+ it will create a range.)
6
+ arg_name 'DATE_STRING'
7
+ command :on do |c|
8
+ c.example 'doing on friday', desc: 'List entries between 12am and 11:59PM last Friday'
9
+ c.example 'doing on 12/21/2020', desc: 'List entries from Dec 21, 2020'
10
+ c.example 'doing on "3d to 1d"', desc: 'List entries added between 3 days ago and 1 day ago'
11
+
12
+ c.desc 'Section'
13
+ c.arg_name 'NAME'
14
+ c.flag %i[s section], default_value: 'All'
15
+
16
+ c.desc 'Show time intervals on @done tasks'
17
+ c.switch %i[t times], default_value: true, negatable: true
18
+
19
+ c.desc 'Show elapsed time on entries without @done tag'
20
+ c.switch [:duration]
21
+
22
+ c.desc 'Show time totals at the end of output'
23
+ c.switch [:totals], default_value: false, negatable: false
24
+
25
+ c.desc 'Sort tags by (name|time)'
26
+ default = @settings['tag_sort'].normalize_tag_sort || :name
27
+ c.arg_name 'KEY'
28
+ c.flag [:tag_sort], must_match: REGEX_TAG_SORT, default_value: default, type: TagSortSymbol
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: 'default'
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.action do |_global_options, options, args|
43
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
44
+
45
+ raise MissingArgument, 'Missing date argument' if args.empty?
46
+
47
+ date_string = args.join(' ').strip
48
+ if date_string =~ /^tod(?:ay)?/i
49
+ date_string = 'today to tomorrow 12am'
50
+ end
51
+ start, finish = date_string.split_date_range
52
+
53
+ raise InvalidTimeExpression, 'Unrecognized date string' unless start
54
+
55
+ message = "date interpreted as #{start}"
56
+ message += " to #{finish}" if finish
57
+ Doing.logger.debug('Interpreter:', message)
58
+
59
+ options[:times] = true if options[:totals]
60
+ options[:sort_tags] = options[:tag_sort]
61
+
62
+ Doing::Pager.page @wwid.list_date([start, finish], options[:section], options[:times], options[:output],
63
+ { template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
64
+ end
65
+ end