doing 2.1.12 → 2.1.16
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/.irbrc +1 -0
- data/.yardoc/checksums +16 -14
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +67 -0
- data/Gemfile.lock +9 -2
- data/README.md +56 -19
- data/bin/doing +317 -113
- data/docs/doc/Array.html +117 -3
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +1 -1
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +7 -4
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +337 -14
- data/docs/doc/Doing/Items.html +66 -2
- data/docs/doc/Doing/LogAdapter.html +1 -1
- data/docs/doc/Doing/Note.html +2 -2
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +103 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/Util/Backup.html +84 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +214 -35
- data/docs/doc/Doing.html +3 -3
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +279 -0
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +881 -138
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/_index.html +14 -9
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +41 -15
- data/docs/doc/index.html +41 -15
- data/docs/doc/method_list.html +408 -256
- data/docs/doc/top-level-namespace.html +2 -2
- data/docs/index.md +56 -19
- data/doing.gemspec +2 -0
- data/doing.rdoc +257 -48
- data/example_plugin.rb +2 -4
- data/lib/completion/_doing.zsh +31 -27
- data/lib/completion/doing.bash +50 -39
- data/lib/completion/doing.fish +37 -7
- data/lib/doing/array_chronify.rb +57 -0
- data/lib/doing/configuration.rb +4 -1
- data/lib/doing/item.rb +176 -0
- data/lib/doing/log_adapter.rb +1 -1
- data/lib/doing/numeric_chronify.rb +40 -0
- data/lib/doing/plugins/export/dayone_export.rb +1 -1
- data/lib/doing/plugins/export/json_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.rb +47 -90
- data/lib/doing/plugins/import/calendar_import.rb +13 -1
- data/lib/doing/plugins/import/doing_import.rb +12 -1
- data/lib/doing/plugins/import/timing_import.rb +13 -1
- data/lib/doing/prompt.rb +54 -1
- data/lib/doing/string.rb +97 -33
- data/lib/doing/string_chronify.rb +112 -14
- data/lib/doing/template_string.rb +1 -1
- data/lib/doing/time.rb +6 -6
- data/lib/doing/util_backup.rb +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +128 -103
- data/lib/doing.rb +36 -31
- data/lib/examples/plugins/say_export.rb +1 -4
- metadata +46 -2
@@ -5,6 +5,7 @@
|
|
5
5
|
# author: Brett Terpstra
|
6
6
|
# url: https://brettterpstra.com
|
7
7
|
module Doing
|
8
|
+
# Template Export
|
8
9
|
class TemplateExport
|
9
10
|
include Doing::Color
|
10
11
|
include Doing::Util
|
@@ -32,7 +33,7 @@ module Doing
|
|
32
33
|
|
33
34
|
placeholders = {}
|
34
35
|
|
35
|
-
if
|
36
|
+
if !item.note.empty? && wwid.config['include_notes']
|
36
37
|
note = item.note.map(&:strip).delete_if(&:empty?)
|
37
38
|
note.map! { |line| "#{line.sub(/^\t*/, '')} " }
|
38
39
|
|
@@ -42,122 +43,74 @@ module Doing
|
|
42
43
|
line.simple_wrap(width)
|
43
44
|
# line.chomp.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
|
44
45
|
end
|
45
|
-
note
|
46
|
+
note.delete_if(&:empty?)
|
46
47
|
end
|
47
48
|
else
|
48
49
|
note = []
|
49
50
|
end
|
50
51
|
|
51
|
-
# output.sub!(/%(\d+)?date/) do
|
52
|
-
# pad = Regexp.last_match(1).to_i
|
53
|
-
# format("%#{pad}s", item.date.strftime(opt[:format]))
|
54
|
-
# end
|
55
52
|
placeholders['date'] = item.date.strftime(opt[:format])
|
56
53
|
|
57
54
|
interval = wwid.get_interval(item, record: true, formatted: false) if opt[:times]
|
58
55
|
if interval
|
59
|
-
case opt[:interval_format].to_sym
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
interval = format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)
|
66
|
-
end
|
56
|
+
interval = case opt[:interval_format].to_sym
|
57
|
+
when :human
|
58
|
+
interval.time_string(format: :hm)
|
59
|
+
else
|
60
|
+
interval.time_string(format: :clock)
|
61
|
+
end
|
67
62
|
end
|
68
63
|
|
69
64
|
interval ||= ''
|
70
|
-
# output.sub!(/%interval/, interval)
|
71
65
|
placeholders['interval'] = interval
|
72
66
|
|
73
67
|
duration = item.duration if opt[:duration]
|
74
68
|
if duration
|
75
|
-
case opt[:interval_format].to_sym
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
duration = format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)
|
82
|
-
end
|
69
|
+
duration = case opt[:interval_format].to_sym
|
70
|
+
when :human
|
71
|
+
duration.time_string(format: :hm)
|
72
|
+
else
|
73
|
+
duration.time_string(format: :clock)
|
74
|
+
end
|
83
75
|
end
|
84
76
|
duration ||= ''
|
85
|
-
# output.sub!(/%duration/, duration)
|
86
77
|
placeholders['duration'] = duration
|
87
78
|
|
88
|
-
|
89
|
-
# pad = Regexp.last_match(1) || 13
|
90
|
-
# format("%#{pad}s", item.date.relative_date)
|
91
|
-
# end
|
92
|
-
placeholders['shortdate'] = format("%13s", item.date.relative_date)
|
93
|
-
# output.sub!(/%section/, item.section) if item.section
|
79
|
+
placeholders['shortdate'] = format('%13s', item.date.relative_date)
|
94
80
|
placeholders['section'] = item.section || ''
|
95
81
|
placeholders['title'] = item.title
|
96
|
-
|
97
|
-
# title_rx = /(?mi)%(?<width>-?\d+)?(?:(?<ichar>[ _t])(?<icount>\d+))?(?<prefix>.[ _t]?)?title(?<after>.*?)$/
|
98
|
-
# title_color = Doing::Color.reset + output.match(/(?mi)^(.*?)(%.*?title)/)[1].last_color
|
99
|
-
|
100
|
-
# title_offset = Doing::Color.uncolor(output).match(title_rx).begin(0)
|
101
|
-
|
102
|
-
# output.sub!(title_rx) do
|
103
|
-
# m = Regexp.last_match
|
104
|
-
|
105
|
-
# after = m['after']
|
106
|
-
# pad = m['width'].to_i
|
107
|
-
# indent = ''
|
108
|
-
# if m['ichar']
|
109
|
-
# char = m['ichar'] =~ /t/ ? "\t" : ' '
|
110
|
-
# indent = char * m['icount'].to_i
|
111
|
-
# end
|
112
|
-
# prefix = m['prefix']
|
113
|
-
# if opt[:wrap_width]&.positive? || pad.positive?
|
114
|
-
# width = pad.positive? ? pad : opt[:wrap_width]
|
115
|
-
# item.title.wrap(width, pad: pad, indent: indent, offset: title_offset, prefix: prefix, color: title_color, after: after, reset: reset)
|
116
|
-
# # flag + item.title.gsub(/(.{#{opt[:wrap_width]}})(?=\s+|\Z)/, "\\1\n ").sub(/\s*$/, '') + reset
|
117
|
-
# else
|
118
|
-
# format("%s%#{pad}s%s", prefix, item.title.sub(/\s*$/, ''), after)
|
119
|
-
# end
|
120
|
-
# end
|
121
|
-
|
122
|
-
|
123
|
-
|
124
82
|
placeholders['note'] = note
|
125
83
|
placeholders['idnote'] = note.empty? ? '' : "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}"
|
126
84
|
placeholders['odnote'] = note.empty? ? '' : "\n#{note.map { |l| "#{l.strip} " }.join("\n")}"
|
127
|
-
placeholders['chompnote'] = note.empty? ? '' : note.map { |l| l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ') }.join(' ')
|
128
|
-
|
129
|
-
# if note.empty?
|
130
|
-
# output.gsub!(/%(chomp|[io]d|(\^.)?(([ _t]|[^a-z0-9])?\d+)?(.[ _t]?)?)?note/, '')
|
131
|
-
# else
|
132
|
-
# output.sub!(/%note/, "\n#{note.map { |l| "\t#{l.strip} " }.join("\n")}")
|
133
|
-
# output.sub!(/%idnote/, "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}")
|
134
|
-
# output.sub!(/%odnote/, "\n#{note.map { |l| "#{l.strip} " }.join("\n")}")
|
135
|
-
# output.sub!(/(?mi)%(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])?(?<icount>\d+))?(?<prefix>.[ _t]?)?note/) do
|
136
|
-
# m = Regexp.last_match
|
137
|
-
# mark = m['mchar'] || ''
|
138
|
-
# indent = if m['ichar']
|
139
|
-
# char = m['ichar'] =~ /t/ ? "\t" : ' '
|
140
|
-
# char * m['icount'].to_i
|
141
|
-
# else
|
142
|
-
# ''
|
143
|
-
# end
|
144
|
-
# prefix = m['prefix'] || ''
|
145
|
-
# "\n#{note.map { |l| "#{mark}#{indent}#{prefix}#{l.strip} " }.join("\n")}"
|
146
|
-
# end
|
147
|
-
|
148
|
-
# output.sub!(/%chompnote/) do
|
149
|
-
# note.map { |l| l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ') }.join(' ')
|
150
|
-
# end
|
151
|
-
# end
|
152
85
|
|
153
|
-
|
154
|
-
|
155
|
-
|
86
|
+
chompnote = []
|
87
|
+
unless note.empty?
|
88
|
+
chompnote = note.map do |l|
|
89
|
+
l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
placeholders['chompnote'] = chompnote.join(' ')
|
156
93
|
|
157
|
-
|
94
|
+
template = opt[:template].dup
|
95
|
+
note_rx = /(?i-m)(?x:^([\s\S]*?)
|
96
|
+
(%(?:[io]d|(?:\^[\s\S])?
|
97
|
+
(?:(?:[ _t]|[^a-z0-9])?\d+)?
|
98
|
+
(?:[\s\S][ _t]?)?)?note)
|
99
|
+
([\s\S]*?)$)/
|
100
|
+
template.sub!(note_rx, '\1\3\2')
|
101
|
+
output = Doing::TemplateString.new(template,
|
102
|
+
color: flag,
|
103
|
+
placeholders: placeholders,
|
104
|
+
reset: reset,
|
105
|
+
tags_color: opt[:tags_color],
|
106
|
+
wrap_width: opt[:wrap_width]).colored
|
107
|
+
|
108
|
+
output.gsub!(/(?<!\\)%(\S)?hr(_under)?/) do
|
158
109
|
o = ''
|
159
|
-
|
160
|
-
|
110
|
+
TTY::Screen.columns.to_i.times do
|
111
|
+
char = Regexp.last_match(2).nil? ? '-' : '_'
|
112
|
+
char = Regexp.last_match(1).nil? ? char : Regexp.last_match(1)
|
113
|
+
o += char
|
161
114
|
end
|
162
115
|
o
|
163
116
|
end
|
@@ -170,7 +123,11 @@ module Doing
|
|
170
123
|
end
|
171
124
|
|
172
125
|
# Doing.logger.debug('Template Export:', "#{items.count} items output to template #{opt[:template]}")
|
173
|
-
|
126
|
+
if opt[:totals]
|
127
|
+
out += wwid.tag_times(format: wwid.config['timer_format'].to_sym,
|
128
|
+
sort_by_name: opt[:sort_tags],
|
129
|
+
sort_order: opt[:tag_order])
|
130
|
+
end
|
174
131
|
out
|
175
132
|
end
|
176
133
|
|
@@ -61,7 +61,19 @@ module Doing
|
|
61
61
|
title.strip!
|
62
62
|
new_entry = Item.new(start_time, title, section)
|
63
63
|
new_entry.note = entry['notes'].split(/\n/).map(&:chomp) if entry.key?('notes')
|
64
|
-
|
64
|
+
|
65
|
+
is_match = true
|
66
|
+
|
67
|
+
if options[:search]
|
68
|
+
is_match = new_entry.search(options[:search], case_type: options[:date], negate: options[:not])
|
69
|
+
end
|
70
|
+
|
71
|
+
if is_match && options[:date_filter]
|
72
|
+
is_match = start_time > options[:date_filter][0] && start_time < options[:date_filter][1]
|
73
|
+
is_match = options[:not] ? !is_match : is_match
|
74
|
+
end
|
75
|
+
|
76
|
+
new_items.push(new_entry) if is_match
|
65
77
|
end
|
66
78
|
total = new_items.count
|
67
79
|
|
@@ -68,7 +68,18 @@ module Doing
|
|
68
68
|
new_item = Item.new(item.date, title, section)
|
69
69
|
new_item.note = item.note
|
70
70
|
|
71
|
-
|
71
|
+
is_match = true
|
72
|
+
|
73
|
+
if options[:search]
|
74
|
+
is_match = new_item.search(options[:search], case_type: options[:case], negate: options[:not])
|
75
|
+
end
|
76
|
+
|
77
|
+
if is_match && options[:date_filter]
|
78
|
+
is_match = new_item.date > options[:date_filter][0] && new_item.date < options[:date_filter][1]
|
79
|
+
is_match = options[:not] ? !is_match : is_match
|
80
|
+
end
|
81
|
+
|
82
|
+
imported.push(new_item) if is_match
|
72
83
|
end
|
73
84
|
|
74
85
|
dups = new_items.count - imported.count
|
@@ -63,7 +63,19 @@ module Doing
|
|
63
63
|
title.strip!
|
64
64
|
new_item = Item.new(start_time, title, section)
|
65
65
|
new_item.note.add(entry['notes']) if entry.key?('notes')
|
66
|
-
|
66
|
+
|
67
|
+
is_match = true
|
68
|
+
|
69
|
+
if options[:search]
|
70
|
+
is_match = new_item.search(options[:search], case_type: options[:case], negate: options[:not])
|
71
|
+
end
|
72
|
+
|
73
|
+
if is_match && options[:date_filter]
|
74
|
+
is_match = start_time > options[:date_filter][0] && start_time < options[:date_filter][1]
|
75
|
+
is_match = options[:not] ? !is_match : is_match
|
76
|
+
end
|
77
|
+
|
78
|
+
new_items.push(new_item) if is_match
|
67
79
|
end
|
68
80
|
total = new_items.count
|
69
81
|
skipped = data.count - total
|
data/lib/doing/prompt.rb
CHANGED
@@ -23,6 +23,59 @@ module Doing
|
|
23
23
|
$stdin.gets.strip
|
24
24
|
end
|
25
25
|
|
26
|
+
def read_line(prompt: 'Enter text', completions: [], default_response: '')
|
27
|
+
return default_response if @default_answer
|
28
|
+
|
29
|
+
unless completions.empty?
|
30
|
+
completions.sort!
|
31
|
+
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
32
|
+
Readline.completion_append_character = " "
|
33
|
+
Readline.completion_proc = comp
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
Readline.readline("#{yellow(prompt).sub(/:?$/, ':')} #{reset}", true).strip
|
38
|
+
rescue Interrupt
|
39
|
+
raise UserCancelled
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_lines(prompt: 'Enter text', completions: [])
|
44
|
+
return default_response if @default_answer
|
45
|
+
|
46
|
+
completions.sort!
|
47
|
+
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
48
|
+
Readline.completion_append_character = " "
|
49
|
+
Readline.completion_proc = comp
|
50
|
+
|
51
|
+
puts "#{boldgreen(prompt.sub(/:?$/, ':'))} #{yellow('Hit return for a new line, ')}#{boldwhite('enter a blank line (')}#{boldyellow('return twice')}#{boldwhite(') to end editing')}"
|
52
|
+
|
53
|
+
res = []
|
54
|
+
|
55
|
+
begin
|
56
|
+
while line = Readline.readline('> ', true)
|
57
|
+
break if line.strip.empty?
|
58
|
+
res << line.chomp
|
59
|
+
end
|
60
|
+
rescue Interrupt
|
61
|
+
raise UserCancelled
|
62
|
+
end
|
63
|
+
|
64
|
+
res.join("\n").strip
|
65
|
+
end
|
66
|
+
|
67
|
+
def request_lines(prompt: 'Enter text')
|
68
|
+
ask_note = []
|
69
|
+
reader = TTY::Reader.new(interrupt: -> { raise Errors::UserCancelled }, track_history: false)
|
70
|
+
puts "#{boldgreen(prompt.sub(/:?$/, ':'))} #{yellow('Hit return for a new line, ')}#{boldwhite('enter a blank line (')}#{boldyellow('return twice')}#{boldwhite(') to end editing')}"
|
71
|
+
loop do
|
72
|
+
res = reader.read_line(green('> '))
|
73
|
+
break if res.strip.empty?
|
74
|
+
|
75
|
+
ask_note.push(res)
|
76
|
+
end
|
77
|
+
ask_note.join("\n").strip
|
78
|
+
end
|
26
79
|
|
27
80
|
##
|
28
81
|
## Ask a yes or no question in the terminal
|
@@ -205,7 +258,7 @@ module Doing
|
|
205
258
|
out = [
|
206
259
|
format("%#{pad}d", i),
|
207
260
|
') ',
|
208
|
-
format('%
|
261
|
+
format('%16s', item.date.strftime('%Y-%m-%d %H:%M')),
|
209
262
|
' | ',
|
210
263
|
item.title
|
211
264
|
]
|
data/lib/doing/string.rb
CHANGED
@@ -259,7 +259,13 @@ module Doing
|
|
259
259
|
end
|
260
260
|
end
|
261
261
|
|
262
|
-
|
262
|
+
##
|
263
|
+
## Pluralize a string based on quantity
|
264
|
+
##
|
265
|
+
## @param number [Integer] the quantity of the
|
266
|
+
## object the string represents
|
267
|
+
##
|
268
|
+
def to_p(number)
|
263
269
|
number == 1 ? self : "#{self}s"
|
264
270
|
end
|
265
271
|
|
@@ -268,10 +274,6 @@ module Doing
|
|
268
274
|
##
|
269
275
|
## @return [Symbol] :oldest or :newest
|
270
276
|
##
|
271
|
-
def normalize_age!(default = :newest)
|
272
|
-
replace normalize_age(default)
|
273
|
-
end
|
274
|
-
|
275
277
|
def normalize_age(default = :newest)
|
276
278
|
case self
|
277
279
|
when /^o/i
|
@@ -283,6 +285,11 @@ module Doing
|
|
283
285
|
end
|
284
286
|
end
|
285
287
|
|
288
|
+
## @see #normalize_age
|
289
|
+
def normalize_age!(default = :newest)
|
290
|
+
replace normalize_age(default)
|
291
|
+
end
|
292
|
+
|
286
293
|
##
|
287
294
|
## Convert a sort order string to a qualified type
|
288
295
|
##
|
@@ -308,10 +315,6 @@ module Doing
|
|
308
315
|
##
|
309
316
|
## @return Symbol :smart, :sensitive, :ignore
|
310
317
|
##
|
311
|
-
def normalize_case!
|
312
|
-
replace normalize_case
|
313
|
-
end
|
314
|
-
|
315
318
|
def normalize_case(default = :smart)
|
316
319
|
case self
|
317
320
|
when /^(c|sens)/i
|
@@ -325,15 +328,16 @@ module Doing
|
|
325
328
|
end
|
326
329
|
end
|
327
330
|
|
331
|
+
## @see #normalize_case
|
332
|
+
def normalize_case!
|
333
|
+
replace normalize_case
|
334
|
+
end
|
335
|
+
|
328
336
|
##
|
329
337
|
## Convert a boolean string to a symbol
|
330
338
|
##
|
331
339
|
## @return Symbol :and, :or, or :not
|
332
340
|
##
|
333
|
-
def normalize_bool!(default = :and)
|
334
|
-
replace normalize_bool(default)
|
335
|
-
end
|
336
|
-
|
337
341
|
def normalize_bool(default = :and)
|
338
342
|
case self
|
339
343
|
when /(and|all)/i
|
@@ -349,15 +353,19 @@ module Doing
|
|
349
353
|
end
|
350
354
|
end
|
351
355
|
|
356
|
+
## @see #normalize_bool
|
357
|
+
def normalize_bool!(default = :and)
|
358
|
+
replace normalize_bool(default)
|
359
|
+
end
|
360
|
+
|
352
361
|
##
|
353
362
|
## Convert a matching configuration string to a symbol
|
354
363
|
##
|
364
|
+
## @param default [Symbol] the default matching
|
365
|
+
## type to return if the string
|
366
|
+
## doesn't match a known symbol
|
355
367
|
## @return Symbol :fuzzy, :pattern, :exact
|
356
368
|
##
|
357
|
-
def normalize_matching!(default = :pattern)
|
358
|
-
replace normalize_bool(default)
|
359
|
-
end
|
360
|
-
|
361
369
|
def normalize_matching(default = :pattern)
|
362
370
|
case self
|
363
371
|
when /^f/i
|
@@ -371,30 +379,64 @@ module Doing
|
|
371
379
|
end
|
372
380
|
end
|
373
381
|
|
374
|
-
|
375
|
-
|
382
|
+
## @see #normalize_matching
|
383
|
+
def normalize_matching!(default = :pattern)
|
384
|
+
replace normalize_bool(default)
|
376
385
|
end
|
377
386
|
|
387
|
+
##
|
388
|
+
## Adds ?: to any parentheticals in a regular expression
|
389
|
+
## to avoid match groups
|
390
|
+
##
|
391
|
+
## @return [String] modified regular expression
|
392
|
+
##
|
378
393
|
def normalize_trigger
|
379
394
|
gsub(/\((?!\?:)/, '(?:').downcase
|
380
395
|
end
|
381
396
|
|
397
|
+
## @see #normalize_trigger
|
398
|
+
def normalize_trigger!
|
399
|
+
replace normalize_trigger
|
400
|
+
end
|
401
|
+
|
402
|
+
##
|
403
|
+
## Convert ? and * wildcards to regular expressions.
|
404
|
+
## Uses \S (non-whitespace) instead of . (any character)
|
405
|
+
##
|
406
|
+
## @return [String] Regular expression string
|
407
|
+
##
|
382
408
|
def wildcard_to_rx
|
383
409
|
gsub(/\?/, '\S').gsub(/\*/, '\S*?')
|
384
410
|
end
|
385
411
|
|
412
|
+
##
|
413
|
+
## Add @ prefix to string if needed, maintains +/- prefix
|
414
|
+
##
|
415
|
+
## @return [String] @string
|
416
|
+
##
|
386
417
|
def add_at
|
387
418
|
strip.sub(/^([+-]*)@/, '\1')
|
388
419
|
end
|
389
420
|
|
421
|
+
##
|
422
|
+
## Convert a list of tags to an array. Tags can be with
|
423
|
+
## or without @ symbols, separated by any character, and
|
424
|
+
## can include parenthetical values (with spaces)
|
425
|
+
##
|
426
|
+
## @return [Array] array of tags including @ symbols
|
427
|
+
##
|
390
428
|
def to_tags
|
391
|
-
gsub(/ *, */, ' ').
|
392
|
-
end
|
393
|
-
|
394
|
-
def add_tags!(tags, remove: false)
|
395
|
-
replace add_tags(tags, remove: remove)
|
429
|
+
gsub(/ *, */, ' ').scan(/(@?(?:\S+(?:\(.+\)))|@?(?:\S+))/).map(&:first).sort.uniq.map(&:add_at)
|
396
430
|
end
|
397
431
|
|
432
|
+
##
|
433
|
+
## @brief Adds tags to a string
|
434
|
+
##
|
435
|
+
## @param tags [String or Array] List of tags to add. @ symbol optional
|
436
|
+
## @param remove [Boolean] remove tags instead of adding
|
437
|
+
##
|
438
|
+
## @return [String] the tagged string
|
439
|
+
##
|
398
440
|
def add_tags(tags, remove: false)
|
399
441
|
title = self.dup
|
400
442
|
tags = tags.to_tags
|
@@ -402,6 +444,11 @@ module Doing
|
|
402
444
|
title
|
403
445
|
end
|
404
446
|
|
447
|
+
## @see #add_tags
|
448
|
+
def add_tags!(tags, remove: false)
|
449
|
+
replace add_tags(tags, remove: remove)
|
450
|
+
end
|
451
|
+
|
405
452
|
##
|
406
453
|
## Add, rename, or remove a tag in place
|
407
454
|
##
|
@@ -484,10 +531,6 @@ module Doing
|
|
484
531
|
##
|
485
532
|
## @return Deduplicated string
|
486
533
|
##
|
487
|
-
def dedup_tags!
|
488
|
-
replace dedup_tags
|
489
|
-
end
|
490
|
-
|
491
534
|
def dedup_tags
|
492
535
|
title = dup
|
493
536
|
tags = title.scan(/(?<=\A| )(@(\S+?)(\([^)]+\))?)(?= |\Z)/).uniq
|
@@ -505,6 +548,11 @@ module Doing
|
|
505
548
|
title
|
506
549
|
end
|
507
550
|
|
551
|
+
## @see #dedup_tags
|
552
|
+
def dedup_tags!
|
553
|
+
replace dedup_tags
|
554
|
+
end
|
555
|
+
|
508
556
|
# Returns the last escape sequence from a string.
|
509
557
|
#
|
510
558
|
# Actually returns all escape codes, with the assumption
|
@@ -525,11 +573,9 @@ module Doing
|
|
525
573
|
##
|
526
574
|
## @param opt [Hash] Additional Options
|
527
575
|
##
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
end
|
532
|
-
|
576
|
+
## @option opt [Symbol] :format can be :markdown or
|
577
|
+
## :html (default)
|
578
|
+
##
|
533
579
|
def link_urls(**opt)
|
534
580
|
fmt = opt.fetch(:format, :html)
|
535
581
|
return self unless fmt
|
@@ -541,6 +587,12 @@ module Doing
|
|
541
587
|
str.replace_qualified_urls(format: fmt).clean_unlinked_urls
|
542
588
|
end
|
543
589
|
|
590
|
+
## @see #link_urls
|
591
|
+
def link_urls!(**opt)
|
592
|
+
fmt = opt.fetch(:format, :html)
|
593
|
+
replace link_urls(format: fmt)
|
594
|
+
end
|
595
|
+
|
544
596
|
# Remove <self-linked> formatting
|
545
597
|
def remove_self_links
|
546
598
|
gsub(/<(.*?)>/) do |match|
|
@@ -590,6 +642,18 @@ module Doing
|
|
590
642
|
end
|
591
643
|
end
|
592
644
|
|
645
|
+
##
|
646
|
+
## Convert a string value to an appropriate type. If
|
647
|
+
## kind is not specified, '[one, two]' becomes an Array,
|
648
|
+
## '1' becomes Integer, '1.5' becomes Float, 'true' or
|
649
|
+
## 'yes' becomes TrueClass, 'false' or 'no' becomes
|
650
|
+
## FalseClass.
|
651
|
+
##
|
652
|
+
## @param kind [String] specify string, array,
|
653
|
+
## integer, float, symbol, or boolean
|
654
|
+
## (falls back to string if value is
|
655
|
+
## not recognized)
|
656
|
+
## @return Converted object type
|
593
657
|
def set_type(kind = nil)
|
594
658
|
if kind
|
595
659
|
case kind.to_s
|