doing 2.1.26 → 2.1.30

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +15 -20
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +52 -0
  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 +2 -1
  11. data/README.md +1 -1
  12. data/Rakefile +2 -3
  13. data/bin/commands/add_section.rb +2 -0
  14. data/bin/commands/again.rb +23 -65
  15. data/bin/commands/archive.rb +20 -61
  16. data/bin/commands/cancel.rb +27 -69
  17. data/bin/commands/changes.rb +53 -12
  18. data/bin/commands/colors.rb +4 -2
  19. data/bin/commands/commands.rb +4 -2
  20. data/bin/commands/commands_accepting.rb +62 -11
  21. data/bin/commands/completion.rb +10 -7
  22. data/bin/commands/config.rb +8 -8
  23. data/bin/commands/done.rb +3 -17
  24. data/bin/commands/finish.rb +7 -30
  25. data/bin/commands/flag.rb +15 -51
  26. data/bin/commands/grep.rb +12 -28
  27. data/bin/commands/import.rb +3 -33
  28. data/bin/commands/last.rb +3 -36
  29. data/bin/commands/meanwhile.rb +3 -13
  30. data/bin/commands/note.rb +13 -52
  31. data/bin/commands/now.rb +15 -21
  32. data/bin/commands/on.rb +3 -4
  33. data/bin/commands/open.rb +3 -3
  34. data/bin/commands/recent.rb +3 -4
  35. data/bin/commands/redo.rb +6 -2
  36. data/bin/commands/reset.rb +19 -52
  37. data/bin/commands/rotate.rb +5 -36
  38. data/bin/commands/select.rb +23 -41
  39. data/bin/commands/show.rb +28 -74
  40. data/bin/commands/since.rb +3 -4
  41. data/bin/commands/tag.rb +4 -34
  42. data/bin/commands/tags.rb +5 -32
  43. data/bin/commands/today.rb +3 -4
  44. data/bin/commands/view.rb +36 -73
  45. data/bin/commands/yesterday.rb +4 -5
  46. data/bin/doing +150 -13
  47. data/docs/doc/Array.html +3 -502
  48. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  49. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  50. data/docs/doc/BooleanTermParser/Query.html +1 -1
  51. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  52. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  53. data/docs/doc/BooleanTermParser.html +1 -1
  54. data/docs/doc/Doing/Color.html +62 -56
  55. data/docs/doc/Doing/Completion.html +1 -1
  56. data/docs/doc/Doing/Configuration.html +35 -1
  57. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  58. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  59. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  60. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  61. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  62. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  63. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  64. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  65. data/docs/doc/Doing/Errors.html +1 -1
  66. data/docs/doc/Doing/Hooks.html +1 -1
  67. data/docs/doc/Doing/Item.html +1 -1
  68. data/docs/doc/Doing/Items.html +2 -2
  69. data/docs/doc/Doing/LogAdapter.html +1 -1
  70. data/docs/doc/Doing/Note.html +2 -2
  71. data/docs/doc/Doing/Pager.html +1 -1
  72. data/docs/doc/Doing/Plugins.html +1 -1
  73. data/docs/doc/Doing/Prompt.html +1 -1
  74. data/docs/doc/Doing/Section.html +1 -1
  75. data/docs/doc/Doing/TemplateString.html +2 -2
  76. data/docs/doc/Doing/Types.html +41 -1
  77. data/docs/doc/Doing/Util/Backup.html +1 -1
  78. data/docs/doc/Doing/Util.html +1 -1
  79. data/docs/doc/Doing/WWID.html +10 -10
  80. data/docs/doc/Doing.html +3 -3
  81. data/docs/doc/FalseClass.html +35 -1
  82. data/docs/doc/GLI/Commands/Help.html +1 -1
  83. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  84. data/docs/doc/GLI/Commands.html +1 -1
  85. data/docs/doc/GLI.html +1 -1
  86. data/docs/doc/Hash.html +1 -1
  87. data/docs/doc/Object.html +1 -1
  88. data/docs/doc/PhraseParser/Operator.html +1 -1
  89. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  90. data/docs/doc/PhraseParser/Query.html +1 -1
  91. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  92. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  93. data/docs/doc/PhraseParser/TermClause.html +1 -1
  94. data/docs/doc/PhraseParser.html +1 -1
  95. data/docs/doc/Status.html +1 -1
  96. data/docs/doc/String.html +287 -3155
  97. data/docs/doc/Symbol.html +40 -6
  98. data/docs/doc/Time.html +1 -1
  99. data/docs/doc/TrueClass.html +35 -1
  100. data/docs/doc/_index.html +5 -10
  101. data/docs/doc/class_list.html +1 -1
  102. data/docs/doc/file.README.html +2 -2
  103. data/docs/doc/index.html +2 -2
  104. data/docs/doc/method_list.html +278 -678
  105. data/docs/doc/top-level-namespace.html +2 -2
  106. data/doing.gemspec +1 -0
  107. data/doing.rdoc +297 -206
  108. data/lib/completion/_doing.zsh +32 -32
  109. data/lib/completion/doing.bash +30 -30
  110. data/lib/completion/doing.fish +87 -77
  111. data/lib/doing/array/array.rb +4 -0
  112. data/lib/doing/array/nested_hash.rb +17 -0
  113. data/lib/doing/{array.rb → array/tags.rb} +7 -25
  114. data/lib/doing/changelog/change.rb +26 -11
  115. data/lib/doing/changelog/changes.rb +37 -8
  116. data/lib/doing/changelog/version.rb +11 -3
  117. data/lib/doing/{array_chronify.rb → chronify/array.rb} +0 -0
  118. data/lib/doing/chronify/chronify.rb +5 -0
  119. data/lib/doing/{numeric_chronify.rb → chronify/numeric.rb} +0 -0
  120. data/lib/doing/{string_chronify.rb → chronify/string.rb} +0 -0
  121. data/lib/doing/colors.rb +115 -54
  122. data/lib/doing/completion/zsh_completion.rb +5 -0
  123. data/lib/doing/configuration.rb +9 -5
  124. data/lib/doing/good.rb +8 -0
  125. data/lib/doing/help_monkey_patch.rb +6 -5
  126. data/lib/doing/item.rb +5 -5
  127. data/lib/doing/items.rb +2 -2
  128. data/lib/doing/log_adapter.rb +35 -2
  129. data/lib/doing/normalize.rb +188 -0
  130. data/lib/doing/plugins/export/dayone_export.rb +1 -1
  131. data/lib/doing/plugins/export/html_export.rb +1 -1
  132. data/lib/doing/plugins/export/json_export.rb +1 -1
  133. data/lib/doing/plugins/export/markdown_export.rb +1 -1
  134. data/lib/doing/plugins/export/template_export.rb +3 -1
  135. data/lib/doing/prompt.rb +1 -3
  136. data/lib/doing/section.rb +1 -1
  137. data/lib/doing/string/highlight.rb +95 -0
  138. data/lib/doing/string/query.rb +129 -0
  139. data/lib/doing/string/string.rb +12 -0
  140. data/lib/doing/string/tags.rb +164 -0
  141. data/lib/doing/string/transform.rb +168 -0
  142. data/lib/doing/string/truncate.rb +75 -0
  143. data/lib/doing/string/url.rb +82 -0
  144. data/lib/doing/template_string.rb +0 -22
  145. data/lib/doing/types.rb +8 -0
  146. data/lib/doing/util.rb +13 -9
  147. data/lib/doing/version.rb +1 -1
  148. data/lib/doing/wwid.rb +54 -36
  149. data/lib/doing.rb +5 -6
  150. data/lib/examples/plugins/wiki_export/wiki_export.rb +1 -1
  151. data/lib/helpers/threaded_tests.rb +15 -2
  152. data/scripts/deploy.rb +107 -0
  153. data/scripts/runtests.sh +4 -0
  154. metadata +39 -8
  155. data/lib/doing/string.rb +0 -765
  156. data/lib/doing/symbol.rb +0 -28
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doing
4
+ ##
5
+ ## String helpers
6
+ ##
7
+ class ::String
8
+ # Compress multiple spaces to single space
9
+ def compress
10
+ gsub(/ +/, ' ').strip
11
+ end
12
+
13
+ def compress!
14
+ replace compress
15
+ end
16
+
17
+ def simple_wrap(width)
18
+ str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }
19
+ words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
20
+ out = []
21
+ line = []
22
+
23
+ words.each do |word|
24
+ if word.uncolor.length >= width
25
+ chars = word.uncolor.split('')
26
+ out << chars.slice!(0, width - 1).join('') while chars.count >= width
27
+ line << chars.join('')
28
+ next
29
+ elsif line.join(' ').uncolor.length + word.uncolor.length + 1 > width
30
+ out.push(line.join(' '))
31
+ line.clear
32
+ end
33
+
34
+ line << word.uncolor
35
+ end
36
+ out.push(line.join(' '))
37
+ out.join("\n")
38
+ end
39
+
40
+ ##
41
+ ## Wrap string at word breaks, respecting tags
42
+ ##
43
+ ## @param len [Integer] The length
44
+ ## @param offset [Integer] (Optional) The width to pad each subsequent line
45
+ ## @param prefix [String] (Optional) A prefix to add to each line
46
+ ##
47
+ def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', color: '', after: '', reset: '', pad_first: false)
48
+ last_color = color.empty? ? '' : after.last_color
49
+ note_rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?note/
50
+ note = ''
51
+ after = after.dup if after.frozen?
52
+ after.sub!(note_rx) do
53
+ note = Regexp.last_match(0)
54
+ ''
55
+ end
56
+
57
+ left_pad = ' ' * offset
58
+ left_pad += indent
59
+
60
+ # return "#{left_pad}#{prefix}#{color}#{self}#{last_color} #{note}" unless len.positive?
61
+
62
+ # Don't break inside of tag values
63
+ str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }.gsub(/\n/, ' ')
64
+
65
+ words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
66
+ out = []
67
+ line = []
68
+
69
+ words.each do |word|
70
+ if word.uncolor.length >= len
71
+ chars = word.uncolor.split('')
72
+ out << chars.slice!(0, len - 1).join('') while chars.count >= len
73
+ line << chars.join('')
74
+ next
75
+ elsif line.join(' ').uncolor.length + word.uncolor.length + 1 > len
76
+ out.push(line.join(' '))
77
+ line.clear
78
+ end
79
+
80
+ line << word.uncolor
81
+ end
82
+ out.push(line.join(' '))
83
+
84
+ last_color = ''
85
+ out[0] = format("%-#{pad}s%s%s", out[0], last_color, after)
86
+
87
+ out.map.with_index { |l, idx|
88
+ if !pad_first && idx == 0
89
+ "#{color}#{prefix}#{l}#{last_color}"
90
+ else
91
+ "#{left_pad}#{color}#{prefix}#{l}#{last_color}"
92
+ end
93
+ }.join("\n") + " #{note}".chomp
94
+ # res.join("\n").strip + last_color + " #{note}".chomp
95
+ end
96
+
97
+ ##
98
+ ## Capitalize on the first character on string
99
+ ##
100
+ ## @return Capitalized string
101
+ ##
102
+ def cap_first
103
+ sub(/^\w/) do |m|
104
+ m.upcase
105
+ end
106
+ end
107
+
108
+ ##
109
+ ## Pluralize a string based on quantity
110
+ ##
111
+ ## @param number [Integer] the quantity of the
112
+ ## object the string represents
113
+ ##
114
+ def to_p(number)
115
+ number == 1 ? self : "#{self}s"
116
+ end
117
+
118
+ ##
119
+ ## Convert a string value to an appropriate type. If
120
+ ## kind is not specified, '[one, two]' becomes an Array,
121
+ ## '1' becomes Integer, '1.5' becomes Float, 'true' or
122
+ ## 'yes' becomes TrueClass, 'false' or 'no' becomes
123
+ ## FalseClass.
124
+ ##
125
+ ## @param kind [String] specify string, array,
126
+ ## integer, float, symbol, or boolean
127
+ ## (falls back to string if value is
128
+ ## not recognized)
129
+ ## @return Converted object type
130
+ def set_type(kind = nil)
131
+ if kind
132
+ case kind.to_s
133
+ when /^a/i
134
+ gsub(/^\[ *| *\]$/, '').split(/ *, */)
135
+ when /^i/i
136
+ to_i
137
+ when /^(fa|tr)/i
138
+ to_bool
139
+ when /^f/i
140
+ to_f
141
+ when /^sy/i
142
+ sub(/^:/, '').to_sym
143
+ when /^b/i
144
+ self =~ /^(true|yes)$/ ? true : false
145
+ else
146
+ to_s
147
+ end
148
+ else
149
+ case self
150
+ when /(^\[.*?\]$| *, *)/
151
+ gsub(/^\[ *| *\]$/, '').split(/ *, */)
152
+ when /^[0-9]+$/
153
+ to_i
154
+ when /^[0-9]+\.[0-9]+$/
155
+ to_f
156
+ when /^:\w+/
157
+ sub(/^:/, '').to_sym
158
+ when /^(true|yes)$/i
159
+ true
160
+ when /^(false|no)$/i
161
+ false
162
+ else
163
+ to_s
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doing
4
+ ##
5
+ ## String truncation
6
+ ##
7
+ class ::String
8
+ ##
9
+ ## Truncate to nearest word
10
+ ##
11
+ ## @param len The length
12
+ ##
13
+ def trunc(len, ellipsis: '...')
14
+ return self if length <= len
15
+
16
+ total = 0
17
+ res = []
18
+
19
+ split(/ /).each do |word|
20
+ break if total + 1 + word.length > len
21
+
22
+ total += 1 + word.length
23
+ res.push(word)
24
+ end
25
+ res.join(' ') + ellipsis
26
+ end
27
+
28
+ def trunc!(len, ellipsis: '...')
29
+ replace trunc(len, ellipsis: ellipsis)
30
+ end
31
+
32
+ ##
33
+ ## Truncate from middle to end at nearest word
34
+ ##
35
+ ## @param len The length
36
+ ##
37
+ def truncend(len, ellipsis: '...')
38
+ return self if length <= len
39
+
40
+ total = 0
41
+ res = []
42
+
43
+ split(/ /).reverse.each do |word|
44
+ break if total + 1 + word.length > len
45
+
46
+ total += 1 + word.length
47
+ res.unshift(word)
48
+ end
49
+ ellipsis + res.join(' ')
50
+ end
51
+
52
+ def truncend!(len, ellipsis: '...')
53
+ replace truncend(len, ellipsis: ellipsis)
54
+ end
55
+
56
+ ##
57
+ ## Truncate string in the middle, separating at nearest word
58
+ ##
59
+ ## @param len The length
60
+ ## @param ellipsis The ellipsis
61
+ ##
62
+ def truncmiddle(len, ellipsis: '...')
63
+ return self if length <= len
64
+ len -= (ellipsis.length / 2).to_i
65
+ half = (len / 2).to_i
66
+ start = trunc(half, ellipsis: ellipsis)
67
+ finish = truncend(half, ellipsis: '')
68
+ start + finish
69
+ end
70
+
71
+ def truncmiddle!(len, ellipsis: '...')
72
+ replace truncmiddle(len, ellipsis: ellipsis)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doing
4
+ ##
5
+ ## URL linking and formatting
6
+ ##
7
+ class ::String
8
+ ##
9
+ ## Turn raw urls into HTML links
10
+ ##
11
+ ## @param opt [Hash] Additional Options
12
+ ##
13
+ ## @option opt [Symbol] :format can be :markdown or
14
+ ## :html (default)
15
+ ##
16
+ def link_urls(**opt)
17
+ fmt = opt.fetch(:format, :html)
18
+ return self unless fmt
19
+
20
+ str = dup
21
+
22
+ str = str.remove_self_links if fmt == :markdown
23
+
24
+ str.replace_qualified_urls(format: fmt).clean_unlinked_urls
25
+ end
26
+
27
+ ## @see #link_urls
28
+ def link_urls!(**opt)
29
+ fmt = opt.fetch(:format, :html)
30
+ replace link_urls(format: fmt)
31
+ end
32
+
33
+ # Remove <self-linked> formatting
34
+ def remove_self_links
35
+ gsub(/<(.*?)>/) do |match|
36
+ m = Regexp.last_match
37
+ if m[1] =~ /^https?:/
38
+ m[1]
39
+ else
40
+ match
41
+ end
42
+ end
43
+ end
44
+
45
+ # Replace qualified urls
46
+ def replace_qualified_urls(**options)
47
+ fmt = options.fetch(:format, :html)
48
+ gsub(%r{(?mi)(?x:
49
+ (?<!["'\[(\\])
50
+ (?<protocol>(?:http|https)://)
51
+ (?<domain>[\w\-]+(?:\.[\w\-]+)+)
52
+ (?<path>[\w\-.,@?^=%&;:/~+#]*[\w\-@^=%&;/~+#])?
53
+ )}) do |_match|
54
+ m = Regexp.last_match
55
+ url = "#{m['domain']}#{m['path']}"
56
+ proto = m['protocol'].nil? ? 'http://' : m['protocol']
57
+ case fmt
58
+ when :terminal
59
+ TTY::Link.link_to("#{proto}#{url}", "#{proto}#{url}")
60
+ when :html
61
+ %(<a href="#{proto}#{url}" title="Link to #{m['domain']}">[#{url}]</a>)
62
+ when :markdown
63
+ "[#{url}](#{proto}#{url})"
64
+ else
65
+ m[0]
66
+ end
67
+ end
68
+ end
69
+
70
+ # Clean up unlinked <urls>
71
+ def clean_unlinked_urls
72
+ gsub(/<(\w+:.*?)>/) do |match|
73
+ m = Regexp.last_match
74
+ if m[1] =~ /<a href/
75
+ match
76
+ else
77
+ %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -5,28 +5,6 @@ module Doing
5
5
  ## Template string formatting
6
6
  ##
7
7
  class TemplateString < String
8
- class ::String
9
- ##
10
- ## Extract the longest valid color from a string.
11
- ##
12
- ## Allows %colors to bleed into other text and still
13
- ## be recognized, e.g. %greensomething still finds
14
- ## %green.
15
- ##
16
- ## @return [String] a valid color name
17
- ## @api private
18
- def validate_color
19
- valid_color = nil
20
- compiled = ''
21
- split('').each do |char|
22
- compiled += char
23
- valid_color = compiled if Color.attributes.include?(compiled.to_sym)
24
- end
25
-
26
- valid_color
27
- end
28
- end
29
-
30
8
  attr_reader :original
31
9
 
32
10
  include Color
data/lib/doing/types.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Doing
4
4
  module Types
5
+ REGEX_CASE = /^[cis].*?$/i.freeze
6
+ REGEX_TAG_SORT = /^(?:n(?:ame)?|t(?:ime)?)$/i.freeze
5
7
  REGEX_BOOL = /^(?:and|all|any|or|not|none|p(?:at(?:tern)?)?)$/i.freeze
6
8
  REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i.freeze
7
9
  REGEX_VALUE_QUERY = /^(?:!)?@?(?:\S+) +(?:!?[<>=][=*]?|[$*^]=) +(?:.*?)$/.freeze
@@ -22,5 +24,11 @@ module Doing
22
24
  DateRangeString = Class.new(Array)
23
25
  DateRangeOptionalString = Class.new(Array)
24
26
  DateIntervalString = Class.new(DateTime)
27
+ BooleanSymbol = Class.new(Symbol)
28
+ CaseSymbol = Class.new(Symbol)
29
+ AgeSymbol = Class.new(String)
30
+ OrderSymbol = Class.new(Symbol)
31
+ TagSortSymbol = Class.new(Symbol)
32
+ MatchingSymbol = Class.new(Symbol)
25
33
  end
26
34
  end
data/lib/doing/util.rb CHANGED
@@ -170,29 +170,33 @@ module Doing
170
170
 
171
171
  if editor_config[editor_for]
172
172
  editor = editor_config[editor_for]
173
- # Doing.logger.debug('Editor:', "Using #{editor} from config 'editors->#{editor_for}'")
174
- return editor unless editor.good?
173
+ Doing.logger.debug('Editor:', "Using #{editor} from config 'editors.#{editor_for}' for #{editor_for}")
174
+ return editor if editor.good?
175
175
  end
176
176
 
177
177
  if editor_for != 'editor' && editor_config['default']
178
178
  editor = editor_config['default']
179
- # Doing.logger.debug('Editor:', "Using #{editor} from config: 'editors->default'")
180
- return editor unless editor.good?
179
+ Doing.logger.debug('Editor:', "Using #{editor} from config: 'editors.default' for #{editor_for}")
180
+ return editor if editor.good?
181
181
  end
182
182
 
183
183
  editor ||= ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR']
184
184
 
185
- unless editor.good?
186
- # Doing.logger.debug('Editor:', "Found editor in environment variables: #{editor}")
185
+ if editor.good?
186
+ Doing.logger.debug('Editor:', "Found editor in environment variables: #{editor} for #{editor_for}")
187
187
  return editor
188
188
  end
189
189
 
190
- Doing.logger.debug('ENV:', 'No EDITOR environment variable, testing available editors')
190
+ Doing.logger.debug('Editor:', 'No EDITOR environment variable, testing available editors')
191
191
  editors = %w[vim vi code subl mate mvim nano emacs]
192
192
  editors.each do |ed|
193
- return TTY::Which.which(ed) if TTY::Which.which(ed)
193
+ try = TTY::Which.which(ed)
194
+ if try
195
+ Doing.logger.debug('Editor:', "Using editor #{try} for #{editor_for}")
196
+ return try
197
+ end
194
198
 
195
- Doing.logger.debug('ENV:', "#{ed} not available")
199
+ Doing.logger.debug('Editor:', "#{ed} not available")
196
200
  end
197
201
 
198
202
  nil
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '2.1.26'
2
+ VERSION = '2.1.30'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -77,7 +77,7 @@ module Doing
77
77
  lines.each do |line|
78
78
  next if line =~ /^\s*$/
79
79
 
80
- if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
80
+ if line =~ /^(\S[\S ]+):( .*)?$/
81
81
  section = Regexp.last_match(1)
82
82
  @content.add_section(Section.new(section, original: line), log: false)
83
83
  elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
@@ -659,6 +659,7 @@ module Doing
659
659
  ## @option opt [Array] :val (nil) Array of tag value queries
660
660
  ##
661
661
  def filter_items(items = Items.new, opt: {})
662
+ logger.benchmark(:filter_items, :start)
662
663
  time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
663
664
 
664
665
  if items.nil? || items.empty?
@@ -810,6 +811,8 @@ module Doing
810
811
  output.concat(filtered_items.reverse.slice(0, count))
811
812
  end
812
813
 
814
+ logger.benchmark(:filter_items, :finish)
815
+
813
816
  output
814
817
  end
815
818
 
@@ -1211,6 +1214,7 @@ module Doing
1211
1214
  opt[:sequential] ||= false
1212
1215
  opt[:date] ||= false
1213
1216
  opt[:remove] ||= false
1217
+ opt[:update] ||= false
1214
1218
  opt[:autotag] ||= false
1215
1219
  opt[:back] ||= false
1216
1220
  opt[:unfinished] ||= false
@@ -1323,7 +1327,7 @@ module Doing
1323
1327
  else
1324
1328
  old_title = item.title.dup
1325
1329
  should_date = opt[:date] && item.should_time?
1326
- item.title.tag!('done', remove: true) if tag =~ /done/ && !should_date
1330
+ item.title.tag!('done', remove: true) if tag =~ /done/ && (!should_date || opt[:update])
1327
1331
  item.title.tag!(tag, value: should_date ? done_date.strftime('%F %R') : nil)
1328
1332
  added << tag if old_title != item.title
1329
1333
  end
@@ -1619,6 +1623,7 @@ module Doing
1619
1623
  ## @param opt [Hash] Additional Options
1620
1624
  ##
1621
1625
  def list_section(opt, items: Items.new)
1626
+ logger.benchmark(:list_section, :start)
1622
1627
  opt[:config_template] ||= 'default'
1623
1628
 
1624
1629
  tpl_cfg = @config.dig('templates', opt[:config_template])
@@ -1632,7 +1637,7 @@ module Doing
1632
1637
  cfg.deep_merge({
1633
1638
  'wrap_width' => @config['wrap_width'] || 0,
1634
1639
  'date_format' => @config['default_date_format'],
1635
- 'order' => @config['order'] || 'asc',
1640
+ 'order' => @config['order'] || :asc,
1636
1641
  'tags_color' => @config['tags_color'],
1637
1642
  'duration' => @config['duration'],
1638
1643
  'interval_format' => @config['interval_format']
@@ -1644,8 +1649,8 @@ module Doing
1644
1649
  opt[:age] ||= :newest
1645
1650
  opt[:age] = opt[:age].normalize_age
1646
1651
  opt[:format] ||= cfg['date_format']
1647
- opt[:order] ||= cfg['order'] || 'asc'
1648
- opt[:tag_order] ||= 'asc'
1652
+ opt[:order] ||= cfg['order'] || :asc
1653
+ opt[:tag_order] ||= :asc
1649
1654
  opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
1650
1655
  opt[:template] ||= cfg['template']
1651
1656
 
@@ -1671,7 +1676,7 @@ module Doing
1671
1676
 
1672
1677
  items = filter_items(items, opt: opt)
1673
1678
 
1674
- items.reverse! unless opt[:order] =~ /^d/i
1679
+ items.reverse! unless opt[:order].normalize_order == :desc
1675
1680
 
1676
1681
  if opt[:delete]
1677
1682
  delete_items(items, force: opt[:force])
@@ -1694,6 +1699,7 @@ module Doing
1694
1699
  opt[:output] ||= 'template'
1695
1700
  opt[:wrap_width] ||= @config['templates']['default']['wrap_width'] || 0
1696
1701
 
1702
+ logger.benchmark(:list_section, :finish)
1697
1703
  output(items, title, is_single, opt)
1698
1704
  end
1699
1705
 
@@ -1721,7 +1727,7 @@ module Doing
1721
1727
  destination = guess_section(destination)
1722
1728
 
1723
1729
  if @content.section?(destination) && (@content.section?(section) || archive_all)
1724
- do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
1730
+ do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before], after: options[:after], from: options[:from] })
1725
1731
  write(doing_file)
1726
1732
  else
1727
1733
  raise InvalidArgument, 'Either source or destination does not exist'
@@ -1743,7 +1749,7 @@ module Doing
1743
1749
  cfg = @config['templates'][opt[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1744
1750
  'wrap_width' => @config['wrap_width'] || 0,
1745
1751
  'date_format' => @config['default_date_format'],
1746
- 'order' => @config['order'] || 'asc',
1752
+ 'order' => @config['order'] || :asc,
1747
1753
  'tags_color' => @config['tags_color'],
1748
1754
  'duration' => @config['duration'],
1749
1755
  'interval_format' => @config['interval_format']
@@ -1762,7 +1768,7 @@ module Doing
1762
1768
  from: opt[:from],
1763
1769
  format: cfg['date_format'],
1764
1770
  interval_format: opt[:interval_format],
1765
- order: cfg['order'] || 'asc',
1771
+ order: cfg['order'] || :asc,
1766
1772
  output: output,
1767
1773
  section: opt[:section],
1768
1774
  sort_tags: opt[:sort_tags],
@@ -1797,7 +1803,7 @@ module Doing
1797
1803
  list_section({
1798
1804
  section: section,
1799
1805
  count: 0,
1800
- order: 'asc',
1806
+ order: :asc,
1801
1807
  date_filter: dates,
1802
1808
  times: times,
1803
1809
  output: output,
@@ -1863,7 +1869,7 @@ module Doing
1863
1869
  cfg = @config['templates'][opt[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1864
1870
  'wrap_width' => @config['wrap_width'] || 0,
1865
1871
  'date_format' => @config['default_date_format'],
1866
- 'order' => @config['order'] || 'asc',
1872
+ 'order' => @config['order'] || :asc,
1867
1873
  'tags_color' => @config['tags_color'],
1868
1874
  'duration' => @config['duration'],
1869
1875
  'interval_format' => @config['interval_format']
@@ -1879,7 +1885,7 @@ module Doing
1879
1885
  opt[:count] = count
1880
1886
  opt[:format] = cfg['date_format']
1881
1887
  opt[:template] = opt[:template] || cfg['template']
1882
- opt[:order] = 'asc'
1888
+ opt[:order] = :asc
1883
1889
  opt[:times] = times
1884
1890
 
1885
1891
  list_section(opt)
@@ -1896,7 +1902,7 @@ module Doing
1896
1902
  cfg = @config['templates'][options[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1897
1903
  'wrap_width' => @config['wrap_width'] || 0,
1898
1904
  'date_format' => @config['default_date_format'],
1899
- 'order' => @config['order'] || 'asc',
1905
+ 'order' => @config['order'] || :asc,
1900
1906
  'tags_color' => @config['tags_color'],
1901
1907
  'duration' => @config['duration'],
1902
1908
  'interval_format' => @config['interval_format']
@@ -2043,10 +2049,10 @@ module Doing
2043
2049
  ##
2044
2050
  ## @param format [String] return format (html,
2045
2051
  ## json, or text)
2046
- ## @param sort_by_name [Boolean] Sort by name if true, otherwise by time
2047
- ## @param sort_order [String] The sort order (asc or desc)
2052
+ ## @param sort_by [Symbol] Sort by :name or :time
2053
+ ## @param sort_order [Symbol] The sort order (:asc or :desc)
2048
2054
  ##
2049
- def tag_times(format: :text, sort_by_name: false, sort_order: 'asc')
2055
+ def tag_times(format: :text, sort_by: :time, sort_order: :asc)
2050
2056
  return '' if @timers.empty?
2051
2057
 
2052
2058
  max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
@@ -2054,13 +2060,13 @@ module Doing
2054
2060
  total = @timers.delete('All')
2055
2061
 
2056
2062
  tags_data = @timers.delete_if { |_k, v| v == 0 }
2057
- sorted_tags_data = if sort_by_name
2063
+ sorted_tags_data = if sort_by.normalize_tag_sort == :name
2058
2064
  tags_data.sort_by { |k, _v| k }
2059
2065
  else
2060
2066
  tags_data.sort_by { |_k, v| v }
2061
2067
  end
2062
2068
 
2063
- sorted_tags_data.reverse! if sort_order =~ /^asc/i
2069
+ sorted_tags_data.reverse! if sort_order.normalize_order == :asc
2064
2070
  case format
2065
2071
  when :html
2066
2072
 
@@ -2210,9 +2216,9 @@ EOS
2210
2216
  Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
2211
2217
  end
2212
2218
 
2213
- Doing.logger.benchmark(:configure, :start)
2219
+ logger.benchmark(:configure, :start)
2214
2220
  config = Doing.config
2215
- Doing.logger.benchmark(:configure, :finish)
2221
+ logger.benchmark(:configure, :finish)
2216
2222
 
2217
2223
  config.settings['backup_dir'] = ENV['DOING_BACKUP_DIR'] if ENV['DOING_BACKUP_DIR']
2218
2224
  @config = config.settings
@@ -2266,6 +2272,7 @@ EOS
2266
2272
  ## template trigger
2267
2273
  ##
2268
2274
  def output(items, title, is_single, opt)
2275
+ logger.benchmark(:output, :start)
2269
2276
  opt ||= {}
2270
2277
  out = nil
2271
2278
 
@@ -2283,7 +2290,7 @@ EOS
2283
2290
  end
2284
2291
 
2285
2292
  logger.debug('Output:', "#{items.count} #{items.count == 1 ? 'item' : 'items'} shown")
2286
-
2293
+ logger.benchmark(:output, :finish)
2287
2294
  out
2288
2295
  end
2289
2296
 
@@ -2327,26 +2334,37 @@ EOS
2327
2334
  section_items = @content.in_section(section)
2328
2335
  max = section_items.count - count.to_i
2329
2336
 
2337
+ opt[:after] = opt[:from][0] if opt[:from]
2338
+ opt[:before] = opt[:from][1] if opt[:from]
2339
+
2340
+ time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
2341
+
2342
+ if opt[:before].is_a?(String) && opt[:before] =~ time_rx
2343
+ opt[:before] = opt[:before].chronify(guess: :end, future: false)
2344
+ end
2345
+
2346
+ if opt[:after].is_a?(String) && opt[:after] =~ time_rx
2347
+ opt[:after] = opt[:after].chronify(guess: :begin, future: false)
2348
+ end
2349
+
2330
2350
  counter = 0
2331
2351
 
2332
2352
  @content.map do |item|
2333
2353
  break if counter >= max
2334
- if opt[:before]
2335
- time_string = opt[:before]
2336
- cutoff = time_string.chronify(guess: :begin)
2337
- end
2338
2354
 
2339
- if (item.section.downcase != section.downcase && section != /^all$/i) || item.section.downcase == destination.downcase
2340
- item
2341
- elsif ((!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff))
2342
- item
2343
- else
2344
- counter += 1
2345
- old_item = item.clone
2346
- item.move_to(destination, label: label, log: false)
2347
- Hooks.trigger :post_entry_updated, self, item, old_item
2348
- item
2349
- end
2355
+ next if item.section.downcase == destination.downcase
2356
+
2357
+ next if item.section.downcase != section.downcase && section != /^all$/i
2358
+
2359
+ next if (opt[:before] && item.date > opt[:before]) || (opt[:after] && item.date < opt[:after])
2360
+
2361
+ next if (!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s))
2362
+
2363
+ counter += 1
2364
+ old_item = item.clone
2365
+ item.move_to(destination, label: label, log: false)
2366
+ Hooks.trigger :post_entry_updated, self, item, old_item
2367
+ item
2350
2368
  end
2351
2369
 
2352
2370
  if counter.positive?