doing 2.1.26 → 2.1.30

Sign up to get free protection for your applications and to get access to all the features.
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?