doing 2.1.13 → 2.1.17
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 +14 -12
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +76 -0
- data/Gemfile.lock +9 -2
- data/README.md +56 -19
- data/bin/doing +218 -68
- 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 +6 -2
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +8 -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 +340 -14
- data/docs/doc/Doing/Items.html +2 -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 +1 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +77 -71
- 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 +997 -118
- 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 +449 -305
- data/docs/doc/top-level-namespace.html +2 -2
- data/docs/index.md +56 -19
- data/doing.gemspec +2 -0
- data/doing.rdoc +76 -9
- data/example_plugin.rb +2 -4
- data/lib/completion/_doing.zsh +17 -17
- data/lib/completion/doing.bash +25 -25
- data/lib/completion/doing.fish +18 -6
- data/lib/doing/array_chronify.rb +57 -0
- data/lib/doing/colors.rb +4 -0
- data/lib/doing/configuration.rb +6 -2
- data/lib/doing/item.rb +108 -0
- data/lib/doing/log_adapter.rb +3 -3
- 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 +49 -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 +65 -1
- data/lib/doing/string.rb +137 -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 +117 -106
- 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
|
@@ -166,11 +119,17 @@ module Doing
|
|
166
119
|
|
167
120
|
output.gsub!(/\\%/, '%')
|
168
121
|
|
122
|
+
output.highlight_search!(opt[:search]) if opt[:search] && !opt[:not] && opt[:hilite]
|
123
|
+
|
169
124
|
out += "#{output}\n"
|
170
125
|
end
|
171
126
|
|
172
127
|
# Doing.logger.debug('Template Export:', "#{items.count} items output to template #{opt[:template]}")
|
173
|
-
|
128
|
+
if opt[:totals]
|
129
|
+
out += wwid.tag_times(format: wwid.config['timer_format'].to_sym,
|
130
|
+
sort_by_name: opt[:sort_tags],
|
131
|
+
sort_order: opt[:tag_order])
|
132
|
+
end
|
174
133
|
out
|
175
134
|
end
|
176
135
|
|
@@ -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
@@ -17,12 +17,74 @@ module Doing
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def enter_text(prompt, default_response: '')
|
20
|
+
$stdin.reopen('/dev/tty')
|
20
21
|
return default_response if @default_answer
|
21
22
|
|
22
23
|
print "#{yellow(prompt).sub(/:?$/, ':')} #{reset}"
|
23
24
|
$stdin.gets.strip
|
24
25
|
end
|
25
26
|
|
27
|
+
def read_line(prompt: 'Enter text', completions: [], default_response: '')
|
28
|
+
$stdin.reopen('/dev/tty')
|
29
|
+
return default_response if @default_answer
|
30
|
+
|
31
|
+
unless completions.empty?
|
32
|
+
completions.sort!
|
33
|
+
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
34
|
+
Readline.completion_append_character = ' '
|
35
|
+
Readline.completion_proc = comp
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
Readline.readline("#{yellow(prompt).sub(/:?$/, ':')} #{reset}", true).strip
|
40
|
+
rescue Interrupt
|
41
|
+
raise UserCancelled
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_lines(prompt: 'Enter text', completions: [])
|
46
|
+
$stdin.reopen('/dev/tty')
|
47
|
+
return default_response if @default_answer
|
48
|
+
|
49
|
+
completions.sort!
|
50
|
+
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
51
|
+
Readline.completion_append_character = ' '
|
52
|
+
Readline.completion_proc = comp
|
53
|
+
prompt_text = []
|
54
|
+
prompt_text << boldgreen(prompt.sub(/:?$/, ':'))
|
55
|
+
prompt_text << yellow(' Enter a blank line (')
|
56
|
+
prompt_text << boldwhite('return twice')
|
57
|
+
prompt_text << yellow(') to end editing')
|
58
|
+
puts prompt_text.join('')
|
59
|
+
|
60
|
+
res = []
|
61
|
+
|
62
|
+
begin
|
63
|
+
while (line = Readline.readline('> ', true))
|
64
|
+
break if line.strip.empty?
|
65
|
+
|
66
|
+
res << line.chomp
|
67
|
+
end
|
68
|
+
rescue Interrupt
|
69
|
+
raise UserCancelled
|
70
|
+
end
|
71
|
+
|
72
|
+
res.join("\n").strip
|
73
|
+
end
|
74
|
+
|
75
|
+
def request_lines(prompt: 'Enter text')
|
76
|
+
$stdin.reopen('/dev/tty')
|
77
|
+
ask_note = []
|
78
|
+
reader = TTY::Reader.new(interrupt: -> { raise Errors::UserCancelled }, track_history: false)
|
79
|
+
puts "#{boldgreen(prompt.sub(/:?$/, ':'))} #{yellow('Hit return for a new line, ')}#{boldwhite('enter a blank line (')}#{boldyellow('return twice')}#{boldwhite(') to end editing')}"
|
80
|
+
loop do
|
81
|
+
res = reader.read_line(green('> '))
|
82
|
+
break if res.strip.empty?
|
83
|
+
|
84
|
+
ask_note.push(res)
|
85
|
+
end
|
86
|
+
ask_note.join("\n").strip
|
87
|
+
end
|
26
88
|
|
27
89
|
##
|
28
90
|
## Ask a yes or no question in the terminal
|
@@ -39,6 +101,8 @@ module Doing
|
|
39
101
|
return @force_answer
|
40
102
|
end
|
41
103
|
|
104
|
+
$stdin.reopen('/dev/tty')
|
105
|
+
|
42
106
|
default = if default_response.is_a?(String)
|
43
107
|
default_response =~ /y/i ? true : false
|
44
108
|
else
|
@@ -205,7 +269,7 @@ module Doing
|
|
205
269
|
out = [
|
206
270
|
format("%#{pad}d", i),
|
207
271
|
') ',
|
208
|
-
format('%
|
272
|
+
format('%16s', item.date.strftime('%Y-%m-%d %H:%M')),
|
209
273
|
' | ',
|
210
274
|
item.title
|
211
275
|
]
|
data/lib/doing/string.rb
CHANGED
@@ -101,6 +101,46 @@ module Doing
|
|
101
101
|
gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{last_color}")
|
102
102
|
end
|
103
103
|
|
104
|
+
def to_phrase_query(query)
|
105
|
+
parser = PhraseParser::QueryParser.new
|
106
|
+
transformer = PhraseParser::QueryTransformer.new
|
107
|
+
parse_tree = parser.parse(query)
|
108
|
+
transformer.apply(parse_tree).to_elasticsearch
|
109
|
+
end
|
110
|
+
|
111
|
+
def ignore_case(search, case_type)
|
112
|
+
(case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
|
113
|
+
end
|
114
|
+
|
115
|
+
def highlight_search!(search, distance: nil, negate: false, case_type: nil)
|
116
|
+
replace highlight_search(search, distance: distance, negate: negate, case_type: case_type)
|
117
|
+
end
|
118
|
+
|
119
|
+
def highlight_search(search, distance: nil, negate: false, case_type: nil)
|
120
|
+
out = dup
|
121
|
+
prefs = Doing.config.settings['search'] || {}
|
122
|
+
matching = prefs.fetch('matching', 'pattern').normalize_matching
|
123
|
+
distance ||= prefs.fetch('distance', 3).to_i
|
124
|
+
case_type ||= prefs.fetch('case', 'smart').normalize_case
|
125
|
+
|
126
|
+
if search.is_rx? || matching == :fuzzy
|
127
|
+
rx = search.to_rx(distance: distance, case_type: case_type)
|
128
|
+
out.gsub!(rx) { |m| m.bgyellow.black }
|
129
|
+
else
|
130
|
+
query = to_phrase_query(search.strip)
|
131
|
+
|
132
|
+
if query[:must].nil? && query[:must_not].nil?
|
133
|
+
query[:must] = query[:should]
|
134
|
+
query[:should] = []
|
135
|
+
end
|
136
|
+
query[:must].concat(query[:should]).each do |s|
|
137
|
+
rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type))
|
138
|
+
out.gsub!(rx) { |m| m.bgyellow.black }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
out
|
142
|
+
end
|
143
|
+
|
104
144
|
##
|
105
145
|
## Test if line should be ignored
|
106
146
|
##
|
@@ -259,7 +299,13 @@ module Doing
|
|
259
299
|
end
|
260
300
|
end
|
261
301
|
|
262
|
-
|
302
|
+
##
|
303
|
+
## Pluralize a string based on quantity
|
304
|
+
##
|
305
|
+
## @param number [Integer] the quantity of the
|
306
|
+
## object the string represents
|
307
|
+
##
|
308
|
+
def to_p(number)
|
263
309
|
number == 1 ? self : "#{self}s"
|
264
310
|
end
|
265
311
|
|
@@ -268,10 +314,6 @@ module Doing
|
|
268
314
|
##
|
269
315
|
## @return [Symbol] :oldest or :newest
|
270
316
|
##
|
271
|
-
def normalize_age!(default = :newest)
|
272
|
-
replace normalize_age(default)
|
273
|
-
end
|
274
|
-
|
275
317
|
def normalize_age(default = :newest)
|
276
318
|
case self
|
277
319
|
when /^o/i
|
@@ -283,6 +325,11 @@ module Doing
|
|
283
325
|
end
|
284
326
|
end
|
285
327
|
|
328
|
+
## @see #normalize_age
|
329
|
+
def normalize_age!(default = :newest)
|
330
|
+
replace normalize_age(default)
|
331
|
+
end
|
332
|
+
|
286
333
|
##
|
287
334
|
## Convert a sort order string to a qualified type
|
288
335
|
##
|
@@ -308,10 +355,6 @@ module Doing
|
|
308
355
|
##
|
309
356
|
## @return Symbol :smart, :sensitive, :ignore
|
310
357
|
##
|
311
|
-
def normalize_case!
|
312
|
-
replace normalize_case
|
313
|
-
end
|
314
|
-
|
315
358
|
def normalize_case(default = :smart)
|
316
359
|
case self
|
317
360
|
when /^(c|sens)/i
|
@@ -325,15 +368,16 @@ module Doing
|
|
325
368
|
end
|
326
369
|
end
|
327
370
|
|
371
|
+
## @see #normalize_case
|
372
|
+
def normalize_case!
|
373
|
+
replace normalize_case
|
374
|
+
end
|
375
|
+
|
328
376
|
##
|
329
377
|
## Convert a boolean string to a symbol
|
330
378
|
##
|
331
379
|
## @return Symbol :and, :or, or :not
|
332
380
|
##
|
333
|
-
def normalize_bool!(default = :and)
|
334
|
-
replace normalize_bool(default)
|
335
|
-
end
|
336
|
-
|
337
381
|
def normalize_bool(default = :and)
|
338
382
|
case self
|
339
383
|
when /(and|all)/i
|
@@ -349,15 +393,19 @@ module Doing
|
|
349
393
|
end
|
350
394
|
end
|
351
395
|
|
396
|
+
## @see #normalize_bool
|
397
|
+
def normalize_bool!(default = :and)
|
398
|
+
replace normalize_bool(default)
|
399
|
+
end
|
400
|
+
|
352
401
|
##
|
353
402
|
## Convert a matching configuration string to a symbol
|
354
403
|
##
|
404
|
+
## @param default [Symbol] the default matching
|
405
|
+
## type to return if the string
|
406
|
+
## doesn't match a known symbol
|
355
407
|
## @return Symbol :fuzzy, :pattern, :exact
|
356
408
|
##
|
357
|
-
def normalize_matching!(default = :pattern)
|
358
|
-
replace normalize_bool(default)
|
359
|
-
end
|
360
|
-
|
361
409
|
def normalize_matching(default = :pattern)
|
362
410
|
case self
|
363
411
|
when /^f/i
|
@@ -371,30 +419,64 @@ module Doing
|
|
371
419
|
end
|
372
420
|
end
|
373
421
|
|
374
|
-
|
375
|
-
|
422
|
+
## @see #normalize_matching
|
423
|
+
def normalize_matching!(default = :pattern)
|
424
|
+
replace normalize_bool(default)
|
376
425
|
end
|
377
426
|
|
427
|
+
##
|
428
|
+
## Adds ?: to any parentheticals in a regular expression
|
429
|
+
## to avoid match groups
|
430
|
+
##
|
431
|
+
## @return [String] modified regular expression
|
432
|
+
##
|
378
433
|
def normalize_trigger
|
379
434
|
gsub(/\((?!\?:)/, '(?:').downcase
|
380
435
|
end
|
381
436
|
|
437
|
+
## @see #normalize_trigger
|
438
|
+
def normalize_trigger!
|
439
|
+
replace normalize_trigger
|
440
|
+
end
|
441
|
+
|
442
|
+
##
|
443
|
+
## Convert ? and * wildcards to regular expressions.
|
444
|
+
## Uses \S (non-whitespace) instead of . (any character)
|
445
|
+
##
|
446
|
+
## @return [String] Regular expression string
|
447
|
+
##
|
382
448
|
def wildcard_to_rx
|
383
449
|
gsub(/\?/, '\S').gsub(/\*/, '\S*?')
|
384
450
|
end
|
385
451
|
|
452
|
+
##
|
453
|
+
## Add @ prefix to string if needed, maintains +/- prefix
|
454
|
+
##
|
455
|
+
## @return [String] @string
|
456
|
+
##
|
386
457
|
def add_at
|
387
458
|
strip.sub(/^([+-]*)@/, '\1')
|
388
459
|
end
|
389
460
|
|
461
|
+
##
|
462
|
+
## Convert a list of tags to an array. Tags can be with
|
463
|
+
## or without @ symbols, separated by any character, and
|
464
|
+
## can include parenthetical values (with spaces)
|
465
|
+
##
|
466
|
+
## @return [Array] array of tags including @ symbols
|
467
|
+
##
|
390
468
|
def to_tags
|
391
|
-
gsub(/ *, */, ' ').
|
392
|
-
end
|
393
|
-
|
394
|
-
def add_tags!(tags, remove: false)
|
395
|
-
replace add_tags(tags, remove: remove)
|
469
|
+
gsub(/ *, */, ' ').scan(/(@?(?:\S+(?:\(.+\)))|@?(?:\S+))/).map(&:first).sort.uniq.map(&:add_at)
|
396
470
|
end
|
397
471
|
|
472
|
+
##
|
473
|
+
## @brief Adds tags to a string
|
474
|
+
##
|
475
|
+
## @param tags [String or Array] List of tags to add. @ symbol optional
|
476
|
+
## @param remove [Boolean] remove tags instead of adding
|
477
|
+
##
|
478
|
+
## @return [String] the tagged string
|
479
|
+
##
|
398
480
|
def add_tags(tags, remove: false)
|
399
481
|
title = self.dup
|
400
482
|
tags = tags.to_tags
|
@@ -402,6 +484,11 @@ module Doing
|
|
402
484
|
title
|
403
485
|
end
|
404
486
|
|
487
|
+
## @see #add_tags
|
488
|
+
def add_tags!(tags, remove: false)
|
489
|
+
replace add_tags(tags, remove: remove)
|
490
|
+
end
|
491
|
+
|
405
492
|
##
|
406
493
|
## Add, rename, or remove a tag in place
|
407
494
|
##
|
@@ -484,10 +571,6 @@ module Doing
|
|
484
571
|
##
|
485
572
|
## @return Deduplicated string
|
486
573
|
##
|
487
|
-
def dedup_tags!
|
488
|
-
replace dedup_tags
|
489
|
-
end
|
490
|
-
|
491
574
|
def dedup_tags
|
492
575
|
title = dup
|
493
576
|
tags = title.scan(/(?<=\A| )(@(\S+?)(\([^)]+\))?)(?= |\Z)/).uniq
|
@@ -505,6 +588,11 @@ module Doing
|
|
505
588
|
title
|
506
589
|
end
|
507
590
|
|
591
|
+
## @see #dedup_tags
|
592
|
+
def dedup_tags!
|
593
|
+
replace dedup_tags
|
594
|
+
end
|
595
|
+
|
508
596
|
# Returns the last escape sequence from a string.
|
509
597
|
#
|
510
598
|
# Actually returns all escape codes, with the assumption
|
@@ -525,11 +613,9 @@ module Doing
|
|
525
613
|
##
|
526
614
|
## @param opt [Hash] Additional Options
|
527
615
|
##
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
end
|
532
|
-
|
616
|
+
## @option opt [Symbol] :format can be :markdown or
|
617
|
+
## :html (default)
|
618
|
+
##
|
533
619
|
def link_urls(**opt)
|
534
620
|
fmt = opt.fetch(:format, :html)
|
535
621
|
return self unless fmt
|
@@ -541,6 +627,12 @@ module Doing
|
|
541
627
|
str.replace_qualified_urls(format: fmt).clean_unlinked_urls
|
542
628
|
end
|
543
629
|
|
630
|
+
## @see #link_urls
|
631
|
+
def link_urls!(**opt)
|
632
|
+
fmt = opt.fetch(:format, :html)
|
633
|
+
replace link_urls(format: fmt)
|
634
|
+
end
|
635
|
+
|
544
636
|
# Remove <self-linked> formatting
|
545
637
|
def remove_self_links
|
546
638
|
gsub(/<(.*?)>/) do |match|
|
@@ -590,6 +682,18 @@ module Doing
|
|
590
682
|
end
|
591
683
|
end
|
592
684
|
|
685
|
+
##
|
686
|
+
## Convert a string value to an appropriate type. If
|
687
|
+
## kind is not specified, '[one, two]' becomes an Array,
|
688
|
+
## '1' becomes Integer, '1.5' becomes Float, 'true' or
|
689
|
+
## 'yes' becomes TrueClass, 'false' or 'no' becomes
|
690
|
+
## FalseClass.
|
691
|
+
##
|
692
|
+
## @param kind [String] specify string, array,
|
693
|
+
## integer, float, symbol, or boolean
|
694
|
+
## (falls back to string if value is
|
695
|
+
## not recognized)
|
696
|
+
## @return Converted object type
|
593
697
|
def set_type(kind = nil)
|
594
698
|
if kind
|
595
699
|
case kind.to_s
|