kronk 1.5.4 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 = ""
|