doing 2.1.2pre → 2.1.6pre
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.
- checksums.yaml +4 -4
- data/.yardoc/checksums +19 -15
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +62 -14
- data/Gemfile.lock +25 -1
- data/README.md +5 -1
- data/Rakefile +2 -0
- data/bin/doing +429 -142
- data/docs/_config.yml +1 -0
- data/{doc → docs/doc}/Array.html +63 -1
- data/docs/doc/BooleanTermParser/Clause.html +293 -0
- data/docs/doc/BooleanTermParser/Operator.html +172 -0
- data/docs/doc/BooleanTermParser/Query.html +417 -0
- data/docs/doc/BooleanTermParser/QueryParser.html +135 -0
- data/docs/doc/BooleanTermParser/QueryTransformer.html +124 -0
- data/docs/doc/BooleanTermParser.html +115 -0
- data/docs/doc/Doing/CLIFormat.html +131 -0
- data/{doc → docs/doc}/Doing/Color.html +2 -2
- data/{doc → docs/doc}/Doing/Completion.html +1 -1
- data/{doc → docs/doc}/Doing/Configuration.html +163 -69
- data/{doc → docs/doc}/Doing/Content.html +0 -0
- data/{doc → docs/doc}/Doing/Errors/DoingNoTraceError.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/DoingRuntimeError.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/DoingStandardError.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/EmptyInput.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/NoResults.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/PluginException.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/UserCancelled.html +1 -1
- data/{doc → docs/doc}/Doing/Errors/WrongCommand.html +1 -1
- data/{doc → docs/doc}/Doing/Errors.html +1 -1
- data/{doc → docs/doc}/Doing/Hooks.html +1 -1
- data/{doc → docs/doc}/Doing/Item.html +135 -89
- data/{doc → docs/doc}/Doing/Items.html +36 -2
- data/{doc → docs/doc}/Doing/LogAdapter.html +70 -1
- data/{doc → docs/doc}/Doing/Note.html +5 -134
- data/{doc → docs/doc}/Doing/Pager.html +1 -1
- data/{doc → docs/doc}/Doing/Plugins.html +431 -35
- data/{doc → docs/doc}/Doing/Prompt.html +70 -18
- data/{doc → docs/doc}/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +713 -0
- data/docs/doc/Doing/Util/Backup.html +686 -0
- data/{doc → docs/doc}/Doing/Util.html +16 -4
- data/{doc → docs/doc}/Doing/WWID.html +133 -73
- data/{doc → docs/doc}/Doing/WWIDFile.html +0 -0
- data/{doc → docs/doc}/Doing.html +4 -4
- data/{doc → docs/doc}/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/{doc → docs/doc}/GLI/Commands.html +1 -1
- data/{doc → docs/doc}/GLI.html +1 -1
- data/{doc → docs/doc}/Hash.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +172 -0
- data/docs/doc/PhraseParser/PhraseClause.html +303 -0
- data/docs/doc/PhraseParser/Query.html +495 -0
- data/docs/doc/PhraseParser/QueryParser.html +136 -0
- data/docs/doc/PhraseParser/QueryTransformer.html +124 -0
- data/docs/doc/PhraseParser/TermClause.html +293 -0
- data/docs/doc/PhraseParser.html +115 -0
- data/{doc → docs/doc}/Status.html +1 -1
- data/{doc → docs/doc}/String.html +319 -13
- data/{doc → docs/doc}/Symbol.html +35 -1
- data/{doc → docs/doc}/Time.html +70 -2
- data/{doc → docs/doc}/_index.html +132 -4
- data/docs/doc/class_list.html +51 -0
- data/{doc → docs/doc}/css/common.css +0 -0
- data/{doc → docs/doc}/css/full_list.css +0 -0
- data/{doc → docs/doc}/css/style.css +0 -0
- data/{doc → docs/doc}/file.README.html +6 -2
- data/{doc → docs/doc}/file_list.html +0 -0
- data/{doc → docs/doc}/frames.html +0 -0
- data/{doc → docs/doc}/index.html +6 -2
- data/{doc → docs/doc}/js/app.js +0 -0
- data/{doc → docs/doc}/js/full_list.js +0 -0
- data/{doc → docs/doc}/js/jquery.js +0 -0
- data/{doc → docs/doc}/method_list.html +684 -196
- data/{doc → docs/doc}/top-level-namespace.html +2 -2
- data/docs/index.md +60 -0
- data/doing.gemspec +3 -0
- data/doing.rdoc +222 -74
- data/example_plugin.rb +3 -1
- data/lib/completion/_doing.zsh +53 -41
- data/lib/completion/doing.bash +17 -6
- data/lib/completion/doing.fish +321 -2
- data/lib/doing/array.rb +9 -0
- data/lib/doing/boolean_term_parser.rb +86 -0
- data/lib/doing/completion/fish_completion.rb +46 -3
- data/lib/doing/completion/zsh_completion.rb +1 -1
- data/lib/doing/configuration.rb +48 -21
- data/lib/doing/item.rb +105 -10
- data/lib/doing/items.rb +6 -0
- data/lib/doing/log_adapter.rb +28 -0
- data/lib/doing/note.rb +31 -30
- data/lib/doing/phrase_parser.rb +124 -0
- data/lib/doing/plugin_manager.rb +84 -21
- data/lib/doing/plugins/export/dayone_export.rb +209 -0
- data/lib/doing/plugins/export/html_export.rb +2 -2
- data/lib/doing/plugins/export/json_export.rb +1 -0
- data/lib/doing/plugins/export/markdown_export.rb +1 -1
- data/lib/doing/plugins/export/template_export.rb +94 -86
- data/lib/doing/prompt.rb +26 -15
- data/lib/doing/string.rb +114 -29
- data/lib/doing/string_chronify.rb +5 -1
- data/lib/doing/symbol.rb +4 -0
- data/lib/doing/template_string.rb +197 -0
- data/lib/doing/time.rb +32 -0
- data/lib/doing/util.rb +6 -7
- data/lib/doing/util_backup.rb +287 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +105 -41
- data/lib/doing.rb +9 -0
- data/lib/examples/plugins/say_export.rb +1 -1
- data/lib/examples/plugins/wiki_export/wiki_export.rb +3 -3
- data/lib/templates/doing-dayone-entry.erb +6 -0
- data/lib/templates/doing-dayone.erb +5 -0
- metadata +136 -51
- data/doc/class_list.html +0 -51
data/lib/doing/prompt.rb
CHANGED
|
@@ -16,6 +16,14 @@ module Doing
|
|
|
16
16
|
@default_answer ||= false
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def enter_text(prompt, default_response: '')
|
|
20
|
+
return default_response if @default_answer
|
|
21
|
+
|
|
22
|
+
print "#{yellow(prompt).sub(/:?$/, ':')} #{reset}"
|
|
23
|
+
$stdin.gets.strip
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
19
27
|
##
|
|
20
28
|
## Ask a yes or no question in the terminal
|
|
21
29
|
##
|
|
@@ -110,14 +118,17 @@ module Doing
|
|
|
110
118
|
return nil unless $stdout.isatty
|
|
111
119
|
|
|
112
120
|
# fzf_args << '-1' # User is expecting a menu, and even if only one it seves as confirmation
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
default_args = []
|
|
122
|
+
default_args << %(--prompt="#{prompt}")
|
|
123
|
+
default_args << "--height=#{options.count + 2}"
|
|
124
|
+
default_args << '--info=inline'
|
|
125
|
+
default_args << '--multi' if multiple
|
|
117
126
|
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
|
118
|
-
|
|
127
|
+
default_args << %(--header="#{header}")
|
|
128
|
+
default_args.concat(fzf_args)
|
|
119
129
|
options.sort! if sorted
|
|
120
|
-
|
|
130
|
+
|
|
131
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{default_args.join(' ')}`
|
|
121
132
|
return false if res.strip.size.zero?
|
|
122
133
|
|
|
123
134
|
res
|
|
@@ -127,16 +138,16 @@ module Doing
|
|
|
127
138
|
## Create an interactive menu to select from a set of Items
|
|
128
139
|
##
|
|
129
140
|
## @param items [Array] list of items
|
|
130
|
-
## @param opt
|
|
131
|
-
## @param include_section [Boolean] include section
|
|
141
|
+
## @param opt Additional options
|
|
132
142
|
##
|
|
133
|
-
## @option opt [
|
|
134
|
-
## @option opt [String] :
|
|
135
|
-
## @option opt [String] :
|
|
136
|
-
## @option opt [
|
|
137
|
-
## @option opt [Boolean] :menu
|
|
138
|
-
## @option opt [Boolean] :
|
|
139
|
-
## @option opt [Boolean] :
|
|
143
|
+
## @option opt [Boolean] :include_section Include section name for each item in menu
|
|
144
|
+
## @option opt [String] :header A custom header string
|
|
145
|
+
## @option opt [String] :prompt A custom prompt string
|
|
146
|
+
## @option opt [String] :query Initial query
|
|
147
|
+
## @option opt [Boolean] :show_if_single Show menu even if there's only one option
|
|
148
|
+
## @option opt [Boolean] :menu Show menu
|
|
149
|
+
## @option opt [Boolean] :sort Sort options
|
|
150
|
+
## @option opt [Boolean] :multiple Allow multiple selections
|
|
140
151
|
## @option opt [Symbol] :case (:sensitive, :ignore, :smart)
|
|
141
152
|
##
|
|
142
153
|
def choose_from_items(items, **opt)
|
data/lib/doing/string.rb
CHANGED
|
@@ -28,7 +28,9 @@ module Doing
|
|
|
28
28
|
##
|
|
29
29
|
## @return [Regexp] Regex pattern
|
|
30
30
|
##
|
|
31
|
-
def to_rx(distance:
|
|
31
|
+
def to_rx(distance: nil, case_type: nil)
|
|
32
|
+
distance ||= Doing.config.settings.dig('search', 'distance').to_i || 3
|
|
33
|
+
case_type ||= Doing.config.settings.dig('search', 'case')&.normalize_case || :smart
|
|
32
34
|
case_sensitive = case case_type
|
|
33
35
|
when :smart
|
|
34
36
|
self =~ /[A-Z]/ ? true : false
|
|
@@ -44,7 +46,9 @@ module Doing
|
|
|
44
46
|
when /^'/
|
|
45
47
|
sub(/^'(.*?)'?$/, '\1')
|
|
46
48
|
else
|
|
47
|
-
split(/ +/).map
|
|
49
|
+
split(/ +/).map do |w|
|
|
50
|
+
w.split('').join(".{0,#{distance}}").gsub(/\+/, '\+').wildcard_to_rx
|
|
51
|
+
end.join('.*?')
|
|
48
52
|
end
|
|
49
53
|
Regexp.new(pattern, !case_sensitive)
|
|
50
54
|
end
|
|
@@ -72,7 +76,7 @@ module Doing
|
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
## @param (see #highlight_tags)
|
|
75
|
-
def highlight_tags!(color = 'yellow')
|
|
79
|
+
def highlight_tags!(color = 'yellow', last_color: nil)
|
|
76
80
|
replace highlight_tags(color)
|
|
77
81
|
end
|
|
78
82
|
|
|
@@ -83,17 +87,18 @@ module Doing
|
|
|
83
87
|
##
|
|
84
88
|
## @return [String] string with @tags highlighted
|
|
85
89
|
##
|
|
86
|
-
def highlight_tags(color = 'yellow')
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
def highlight_tags(color = 'yellow', last_color: nil)
|
|
91
|
+
unless last_color
|
|
92
|
+
escapes = scan(/(\e\[[\d;]+m)[^\e]+@/)
|
|
93
|
+
color = color.split(' ') unless color.is_a?(Array)
|
|
94
|
+
tag_color = color.each_with_object([]) { |c, arr| arr << Doing::Color.send(c) }.join('')
|
|
95
|
+
last_color = if !escapes.empty?
|
|
96
|
+
(escapes.count > 1 ? escapes[-2..-1] : [escapes[-1]]).map { |v| v[0] }.join('')
|
|
97
|
+
else
|
|
98
|
+
Doing::Color.default
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{last_color}")
|
|
97
102
|
end
|
|
98
103
|
|
|
99
104
|
##
|
|
@@ -162,6 +167,29 @@ module Doing
|
|
|
162
167
|
replace uncolor
|
|
163
168
|
end
|
|
164
169
|
|
|
170
|
+
def simple_wrap(width)
|
|
171
|
+
str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }
|
|
172
|
+
words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
|
|
173
|
+
out = []
|
|
174
|
+
line = []
|
|
175
|
+
|
|
176
|
+
words.each do |word|
|
|
177
|
+
if word.uncolor.length >= width
|
|
178
|
+
chars = word.uncolor.split('')
|
|
179
|
+
out << chars.slice!(0, width - 1).join('') while chars.count >= width
|
|
180
|
+
line << chars.join('')
|
|
181
|
+
next
|
|
182
|
+
elsif line.join(' ').uncolor.length + word.uncolor.length + 1 > width
|
|
183
|
+
out.push(line.join(' '))
|
|
184
|
+
line.clear
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
line << word.uncolor
|
|
188
|
+
end
|
|
189
|
+
out.push(line.join(' '))
|
|
190
|
+
out.join("\n")
|
|
191
|
+
end
|
|
192
|
+
|
|
165
193
|
##
|
|
166
194
|
## Wrap string at word breaks, respecting tags
|
|
167
195
|
##
|
|
@@ -169,16 +197,36 @@ module Doing
|
|
|
169
197
|
## @param offset [Integer] (Optional) The width to pad each subsequent line
|
|
170
198
|
## @param prefix [String] (Optional) A prefix to add to each line
|
|
171
199
|
##
|
|
172
|
-
def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', color: '', after: '', reset: '')
|
|
200
|
+
def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', color: '', after: '', reset: '', pad_first: false)
|
|
173
201
|
last_color = color.empty? ? '' : after.last_color
|
|
174
|
-
note_rx = /(?
|
|
202
|
+
note_rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?note/
|
|
203
|
+
note = ''
|
|
204
|
+
after = after.dup if after.frozen?
|
|
205
|
+
after.sub!(note_rx) do
|
|
206
|
+
note = Regexp.last_match(0)
|
|
207
|
+
''
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
left_pad = ' ' * offset
|
|
211
|
+
left_pad += indent
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# return "#{left_pad}#{prefix}#{color}#{self}#{last_color} #{note}" unless len.positive?
|
|
215
|
+
|
|
175
216
|
# Don't break inside of tag values
|
|
176
|
-
str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }
|
|
217
|
+
str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }.gsub(/\n/, ' ')
|
|
218
|
+
|
|
177
219
|
words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
|
|
178
220
|
out = []
|
|
179
221
|
line = []
|
|
222
|
+
|
|
180
223
|
words.each do |word|
|
|
181
|
-
if
|
|
224
|
+
if word.uncolor.length >= len
|
|
225
|
+
chars = word.uncolor.split('')
|
|
226
|
+
out << chars.slice!(0, len - 1).join('') while chars.count >= len
|
|
227
|
+
line << chars.join('')
|
|
228
|
+
next
|
|
229
|
+
elsif line.join(' ').uncolor.length + word.uncolor.length + 1 > len
|
|
182
230
|
out.push(line.join(' '))
|
|
183
231
|
line.clear
|
|
184
232
|
end
|
|
@@ -186,17 +234,18 @@ module Doing
|
|
|
186
234
|
line << word.uncolor
|
|
187
235
|
end
|
|
188
236
|
out.push(line.join(' '))
|
|
189
|
-
note = ''
|
|
190
|
-
after.sub!(note_rx) do
|
|
191
|
-
note = Regexp.last_match(0)
|
|
192
|
-
''
|
|
193
|
-
end
|
|
194
237
|
|
|
238
|
+
last_color = ''
|
|
195
239
|
out[0] = format("%-#{pad}s%s%s", out[0], last_color, after)
|
|
196
240
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
241
|
+
out.map.with_index { |l, idx|
|
|
242
|
+
if !pad_first && idx == 0
|
|
243
|
+
"#{color}#{prefix}#{l}#{last_color}"
|
|
244
|
+
else
|
|
245
|
+
"#{left_pad}#{color}#{prefix}#{l}#{last_color}"
|
|
246
|
+
end
|
|
247
|
+
}.join("\n") + " #{note}".chomp
|
|
248
|
+
# res.join("\n").strip + last_color + " #{note}".chomp
|
|
200
249
|
end
|
|
201
250
|
|
|
202
251
|
##
|
|
@@ -210,6 +259,10 @@ module Doing
|
|
|
210
259
|
end
|
|
211
260
|
end
|
|
212
261
|
|
|
262
|
+
def pluralize(number)
|
|
263
|
+
number == 1 ? self : "#{self}s"
|
|
264
|
+
end
|
|
265
|
+
|
|
213
266
|
##
|
|
214
267
|
## Convert a sort order string to a qualified type
|
|
215
268
|
##
|
|
@@ -241,7 +294,7 @@ module Doing
|
|
|
241
294
|
|
|
242
295
|
def normalize_case(default = :smart)
|
|
243
296
|
case self
|
|
244
|
-
when /^c/i
|
|
297
|
+
when /^(c|sens)/i
|
|
245
298
|
:sensitive
|
|
246
299
|
when /^i/i
|
|
247
300
|
:ignore
|
|
@@ -269,11 +322,35 @@ module Doing
|
|
|
269
322
|
:or
|
|
270
323
|
when /(not|none)/i
|
|
271
324
|
:not
|
|
325
|
+
when /^p/i
|
|
326
|
+
:pattern
|
|
272
327
|
else
|
|
273
328
|
default.is_a?(Symbol) ? default : default.normalize_bool
|
|
274
329
|
end
|
|
275
330
|
end
|
|
276
331
|
|
|
332
|
+
##
|
|
333
|
+
## Convert a matching configuration string to a symbol
|
|
334
|
+
##
|
|
335
|
+
## @return Symbol :fuzzy, :pattern, :exact
|
|
336
|
+
##
|
|
337
|
+
def normalize_matching!(default = :pattern)
|
|
338
|
+
replace normalize_bool(default)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def normalize_matching(default = :pattern)
|
|
342
|
+
case self
|
|
343
|
+
when /^f/i
|
|
344
|
+
:fuzzy
|
|
345
|
+
when /^p/i
|
|
346
|
+
:pattern
|
|
347
|
+
when /^e/i
|
|
348
|
+
:exact
|
|
349
|
+
else
|
|
350
|
+
default.is_a?(Symbol) ? default : default.normalize_matching
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
277
354
|
def normalize_trigger!
|
|
278
355
|
replace normalize_trigger
|
|
279
356
|
end
|
|
@@ -282,8 +359,16 @@ module Doing
|
|
|
282
359
|
gsub(/\((?!\?:)/, '(?:').downcase
|
|
283
360
|
end
|
|
284
361
|
|
|
362
|
+
def wildcard_to_rx
|
|
363
|
+
gsub(/\?/, '\S').gsub(/\*/, '\S*?')
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def add_at
|
|
367
|
+
strip.sub(/^([+-]*)@/, '\1')
|
|
368
|
+
end
|
|
369
|
+
|
|
285
370
|
def to_tags
|
|
286
|
-
gsub(/ *, */, ' ').gsub(/ +/, ' ').split(/ /).sort.uniq.map
|
|
371
|
+
gsub(/ *, */, ' ').gsub(/ +/, ' ').split(/ /).sort.uniq.map(&:add_at)
|
|
287
372
|
end
|
|
288
373
|
|
|
289
374
|
def add_tags!(tags, remove: false)
|
|
@@ -503,7 +588,7 @@ module Doing
|
|
|
503
588
|
end
|
|
504
589
|
else
|
|
505
590
|
case self
|
|
506
|
-
when / *,
|
|
591
|
+
when /(^\[.*?\]$| *, *)/
|
|
507
592
|
gsub(/^\[ *| *\]$/, '').split(/ *, */)
|
|
508
593
|
when /^[0-9]+$/
|
|
509
594
|
to_i
|
|
@@ -41,7 +41,11 @@ module Doing
|
|
|
41
41
|
if secs_ago
|
|
42
42
|
now - secs_ago
|
|
43
43
|
else
|
|
44
|
-
Chronic.parse(self, {
|
|
44
|
+
Chronic.parse(self, {
|
|
45
|
+
guess: options.fetch(:guess, :begin),
|
|
46
|
+
context: options.fetch(:future, false) ? :future : :past,
|
|
47
|
+
ambiguous_time_range: 8
|
|
48
|
+
})
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
|
data/lib/doing/symbol.rb
CHANGED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doing
|
|
4
|
+
##
|
|
5
|
+
## Template string formatting
|
|
6
|
+
##
|
|
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
|
+
attr_reader :original
|
|
31
|
+
|
|
32
|
+
include Color
|
|
33
|
+
def initialize(string, placeholders: {}, force_color: false, wrap_width: 0, color: '', tags_color: '', reset: '')
|
|
34
|
+
Color.coloring = true if force_color
|
|
35
|
+
@colors = nil
|
|
36
|
+
@original = string
|
|
37
|
+
super(Color.reset + string)
|
|
38
|
+
|
|
39
|
+
placeholders.each { |k, v| fill(k, v, wrap_width: wrap_width, color: color, tags_color: tags_color) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
## Test if string contains any valid %colors
|
|
44
|
+
##
|
|
45
|
+
## @return [Boolean] True if colors, False otherwise.
|
|
46
|
+
##
|
|
47
|
+
def colors?
|
|
48
|
+
scan(/%([a-z]+)/).each do
|
|
49
|
+
return true if Regexp.last_match(1).validate_color
|
|
50
|
+
end
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def reparse
|
|
55
|
+
@parsed_colors = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
## Return string with %colors replaced with escape codes
|
|
60
|
+
##
|
|
61
|
+
## @return [String] colorized string
|
|
62
|
+
##
|
|
63
|
+
def colored
|
|
64
|
+
reparse
|
|
65
|
+
parsed_colors[:string].apply_colors(parsed_colors[:colors])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
## Remove all valid %colors from string
|
|
70
|
+
##
|
|
71
|
+
## @return [String] cleaned string
|
|
72
|
+
##
|
|
73
|
+
def raw
|
|
74
|
+
parsed_colors[:string].uncolor
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parsed_colors
|
|
78
|
+
@parsed_colors ||= parse_colors
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
## Parse a template string for %colors and return a hash
|
|
83
|
+
## of colors and string locations
|
|
84
|
+
##
|
|
85
|
+
## @return [Hash] Uncolored string and array of colors and locations
|
|
86
|
+
def parse_colors
|
|
87
|
+
working = dup
|
|
88
|
+
color_array = []
|
|
89
|
+
|
|
90
|
+
scan(/(?<!\\)(%([a-z]+))/).each do |color|
|
|
91
|
+
valid_color = color[1].validate_color
|
|
92
|
+
next unless valid_color
|
|
93
|
+
|
|
94
|
+
idx = working.match(/(?<!\\)%#{valid_color}/).begin(0)
|
|
95
|
+
color_array.push({ name: valid_color, color: Color.send(valid_color), index: idx })
|
|
96
|
+
working.sub!(/(?<!\\)%#{valid_color}/, '')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
{ string: working, colors: color_array }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
## Apply a color array to a string
|
|
104
|
+
##
|
|
105
|
+
## @param color_array [Array] Array of hashes
|
|
106
|
+
## containing :name, :color,
|
|
107
|
+
## :index
|
|
108
|
+
##
|
|
109
|
+
def apply_colors(color_array)
|
|
110
|
+
str = dup
|
|
111
|
+
color_array.reverse.each do |color|
|
|
112
|
+
c = color[:color].empty? ? Color.send(color[:name]) : color[:color]
|
|
113
|
+
str.insert(color[:index], c)
|
|
114
|
+
end
|
|
115
|
+
str
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def fill(placeholder, value, wrap_width: 0, color: '', tags_color: '', reset: '')
|
|
119
|
+
reparse
|
|
120
|
+
rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?#{placeholder.sub(/^%/, '')}(?<after>.*?)$/
|
|
121
|
+
ph = raw.match(rx)
|
|
122
|
+
|
|
123
|
+
return unless ph
|
|
124
|
+
placeholder_offset = ph.begin(0)
|
|
125
|
+
last_colors = parsed_colors[:colors].select { |v| v[:index] <= placeholder_offset + 4 }
|
|
126
|
+
|
|
127
|
+
last_color = last_colors.map { |v| v[:color] }.pop(3).join('')
|
|
128
|
+
|
|
129
|
+
sub!(rx) do
|
|
130
|
+
m = Regexp.last_match
|
|
131
|
+
|
|
132
|
+
after = m['after']
|
|
133
|
+
|
|
134
|
+
if value.nil? || value.empty?
|
|
135
|
+
after
|
|
136
|
+
else
|
|
137
|
+
pad = m['width'].to_i
|
|
138
|
+
mark = m['mchar'] || ''
|
|
139
|
+
if placeholder == 'shortdate' && m['width'].nil?
|
|
140
|
+
pad = 13
|
|
141
|
+
end
|
|
142
|
+
indent = nil
|
|
143
|
+
if m['ichar']
|
|
144
|
+
char = m['ichar'] =~ /t/ ? "\t" : ' '
|
|
145
|
+
indent = char * m['icount'].to_i
|
|
146
|
+
end
|
|
147
|
+
indent ||= placeholder =~ /^title/ ? '' : "\t"
|
|
148
|
+
prefix = m['prefix']
|
|
149
|
+
if placeholder =~ /^title/
|
|
150
|
+
color = last_color + color
|
|
151
|
+
|
|
152
|
+
if wrap_width.positive? || pad.positive?
|
|
153
|
+
width = pad.positive? ? pad : wrap_width
|
|
154
|
+
|
|
155
|
+
out = value.gsub(/%/, '\%').strip.wrap(width,
|
|
156
|
+
pad: pad,
|
|
157
|
+
indent: indent,
|
|
158
|
+
offset: placeholder_offset,
|
|
159
|
+
prefix: prefix,
|
|
160
|
+
color: color,
|
|
161
|
+
after: after,
|
|
162
|
+
reset: reset,
|
|
163
|
+
pad_first: false)
|
|
164
|
+
out.highlight_tags!(tags_color, last_color: color) if tags_color && !tags_color.empty?
|
|
165
|
+
out
|
|
166
|
+
else
|
|
167
|
+
out = format("%s%s%#{pad}s%s", prefix, color, value.gsub(/%/, '\%').sub(/\s*$/, ''), after)
|
|
168
|
+
out.highlight_tags!(tags_color, last_color: color) if tags_color && !tags_color.empty?
|
|
169
|
+
out
|
|
170
|
+
end
|
|
171
|
+
elsif placeholder =~ /^note/
|
|
172
|
+
if wrap_width.positive? || pad.positive?
|
|
173
|
+
width = pad.positive? ? pad : wrap_width
|
|
174
|
+
outstring = value.map do |l|
|
|
175
|
+
if l.empty?
|
|
176
|
+
' '
|
|
177
|
+
else
|
|
178
|
+
line = l.gsub(/%/, '\%').strip.wrap(width, pad: pad, indent: indent, offset: 0, prefix: prefix, color: last_color, after: after, reset: reset, pad_first: true)
|
|
179
|
+
line.highlight_tags!(tags_color, last_color: last_color) unless tags_color.nil? || tags_color.empty?
|
|
180
|
+
"#{line} "
|
|
181
|
+
end
|
|
182
|
+
end.join("\n")
|
|
183
|
+
"\n#{last_color}#{mark}#{outstring} "
|
|
184
|
+
else
|
|
185
|
+
out = format("\n%s%s%s%#{pad}s%s", indent, prefix, last_color, value.join("\n#{indent}#{prefix}").gsub(/%/, '\%').sub(/\s*$/, ''), after)
|
|
186
|
+
out.highlight_tags!(tags_color, last_color: last_color) if tags_color && !tags_color.empty?
|
|
187
|
+
out
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
format("%s%#{pad}s%s", prefix, value.gsub(/%/, '\%').sub(/\s*$/, ''), after)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
@parsed_colors = parse_colors
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
data/lib/doing/time.rb
CHANGED
|
@@ -14,5 +14,37 @@ module Doing
|
|
|
14
14
|
strftime('%m/%d/%Y %_I:%M%P')
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
def humanize(seconds)
|
|
19
|
+
s = seconds
|
|
20
|
+
m = (s / 60).floor
|
|
21
|
+
s = (s % 60).floor
|
|
22
|
+
h = (m / 60).floor
|
|
23
|
+
m = (m % 60).floor
|
|
24
|
+
d = (h / 24).floor
|
|
25
|
+
h = h % 24
|
|
26
|
+
|
|
27
|
+
output = []
|
|
28
|
+
output.push("#{d} #{'day'.pluralize(d)}") if d.positive?
|
|
29
|
+
output.push("#{h} #{'hour'.pluralize(h)}") if h.positive?
|
|
30
|
+
output.push("#{m} #{'minute'.pluralize(m)}") if m.positive?
|
|
31
|
+
output.push("#{s} #{'second'.pluralize(s)}") if s.positive?
|
|
32
|
+
output.join(', ')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def time_ago
|
|
36
|
+
if self > Date.today.to_time
|
|
37
|
+
output = humanize(Time.now - self)
|
|
38
|
+
"#{output} ago"
|
|
39
|
+
elsif self > (Date.today - 1).to_time
|
|
40
|
+
"Yesterday at #{strftime('%_I:%M:%S%P')}"
|
|
41
|
+
elsif self > (Date.today - 6).to_time
|
|
42
|
+
strftime('%a %I:%M:%S%P')
|
|
43
|
+
elsif self.year == Date.today.year
|
|
44
|
+
strftime('%m/%d %I:%M:%S%P')
|
|
45
|
+
else
|
|
46
|
+
strftime('%m/%d/%Y %I:%M:%S%P')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
17
49
|
end
|
|
18
50
|
end
|
data/lib/doing/util.rb
CHANGED
|
@@ -112,20 +112,19 @@ module Doing
|
|
|
112
112
|
puts content
|
|
113
113
|
return
|
|
114
114
|
end
|
|
115
|
-
|
|
115
|
+
Doing.logger.benchmark(:write_file, :start)
|
|
116
116
|
file = File.expand_path(file)
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
# Create a backup copy for the undo command
|
|
120
|
-
FileUtils.cp(file, "#{file}~")
|
|
121
|
-
end
|
|
118
|
+
Backup.write_backup(file) if backup
|
|
122
119
|
|
|
123
120
|
File.open(file, 'w+') do |f|
|
|
124
121
|
f.puts content
|
|
125
122
|
Doing.logger.debug('Write:', "File written: #{file}")
|
|
126
123
|
end
|
|
127
|
-
|
|
124
|
+
Doing.logger.benchmark(:_post_write_hook, :start)
|
|
128
125
|
Hooks.trigger :post_write, file
|
|
126
|
+
Doing.logger.benchmark(:_post_write_hook, :finish)
|
|
127
|
+
Doing.logger.benchmark(:write_file, :finish)
|
|
129
128
|
end
|
|
130
129
|
|
|
131
130
|
def safe_load_file(filename)
|
|
@@ -133,7 +132,7 @@ module Doing
|
|
|
133
132
|
end
|
|
134
133
|
|
|
135
134
|
def default_editor
|
|
136
|
-
@default_editor
|
|
135
|
+
@default_editor ||= find_default_editor
|
|
137
136
|
end
|
|
138
137
|
|
|
139
138
|
def editor_with_args
|