kronk 1.5.4 → 1.6.0
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.
- data/History.rdoc +14 -0
- data/Manifest.txt +8 -2
- data/README.rdoc +6 -0
- data/TODO.rdoc +12 -0
- data/lib/kronk.rb +6 -2
- data/lib/kronk/cmd.rb +18 -2
- data/lib/kronk/constants.rb +1 -0
- data/lib/kronk/data_renderer.rb +95 -22
- data/lib/kronk/diff.rb +18 -64
- data/lib/kronk/diff/ascii_format.rb +11 -0
- data/lib/kronk/diff/color_format.rb +14 -2
- data/lib/kronk/diff/output.rb +155 -0
- data/lib/kronk/path.rb +48 -153
- data/lib/kronk/path/matcher.rb +189 -0
- data/lib/kronk/path/path_match.rb +74 -0
- data/lib/kronk/path/transaction.rb +157 -47
- data/lib/kronk/player/benchmark.rb +2 -1
- data/lib/kronk/player/suite.rb +8 -0
- data/lib/kronk/response.rb +7 -6
- data/test/test_cmd.rb +29 -8
- data/test/test_data_string.rb +58 -0
- data/test/test_diff.rb +137 -36
- data/test/test_helper.rb +2 -0
- data/test/test_kronk.rb +19 -3
- data/test/test_path.rb +87 -170
- data/test/test_path_match.rb +60 -0
- data/test/test_path_matcher.rb +329 -0
- data/test/test_response.rb +10 -10
- data/test/test_transaction.rb +132 -3
- metadata +82 -75
@@ -7,6 +7,17 @@ class Kronk
|
|
7
7
|
|
8
8
|
class AsciiFormat
|
9
9
|
|
10
|
+
|
11
|
+
def self.head left, right
|
12
|
+
["--- #{left}", "+++ #{right}"]
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def self.context left, right, info=nil
|
17
|
+
"@@ -#{left} +#{right} @@ #{info}"
|
18
|
+
end
|
19
|
+
|
20
|
+
|
10
21
|
def self.lines line_nums, col_width
|
11
22
|
out =
|
12
23
|
[*line_nums].map do |lnum|
|
@@ -17,6 +17,18 @@ class Kronk
|
|
17
17
|
end
|
18
18
|
|
19
19
|
|
20
|
+
def self.head left, right
|
21
|
+
ensure_color
|
22
|
+
["\033[1;33m--- #{left}", "+++ #{right}\033[0m"]
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def self.context left, right, info=nil
|
27
|
+
ensure_color
|
28
|
+
"\033[1;35m@@ -#{left} +#{right} @@\033[0m #{info}"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
20
32
|
def self.lines line_nums, col_width
|
21
33
|
ensure_color
|
22
34
|
|
@@ -31,13 +43,13 @@ class Kronk
|
|
31
43
|
|
32
44
|
def self.deleted str
|
33
45
|
ensure_color
|
34
|
-
"\033[31m- #{str}\033[0m"
|
46
|
+
"\033[1;31m- #{str}\033[0m"
|
35
47
|
end
|
36
48
|
|
37
49
|
|
38
50
|
def self.added str
|
39
51
|
ensure_color
|
40
|
-
"\033[32m+ #{str}\033[0m"
|
52
|
+
"\033[1;32m+ #{str}\033[0m"
|
41
53
|
end
|
42
54
|
|
43
55
|
|
@@ -0,0 +1,155 @@
|
|
1
|
+
class Kronk::Diff
|
2
|
+
|
3
|
+
class Output
|
4
|
+
|
5
|
+
##
|
6
|
+
# Returns a formatter from a symbol or string. Returns nil if not found.
|
7
|
+
|
8
|
+
def self.formatter name
|
9
|
+
return unless name
|
10
|
+
|
11
|
+
return name if Class === name
|
12
|
+
return AsciiFormat if name == :ascii_diff
|
13
|
+
return ColorFormat if name == :color_diff
|
14
|
+
Kronk.find_const name rescue name
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def self.attr_rm_cache *attrs # :nodoc:
|
19
|
+
self.send :attr_reader, *attrs
|
20
|
+
|
21
|
+
attrs.each do |attr|
|
22
|
+
define_method "#{attr}=" do |value|
|
23
|
+
if send(attr) != value
|
24
|
+
instance_variable_set("@#{attr}", value)
|
25
|
+
instance_variable_set("@cached", nil)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
attr_rm_cache :labels, :show_lines, :join_ch, :context, :format, :diff_ary
|
33
|
+
|
34
|
+
|
35
|
+
def initialize diff, opts={}
|
36
|
+
@output = []
|
37
|
+
@cached = nil
|
38
|
+
@diff = diff
|
39
|
+
|
40
|
+
@format =
|
41
|
+
self.class.formatter(opts[:format] || Kronk.config[:diff_format]) ||
|
42
|
+
AsciiFormat
|
43
|
+
|
44
|
+
@context = Kronk.config[:context]
|
45
|
+
@context = opts[:context] if opts[:context] || opts[:context] == false
|
46
|
+
|
47
|
+
@join_ch = opts[:join_char] || "\n"
|
48
|
+
|
49
|
+
@labels = Array(opts[:labels])
|
50
|
+
@labels[0] ||= "left"
|
51
|
+
@labels[1] ||= "right"
|
52
|
+
|
53
|
+
@show_lines = opts[:show_lines] || Kronk.config[:show_lines]
|
54
|
+
@record = false
|
55
|
+
|
56
|
+
lines1 = diff.str1.lines.count
|
57
|
+
lines2 = diff.str2.lines.count
|
58
|
+
@cwidth = (lines1 > lines2 ? lines1 : lines2).to_s.length
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def record? i, line1, line2
|
63
|
+
if @context
|
64
|
+
clen = @context + 1
|
65
|
+
next_diff = @diff_ary[i,clen].to_a.find{|da| Array === da}
|
66
|
+
end
|
67
|
+
|
68
|
+
if !clen || next_diff
|
69
|
+
@record || [@output.length, line1+1, line2+1, 0, 0]
|
70
|
+
|
71
|
+
elsif @record && clen && !next_diff
|
72
|
+
scheck = @output.length - (clen - 1)
|
73
|
+
subary = @output[scheck..-1].to_a
|
74
|
+
|
75
|
+
if i == @diff_ary.length || !subary.find{|da| Array === da}
|
76
|
+
start = @record[0]
|
77
|
+
cleft = "#{@record[1]},#{@record[3]}"
|
78
|
+
cright = "#{@record[2]},#{@record[4]}"
|
79
|
+
|
80
|
+
str = @output[start]
|
81
|
+
str = str[0][0] if Array === str
|
82
|
+
info = str.meta[0] if str.respond_to?(:meta)
|
83
|
+
|
84
|
+
@output[start,0] = @format.context cleft, cright, info
|
85
|
+
false
|
86
|
+
|
87
|
+
else
|
88
|
+
@record
|
89
|
+
end
|
90
|
+
|
91
|
+
else
|
92
|
+
@record
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def render force=false
|
98
|
+
self.diff_ary = @diff.diff_array
|
99
|
+
|
100
|
+
return @cached if !force && @cached
|
101
|
+
@output << @format.head(*@labels)
|
102
|
+
|
103
|
+
line1 = line2 = 0
|
104
|
+
|
105
|
+
0.upto(@diff_ary.length) do |i|
|
106
|
+
item = @diff_ary[i]
|
107
|
+
@record = record? i, line1, line2
|
108
|
+
|
109
|
+
case item
|
110
|
+
when String
|
111
|
+
line1 = line1.next
|
112
|
+
line2 = line2.next
|
113
|
+
@output << make_line(item, line1, line2) if @record
|
114
|
+
|
115
|
+
when Array
|
116
|
+
sides = [[],[]]
|
117
|
+
|
118
|
+
item[0].each do |ditem|
|
119
|
+
line1 = line1.next
|
120
|
+
sides[0] << make_line(ditem, line1, nil) if @record
|
121
|
+
end
|
122
|
+
|
123
|
+
item[1].each do |ditem|
|
124
|
+
line2 = line2.next
|
125
|
+
sides[0] << make_line(ditem, nil, line2) if @record
|
126
|
+
end
|
127
|
+
|
128
|
+
@output << sides if @record
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
@cached = @output.flatten.join(@join_ch)
|
133
|
+
end
|
134
|
+
|
135
|
+
alias to_s render
|
136
|
+
|
137
|
+
|
138
|
+
def make_line item, line1, line2
|
139
|
+
action = if line1 && !line2
|
140
|
+
:deleted
|
141
|
+
elsif !line1 && line2
|
142
|
+
:added
|
143
|
+
else
|
144
|
+
:common
|
145
|
+
end
|
146
|
+
|
147
|
+
lines = @format.lines [line1, line2], @cwidth if @show_lines
|
148
|
+
line = "#{lines}#{@format.send action, item}"
|
149
|
+
line = Kronk::DataString.new line, item.meta[0] if item.respond_to? :meta
|
150
|
+
@record[3] += 1 if line1
|
151
|
+
@record[4] += 1 if line2
|
152
|
+
line
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/kronk/path.rb
CHANGED
@@ -38,9 +38,6 @@ class Kronk
|
|
38
38
|
# Used as path instruction to go up one path level.
|
39
39
|
module PARENT; end
|
40
40
|
|
41
|
-
# Used as path item value to match any key or value.
|
42
|
-
module ANY_VALUE; end
|
43
|
-
|
44
41
|
# Mapping of letters to Regexp options.
|
45
42
|
REGEX_OPTS = {
|
46
43
|
"i" => Regexp::IGNORECASE,
|
@@ -49,19 +46,12 @@ class Kronk
|
|
49
46
|
"x" => Regexp::EXTENDED
|
50
47
|
}
|
51
48
|
|
52
|
-
# Shortcut characters that require modification before being turned into
|
53
|
-
# a matcher.
|
54
|
-
SUFF_CHARS = Regexp.escape "*?"
|
55
|
-
|
56
|
-
# All special path characters.
|
57
|
-
PATH_CHARS = Regexp.escape("()|") << SUFF_CHARS
|
58
|
-
|
59
|
-
# Path chars that get regexp escaped.
|
60
|
-
RESC_CHARS = "*?()|/"
|
61
|
-
|
62
49
|
# The path item delimiter character "/"
|
63
50
|
DCH = "/"
|
64
51
|
|
52
|
+
# The replacement character "%" for path mapping
|
53
|
+
RCH = "%"
|
54
|
+
|
65
55
|
# The path character to assign value "="
|
66
56
|
VCH = "="
|
67
57
|
|
@@ -80,26 +70,14 @@ class Kronk
|
|
80
70
|
# The key string that indicates recursive lookup.
|
81
71
|
RECUR_KEY = "**"
|
82
72
|
|
83
|
-
# Matcher for Range path item.
|
84
|
-
RANGE_MATCHER = %r{^(\-?\d+)(\.{2,3})(\-?\d+)$}
|
85
|
-
|
86
|
-
# Matcher for index,length path item.
|
87
|
-
ILEN_MATCHER = %r{^(\-?\d+),(\-?\d+)$}
|
88
|
-
|
89
|
-
# Matcher allowing any value to be matched.
|
90
|
-
ANYVAL_MATCHER = /^(\?*\*+\?*)*$/
|
91
|
-
|
92
|
-
# Matcher to assert if any unescaped special chars are in a path item.
|
93
|
-
PATH_CHAR_MATCHER = /(^|[^#{RECH}])([#{PATH_CHARS}])/
|
94
|
-
|
95
73
|
|
96
74
|
##
|
97
75
|
# Instantiate a Path object with a String data path.
|
98
76
|
# Path.new "/path/**/to/*=bar/../../**/last"
|
99
77
|
|
100
|
-
def initialize path_str, regex_opts=nil
|
78
|
+
def initialize path_str, regex_opts=nil, &block
|
101
79
|
path_str = path_str.dup
|
102
|
-
@path = self.class.parse_path_str path_str, regex_opts
|
80
|
+
@path = self.class.parse_path_str path_str, regex_opts, &block
|
103
81
|
end
|
104
82
|
|
105
83
|
|
@@ -129,11 +107,10 @@ class Kronk
|
|
129
107
|
def find_in data
|
130
108
|
matches = {[] => data}
|
131
109
|
|
132
|
-
@path.each_with_index do |
|
133
|
-
args = [matches, data, mkey, mvalue, recur]
|
110
|
+
@path.each_with_index do |matcher, i|
|
134
111
|
last_item = i == @path.length - 1
|
135
112
|
|
136
|
-
self.class.assign_find(
|
113
|
+
self.class.assign_find(matches, data, matcher) do |sdata, key, spath|
|
137
114
|
yield sdata, key, spath if last_item && block_given?
|
138
115
|
end
|
139
116
|
end
|
@@ -142,6 +119,36 @@ class Kronk
|
|
142
119
|
end
|
143
120
|
|
144
121
|
|
122
|
+
##
|
123
|
+
# Returns a path-keyed data hash. Be careful of mixed key types in hashes
|
124
|
+
# as Symbols and Strings both use #to_s.
|
125
|
+
|
126
|
+
def self.pathed data, escape=true
|
127
|
+
new_data = {}
|
128
|
+
|
129
|
+
find "**", data do |subdata, key, path|
|
130
|
+
next if Array === subdata[key] || Hash === subdata[key]
|
131
|
+
path_str = "#{DCH}#{join(path, escape)}"
|
132
|
+
new_data[path_str] = subdata[key]
|
133
|
+
end
|
134
|
+
|
135
|
+
new_data
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
##
|
140
|
+
# Joins an Array into a path String.
|
141
|
+
|
142
|
+
def self.join path_arr, escape=true
|
143
|
+
path_esc = "*?()|/."
|
144
|
+
path_arr.map! do |k|
|
145
|
+
k.to_s.gsub(/([#{path_esc}])/){|m| "\\#{m}"}
|
146
|
+
end if escape
|
147
|
+
|
148
|
+
path_arr.join(DCH)
|
149
|
+
end
|
150
|
+
|
151
|
+
|
145
152
|
##
|
146
153
|
# Fully streamed version of:
|
147
154
|
# Path.new(str_path).find_in data
|
@@ -151,8 +158,8 @@ class Kronk
|
|
151
158
|
def self.find path_str, data, regex_opts=nil, &block
|
152
159
|
matches = {[] => data}
|
153
160
|
|
154
|
-
parse_path_str path_str, regex_opts do |
|
155
|
-
assign_find matches, data,
|
161
|
+
parse_path_str path_str, regex_opts do |matcher, last_item|
|
162
|
+
assign_find matches, data, matcher do |sdata, key, spath|
|
156
163
|
yield sdata, key, spath if last_item && block_given?
|
157
164
|
end
|
158
165
|
end
|
@@ -164,11 +171,11 @@ class Kronk
|
|
164
171
|
##
|
165
172
|
# Common find functionality that assigns to the matches hash.
|
166
173
|
|
167
|
-
def self.assign_find matches, data,
|
174
|
+
def self.assign_find matches, data, matcher
|
168
175
|
matches.keys.each do |path|
|
169
176
|
pdata = matches.delete path
|
170
177
|
|
171
|
-
if
|
178
|
+
if matcher.key == PARENT
|
172
179
|
path = path[0..-2]
|
173
180
|
subdata = data_at_path path[0..-2], data
|
174
181
|
|
@@ -180,7 +187,7 @@ class Kronk
|
|
180
187
|
next
|
181
188
|
end
|
182
189
|
|
183
|
-
|
190
|
+
matcher.find_in pdata, path do |sdata, key, spath|
|
184
191
|
yield sdata, key, spath if block_given?
|
185
192
|
matches[spath] = sdata[key]
|
186
193
|
end
|
@@ -206,118 +213,6 @@ class Kronk
|
|
206
213
|
end
|
207
214
|
|
208
215
|
|
209
|
-
##
|
210
|
-
# Universal iterator for Hash and Array like objects.
|
211
|
-
# The data argument must either respond to both :each_with_index
|
212
|
-
# and :length, or respond to :each yielding a key/value pair.
|
213
|
-
|
214
|
-
def self.each_data_item data, &block
|
215
|
-
if data.respond_to?(:has_key?) && data.respond_to?(:each)
|
216
|
-
data.each(&block)
|
217
|
-
|
218
|
-
elsif data.respond_to?(:each_with_index) && data.respond_to?(:length)
|
219
|
-
# We need to iterate through the array this way
|
220
|
-
# in case items in it get deleted.
|
221
|
-
(data.length - 1).downto(0) do |i|
|
222
|
-
block.call i, data[i]
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
|
228
|
-
##
|
229
|
-
# Finds data with the given key and value matcher, optionally recursive.
|
230
|
-
# Yields data, key and path Array when block is given.
|
231
|
-
# Returns an Array of path arrays.
|
232
|
-
|
233
|
-
def self.find_match data, mkey, mvalue=ANY_VALUE,
|
234
|
-
recur=false, path=nil, &block
|
235
|
-
|
236
|
-
return [] unless Array === data || Hash === data
|
237
|
-
|
238
|
-
paths = []
|
239
|
-
path ||= []
|
240
|
-
|
241
|
-
each_data_item data do |key, value|
|
242
|
-
c_path = path.dup << key
|
243
|
-
|
244
|
-
if match_data_item(mkey, key) && match_data_item(mvalue, value)
|
245
|
-
yield data, key, c_path if block_given?
|
246
|
-
paths << c_path
|
247
|
-
end
|
248
|
-
|
249
|
-
paths.concat \
|
250
|
-
find_match(data[key], mkey, mvalue, true, c_path, &block) if recur
|
251
|
-
end
|
252
|
-
|
253
|
-
paths
|
254
|
-
end
|
255
|
-
|
256
|
-
|
257
|
-
##
|
258
|
-
# Check if data key or value is a match for nested data searches.
|
259
|
-
|
260
|
-
def self.match_data_item item1, item2
|
261
|
-
return if ANY_VALUE != item1 && (Array === item2 || Hash === item2)
|
262
|
-
|
263
|
-
if item1.class == item2.class
|
264
|
-
item1 == item2
|
265
|
-
|
266
|
-
elsif Regexp === item1
|
267
|
-
item2.to_s =~ item1
|
268
|
-
|
269
|
-
elsif Range === item1
|
270
|
-
item1.include? item2.to_i
|
271
|
-
|
272
|
-
elsif ANY_VALUE == item1
|
273
|
-
true
|
274
|
-
|
275
|
-
else
|
276
|
-
item2.to_s.downcase == item1.to_s.downcase
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
|
281
|
-
##
|
282
|
-
# Decide whether to make path item matcher a regex, range, array, or string.
|
283
|
-
|
284
|
-
def self.parse_path_item str, regex_opts=nil
|
285
|
-
case str
|
286
|
-
when nil, ANYVAL_MATCHER
|
287
|
-
ANY_VALUE
|
288
|
-
|
289
|
-
when RANGE_MATCHER
|
290
|
-
Range.new $1.to_i, $3.to_i, ($2 == "...")
|
291
|
-
|
292
|
-
when ILEN_MATCHER
|
293
|
-
Range.new $1.to_i, ($1.to_i + $2.to_i), true
|
294
|
-
|
295
|
-
else
|
296
|
-
if String === str && (regex_opts || str =~ PATH_CHAR_MATCHER)
|
297
|
-
|
298
|
-
# Remove extra suffix characters
|
299
|
-
str.gsub! %r{(^|[^#{RECH}])(\*+\?+|\?+\*+)}, '\1*'
|
300
|
-
str.gsub! %r{(^|[^#{RECH}])\*+}, '\1*'
|
301
|
-
|
302
|
-
str = Regexp.escape str
|
303
|
-
|
304
|
-
# Remove escaping from special path characters
|
305
|
-
str.gsub! %r{#{RECH}([#{PATH_CHARS}])}, '\1'
|
306
|
-
str.gsub! %r{#{RECH}([#{RESC_CHARS}])}, '\1'
|
307
|
-
str.gsub! %r{(^|[^#{RECH}])([#{SUFF_CHARS}])}, '\1.\2'
|
308
|
-
|
309
|
-
Regexp.new "\\A(#{str})\\Z", regex_opts
|
310
|
-
|
311
|
-
elsif String === str
|
312
|
-
str.gsub %r{#{RECH}([^#{RECH}]|$)}, '\1'
|
313
|
-
|
314
|
-
else
|
315
|
-
str
|
316
|
-
end
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
|
321
216
|
##
|
322
217
|
# Parses a path String into an Array of arrays containing
|
323
218
|
# matchers for key, value, and any special modifiers
|
@@ -388,13 +283,13 @@ class Kronk
|
|
388
283
|
end
|
389
284
|
|
390
285
|
unless key =~ /^\.?$/ && !value
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
yield_args = (parsed.last.dup << path.empty?)
|
286
|
+
matcher = Matcher.new :key => key,
|
287
|
+
:value => value,
|
288
|
+
:recursive => recur,
|
289
|
+
:regex_opts => regex_opts
|
396
290
|
|
397
|
-
|
291
|
+
parsed << matcher
|
292
|
+
yield matcher, path.empty? if block_given?
|
398
293
|
end
|
399
294
|
|
400
295
|
key = ""
|