doing 2.1.1pre → 2.1.5pre
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/CHANGELOG.md +58 -8
- data/Gemfile.lock +25 -1
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/bin/doing +447 -149
- data/doc/Array.html +63 -1
- data/doc/BooleanTermParser/Clause.html +293 -0
- data/doc/BooleanTermParser/Operator.html +172 -0
- data/doc/BooleanTermParser/Query.html +417 -0
- data/doc/BooleanTermParser/QueryParser.html +135 -0
- data/doc/BooleanTermParser/QueryTransformer.html +124 -0
- data/doc/BooleanTermParser.html +115 -0
- data/doc/Doing/CLIFormat.html +131 -0
- data/doc/Doing/Color.html +2 -2
- data/doc/Doing/Completion.html +1 -1
- data/doc/Doing/Configuration.html +168 -73
- data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/doc/Doing/Errors/EmptyInput.html +1 -1
- data/doc/Doing/Errors/NoResults.html +1 -1
- data/doc/Doing/Errors/PluginException.html +1 -1
- data/doc/Doing/Errors/UserCancelled.html +1 -1
- data/doc/Doing/Errors/WrongCommand.html +1 -1
- data/doc/Doing/Errors.html +1 -1
- data/doc/Doing/Hooks.html +1 -1
- data/doc/Doing/Item.html +177 -86
- data/doc/Doing/Items.html +36 -2
- data/doc/Doing/LogAdapter.html +70 -1
- data/doc/Doing/Note.html +5 -134
- data/doc/Doing/Pager.html +1 -1
- data/doc/Doing/Plugins.html +380 -40
- data/doc/Doing/Prompt.html +70 -18
- data/doc/Doing/Section.html +1 -1
- data/doc/Doing/TemplateString.html +713 -0
- data/doc/Doing/Util/Backup.html +686 -0
- data/doc/Doing/Util.html +16 -4
- data/doc/Doing/WWID.html +133 -73
- data/doc/Doing.html +4 -4
- data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/doc/GLI/Commands.html +1 -1
- data/doc/GLI.html +1 -1
- data/doc/Hash.html +1 -1
- data/doc/PhraseParser/Operator.html +172 -0
- data/doc/PhraseParser/PhraseClause.html +303 -0
- data/doc/PhraseParser/Query.html +495 -0
- data/doc/PhraseParser/QueryParser.html +136 -0
- data/doc/PhraseParser/QueryTransformer.html +124 -0
- data/doc/PhraseParser/TermClause.html +293 -0
- data/doc/PhraseParser.html +115 -0
- data/doc/Status.html +1 -1
- data/doc/String.html +319 -13
- data/doc/Symbol.html +35 -1
- data/doc/Time.html +70 -2
- data/doc/_index.html +132 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +2 -2
- data/doc/index.html +2 -2
- data/doc/method_list.html +648 -160
- data/doc/top-level-namespace.html +2 -2
- data/doing.gemspec +3 -0
- data/doing.rdoc +263 -82
- data/lib/completion/doing.bash +18 -18
- data/lib/doing/array.rb +9 -0
- data/lib/doing/boolean_term_parser.rb +86 -0
- data/lib/doing/configuration.rb +63 -24
- data/lib/doing/item.rb +112 -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 +57 -13
- data/lib/doing/plugins/export/dayone_export.rb +209 -0
- data/lib/doing/plugins/export/template_export.rb +113 -81
- data/lib/doing/prompt.rb +26 -13
- 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 +152 -55
- data/lib/doing.rb +9 -0
- data/lib/templates/doing-dayone-entry.erb +6 -0
- data/lib/templates/doing-dayone.erb +5 -0
- metadata +85 -2
|
@@ -23,116 +23,148 @@ module Doing
|
|
|
23
23
|
out = ''
|
|
24
24
|
items.each do |item|
|
|
25
25
|
if opt[:highlight] && item.title =~ /@#{wwid.config['marker_tag']}\b/i
|
|
26
|
-
|
|
27
|
-
reset = Doing::Color.default
|
|
26
|
+
flag = Doing::Color.send(wwid.config['marker_color'])
|
|
27
|
+
reset = Doing::Color.reset + Doing::Color.default
|
|
28
28
|
else
|
|
29
|
-
|
|
29
|
+
flag = ''
|
|
30
30
|
reset = ''
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
placeholders = {}
|
|
34
|
+
|
|
33
35
|
if (!item.note.empty?) && wwid.config['include_notes']
|
|
34
36
|
note = item.note.map(&:strip).delete_if(&:empty?)
|
|
35
37
|
note.map! { |line| "#{line.sub(/^\t*/, '')} " }
|
|
36
38
|
|
|
37
39
|
if opt[:wrap_width]&.positive?
|
|
38
40
|
width = opt[:wrap_width]
|
|
39
|
-
note.map!
|
|
40
|
-
|
|
41
|
+
note.map! do |line|
|
|
42
|
+
line.simple_wrap(width)
|
|
43
|
+
# line.chomp.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
|
|
44
|
+
end
|
|
45
|
+
note = note.delete_if(&:empty?)
|
|
41
46
|
end
|
|
42
47
|
else
|
|
43
48
|
note = []
|
|
44
49
|
end
|
|
45
50
|
|
|
46
|
-
output
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
placeholders['date'] = item.date.strftime(opt[:format])
|
|
56
|
+
|
|
57
|
+
interval = wwid.get_interval(item, record: true, formatted: false) if opt[:times]
|
|
58
|
+
if interval
|
|
59
|
+
case opt[:interval_format].to_sym
|
|
60
|
+
when :human
|
|
61
|
+
_d, h, m = wwid.format_time(interval, human: true)
|
|
62
|
+
interval = format('%<h> 4dh %<m>02dm', h: h, m: m)
|
|
51
63
|
else
|
|
52
|
-
m
|
|
64
|
+
d, h, m = wwid.format_time(interval)
|
|
65
|
+
interval = format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)
|
|
53
66
|
end
|
|
54
67
|
end
|
|
55
68
|
|
|
56
|
-
output.sub!(/%(\d+)?date/) do
|
|
57
|
-
pad = Regexp.last_match(1).to_i
|
|
58
|
-
format("%#{pad}s", item.date.strftime(opt[:format]))
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
interval = wwid.get_interval(item, record: true) if opt[:times]
|
|
62
|
-
|
|
63
69
|
interval ||= ''
|
|
64
|
-
output.sub!(/%interval/, interval)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
title_rx = /(?mi)%(?<width>-?\d+)?(?:(?<ichar>[ _t])(?<icount>\d+))?(?<prefix>.[ _t]?)?title(?<after>.*?)$/
|
|
74
|
-
title_color = Doing::Color.reset + output.match(/(?mi)^(.*?)(%.*?title)/)[1].last_color
|
|
75
|
-
|
|
76
|
-
title_offset = Doing::Color.uncolor(output).match(title_rx).begin(0)
|
|
77
|
-
|
|
78
|
-
output.sub!(title_rx) do
|
|
79
|
-
m = Regexp.last_match
|
|
80
|
-
|
|
81
|
-
after = m['after']
|
|
82
|
-
pad = m['width'].to_i
|
|
83
|
-
indent = ''
|
|
84
|
-
if m['ichar']
|
|
85
|
-
char = m['ichar'] =~ /t/ ? "\t" : ' '
|
|
86
|
-
indent = char * m['icount'].to_i
|
|
87
|
-
end
|
|
88
|
-
prefix = m['prefix']
|
|
89
|
-
if opt[:wrap_width]&.positive? || pad.positive?
|
|
90
|
-
width = pad.positive? ? pad : opt[:wrap_width]
|
|
91
|
-
item.title.wrap(width, pad: pad, indent: indent, offset: title_offset, prefix: prefix, color: title_color, after: after, reset: reset)
|
|
92
|
-
# flag + item.title.gsub(/(.{#{opt[:wrap_width]}})(?=\s+|\Z)/, "\\1\n ").sub(/\s*$/, '') + reset
|
|
70
|
+
# output.sub!(/%interval/, interval)
|
|
71
|
+
placeholders['interval'] = interval
|
|
72
|
+
|
|
73
|
+
duration = item.duration if opt[:duration]
|
|
74
|
+
if duration
|
|
75
|
+
case opt[:interval_format].to_sym
|
|
76
|
+
when :human
|
|
77
|
+
_d, h, m = wwid.format_time(duration, human: true)
|
|
78
|
+
duration = format('%<h> 4dh %<m>02dm', h: h, m: m)
|
|
93
79
|
else
|
|
94
|
-
|
|
80
|
+
d, h, m = wwid.format_time(duration)
|
|
81
|
+
duration = format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)
|
|
95
82
|
end
|
|
96
83
|
end
|
|
97
|
-
|
|
98
|
-
# output.sub!(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
84
|
+
duration ||= ''
|
|
85
|
+
# output.sub!(/%duration/, duration)
|
|
86
|
+
placeholders['duration'] = duration
|
|
87
|
+
|
|
88
|
+
# output.sub!(/%(\d+)?shortdate/) do
|
|
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
|
|
94
|
+
placeholders['section'] = item.section || ''
|
|
95
|
+
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
|
+
placeholders['note'] = note
|
|
125
|
+
placeholders['idnote'] = note.empty? ? '' : "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}"
|
|
126
|
+
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
|
+
|
|
153
|
+
template = opt[:template].dup
|
|
154
|
+
template.sub!(/(?i-m)^([\s\S]*?)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)([\s\S]*?)$/, '\1\3\2')
|
|
155
|
+
output = Doing::TemplateString.new(template, placeholders: placeholders, wrap_width: opt[:wrap_width], color: flag, tags_color: opt[:tags_color], reset: reset).colored
|
|
156
|
+
|
|
157
|
+
output.gsub!(/(?<!\\)%hr(_under)?/) do
|
|
128
158
|
o = ''
|
|
129
159
|
`tput cols`.to_i.times do
|
|
130
160
|
o += Regexp.last_match(1).nil? ? '-' : '_'
|
|
131
161
|
end
|
|
132
162
|
o
|
|
133
163
|
end
|
|
134
|
-
output.gsub!(
|
|
135
|
-
output.gsub!(
|
|
164
|
+
output.gsub!(/(?<!\\)%n/, "\n")
|
|
165
|
+
output.gsub!(/(?<!\\)%t/, "\t")
|
|
166
|
+
|
|
167
|
+
output.gsub!(/\\%/, '%')
|
|
136
168
|
|
|
137
169
|
out += "#{output}\n"
|
|
138
170
|
end
|
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,12 +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
|
-
|
|
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
|
|
115
126
|
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
|
116
|
-
|
|
127
|
+
default_args << %(--header="#{header}")
|
|
128
|
+
default_args.concat(fzf_args)
|
|
117
129
|
options.sort! if sorted
|
|
118
|
-
|
|
130
|
+
|
|
131
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{default_args.join(' ')}`
|
|
119
132
|
return false if res.strip.size.zero?
|
|
120
133
|
|
|
121
134
|
res
|
|
@@ -125,16 +138,16 @@ module Doing
|
|
|
125
138
|
## Create an interactive menu to select from a set of Items
|
|
126
139
|
##
|
|
127
140
|
## @param items [Array] list of items
|
|
128
|
-
## @param opt
|
|
129
|
-
## @param include_section [Boolean] include section
|
|
141
|
+
## @param opt Additional options
|
|
130
142
|
##
|
|
131
|
-
## @option opt [
|
|
132
|
-
## @option opt [String] :
|
|
133
|
-
## @option opt [String] :
|
|
134
|
-
## @option opt [
|
|
135
|
-
## @option opt [Boolean] :menu
|
|
136
|
-
## @option opt [Boolean] :
|
|
137
|
-
## @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
|
|
138
151
|
## @option opt [Symbol] :case (:sensitive, :ignore, :smart)
|
|
139
152
|
##
|
|
140
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
|
|