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