kronk 1.8.5 → 1.8.6
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +12 -0
- data/Manifest.txt +0 -10
- data/README.rdoc +7 -2
- data/Rakefile +1 -0
- data/lib/kronk.rb +2 -5
- data/lib/kronk/cmd.rb +3 -3
- data/lib/kronk/data_string.rb +1 -1
- data/lib/kronk/diff/output.rb +2 -2
- data/lib/kronk/player.rb +1 -0
- data/lib/kronk/player/benchmark.rb +1 -0
- data/lib/kronk/player/suite.rb +20 -16
- data/lib/kronk/request.rb +9 -2
- data/lib/kronk/response.rb +2 -2
- data/lib/kronk/test/assertions.rb +4 -4
- data/test/test_helper.rb +1 -1
- metadata +49 -27
- data/lib/kronk/core_ext.rb +0 -110
- data/lib/kronk/path.rb +0 -334
- data/lib/kronk/path/match.rb +0 -130
- data/lib/kronk/path/matcher.rb +0 -193
- data/lib/kronk/path/transaction.rb +0 -341
- data/test/test_core_ext.rb +0 -74
- data/test/test_path.rb +0 -317
- data/test/test_path_match.rb +0 -105
- data/test/test_path_matcher.rb +0 -371
- data/test/test_transaction.rb +0 -520
data/lib/kronk/path/matcher.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Path::Matcher is representation of a single node of a relative path used
|
3
|
-
# to find values in a data set.
|
4
|
-
|
5
|
-
class Kronk::Path::Matcher
|
6
|
-
|
7
|
-
# Used as path item value to match any key or value.
|
8
|
-
module ANY_VALUE; end
|
9
|
-
|
10
|
-
# Shortcut characters that require modification before being turned into
|
11
|
-
# a matcher.
|
12
|
-
SUFF_CHARS = Regexp.escape "*?"
|
13
|
-
|
14
|
-
# All special path characters.
|
15
|
-
PATH_CHARS = Regexp.escape("()|") << SUFF_CHARS
|
16
|
-
|
17
|
-
# Path chars that get regexp escaped.
|
18
|
-
RESC_CHARS = "*?()|/"
|
19
|
-
|
20
|
-
# Matcher for Range path item.
|
21
|
-
RANGE_MATCHER = %r{^(\-?\d+)(\.{2,3})(\-?\d+)$}
|
22
|
-
|
23
|
-
# Matcher for index,length path item.
|
24
|
-
ILEN_MATCHER = %r{^(\-?\d+),(\-?\d+)$}
|
25
|
-
|
26
|
-
# Matcher allowing any value to be matched.
|
27
|
-
ANYVAL_MATCHER = /^(\?*\*+\?*)*$/
|
28
|
-
|
29
|
-
# Matcher to assert if any unescaped special chars are in a path item.
|
30
|
-
PATH_CHAR_MATCHER = /(^|[^#{Kronk::Path::RECH}])([#{PATH_CHARS}])/
|
31
|
-
|
32
|
-
|
33
|
-
attr_reader :key, :value, :regex_opts
|
34
|
-
attr_accessor :recursive
|
35
|
-
|
36
|
-
##
|
37
|
-
# New instance of Matcher. Options supported:
|
38
|
-
# :key:: String - The path item key to match.
|
39
|
-
# :value:: String - The path item value to match.
|
40
|
-
# :recursive:: Boolean - Look for path item recursively.
|
41
|
-
# :regex_opts:: Fixnum - representing the Regexp options.
|
42
|
-
|
43
|
-
def initialize opts={}
|
44
|
-
@regex_opts = opts[:regex_opts]
|
45
|
-
@recursive = !!opts[:recursive]
|
46
|
-
|
47
|
-
@key = nil
|
48
|
-
@key = parse_node opts[:key] if
|
49
|
-
opts[:key] && !opts[:key].to_s.empty?
|
50
|
-
|
51
|
-
@value = nil
|
52
|
-
@value = parse_node opts[:value] if
|
53
|
-
opts[:value] && !opts[:value].to_s.empty?
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
def == other # :nodoc:
|
58
|
-
self.class == other.class &&
|
59
|
-
@key == other.key &&
|
60
|
-
@value == other.value &&
|
61
|
-
@regex_opts == other.regex_opts
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
##
|
66
|
-
# Universal iterator for Hash and Array like objects.
|
67
|
-
# The data argument must either respond to both :each_with_index
|
68
|
-
# and :length, or respond to :has_key? and :each yielding a key/value pair.
|
69
|
-
|
70
|
-
def each_data_item data, &block
|
71
|
-
if data.respond_to?(:has_key?) && data.respond_to?(:each)
|
72
|
-
data.each(&block)
|
73
|
-
|
74
|
-
elsif data.respond_to?(:each_with_index) && data.respond_to?(:length)
|
75
|
-
# We need to iterate through the array this way
|
76
|
-
# in case items in it get deleted.
|
77
|
-
(data.length - 1).downto(0) do |i|
|
78
|
-
block.call i, data[i]
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
##
|
85
|
-
# Finds data with the given key and value matcher, optionally recursive.
|
86
|
-
# Yields data, key and path Array when block is given.
|
87
|
-
# Returns an Array of path arrays.
|
88
|
-
|
89
|
-
def find_in data, path=nil, &block
|
90
|
-
return [] unless Array === data || Hash === data
|
91
|
-
|
92
|
-
paths = []
|
93
|
-
path ||= Kronk::Path::Match.new
|
94
|
-
path = Kronk::Path::Match.new path if path.class == Array
|
95
|
-
|
96
|
-
each_data_item data do |key, value|
|
97
|
-
c_path = path.dup << key
|
98
|
-
|
99
|
-
found, kmatch = match_node(@key, key) if @key
|
100
|
-
found, vmatch = match_node(@value, value) if @value && (!@key || found)
|
101
|
-
|
102
|
-
c_path.append_splat self, key if @recursive
|
103
|
-
|
104
|
-
if found
|
105
|
-
c_path.matches.concat kmatch.to_a
|
106
|
-
c_path.matches.concat vmatch.to_a
|
107
|
-
|
108
|
-
f_path = c_path.dup
|
109
|
-
f_path.splat[-1][-1].pop if @key && !f_path.splat.empty?
|
110
|
-
|
111
|
-
yield data, key, f_path if block_given?
|
112
|
-
paths << f_path
|
113
|
-
end
|
114
|
-
|
115
|
-
paths.concat find_in(data[key], c_path, &block) if @recursive
|
116
|
-
end
|
117
|
-
|
118
|
-
paths
|
119
|
-
end
|
120
|
-
|
121
|
-
|
122
|
-
##
|
123
|
-
# Check if data key or value is a match for nested data searches.
|
124
|
-
# Returns an array with a boolean expressing if the value matched the node,
|
125
|
-
# and the matches found.
|
126
|
-
|
127
|
-
def match_node node, value
|
128
|
-
return if ANY_VALUE != node &&
|
129
|
-
(Array === value || Hash === value)
|
130
|
-
|
131
|
-
if node.class == value.class
|
132
|
-
node == value
|
133
|
-
|
134
|
-
elsif Regexp === node
|
135
|
-
match = node.match value.to_s
|
136
|
-
return false unless match
|
137
|
-
match = match.size > 1 ? match[1..-1] : match.to_a
|
138
|
-
[true, match]
|
139
|
-
|
140
|
-
elsif Range === node
|
141
|
-
stat = node.include? value.to_i
|
142
|
-
match = [value.to_i] if stat
|
143
|
-
[stat, match]
|
144
|
-
|
145
|
-
elsif ANY_VALUE == node
|
146
|
-
[true, [value]]
|
147
|
-
|
148
|
-
else
|
149
|
-
value.to_s == node.to_s
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
##
|
155
|
-
# Decide whether to make path item matcher a regex, range, array, or string.
|
156
|
-
|
157
|
-
def parse_node str
|
158
|
-
case str
|
159
|
-
when nil, ANYVAL_MATCHER
|
160
|
-
ANY_VALUE
|
161
|
-
|
162
|
-
when RANGE_MATCHER
|
163
|
-
Range.new $1.to_i, $3.to_i, ($2 == "...")
|
164
|
-
|
165
|
-
when ILEN_MATCHER
|
166
|
-
Range.new $1.to_i, ($1.to_i + $2.to_i), true
|
167
|
-
|
168
|
-
when String
|
169
|
-
if @regex_opts || str =~ PATH_CHAR_MATCHER
|
170
|
-
|
171
|
-
# Remove extra suffix characters
|
172
|
-
str.gsub! %r{(^|[^#{Kronk::Path::RECH}])(\*+\?*)}, '\1*'
|
173
|
-
str.gsub! %r{(^|[^#{Kronk::Path::RECH}])\*+}, '\1*'
|
174
|
-
|
175
|
-
str = Regexp.escape str
|
176
|
-
|
177
|
-
# Remove escaping from special path characters
|
178
|
-
str.gsub! %r{#{Kronk::Path::RECH}([#{PATH_CHARS}])}, '\1'
|
179
|
-
str.gsub! %r{#{Kronk::Path::RECH}([#{RESC_CHARS}])}, '\1'
|
180
|
-
str.gsub! %r{(^|[^#{Kronk::Path::RECH}])([#{SUFF_CHARS}])}, '\1(.\2)'
|
181
|
-
str.gsub! %r{(^|[^\.#{Kronk::Path::RECH}])([#{SUFF_CHARS}])}, '\1(.\2)'
|
182
|
-
|
183
|
-
Regexp.new "\\A(?:#{str})\\Z", @regex_opts
|
184
|
-
|
185
|
-
else
|
186
|
-
str.gsub %r{#{Kronk::Path::RECH}([^#{Kronk::Path::RECH}]|$)}, '\1'
|
187
|
-
end
|
188
|
-
|
189
|
-
else
|
190
|
-
str
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
@@ -1,341 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Path Transactions are a convenient way to apply selections and deletions
|
3
|
-
# to complex data structures without having to know what state the data will
|
4
|
-
# be in after each operation.
|
5
|
-
#
|
6
|
-
# data = [
|
7
|
-
# {:name => "Jamie", :id => "12345"},
|
8
|
-
# {:name => "Adam", :id => "54321"},
|
9
|
-
# {:name => "Kari", :id => "12345"},
|
10
|
-
# {:name => "Grant", :id => "12345"},
|
11
|
-
# {:name => "Tory", :id => "12345"},
|
12
|
-
# ]
|
13
|
-
#
|
14
|
-
# # Select all element names, delete the one at index 2,
|
15
|
-
# # and move the element with the value "Tory" to the same path but
|
16
|
-
# # with the key renamed to "boo"
|
17
|
-
# Transaction.run data do |t|
|
18
|
-
# t.select "*/name"
|
19
|
-
# t.move "**=Tory" => "%%/boo"
|
20
|
-
# t.delete "2"
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# # => [
|
24
|
-
# # {:name => "Jamie"},
|
25
|
-
# # {:name => "Adam"},
|
26
|
-
# # {:name => "Grant"},
|
27
|
-
# # {"boo" => "Tory"},
|
28
|
-
# # ]
|
29
|
-
|
30
|
-
class Kronk::Path::Transaction
|
31
|
-
|
32
|
-
##
|
33
|
-
# Create new Transaction instance and run it with a block.
|
34
|
-
# Equivalent to:
|
35
|
-
# Transaction.new(data).run(opts)
|
36
|
-
|
37
|
-
def self.run data, opts={}, &block
|
38
|
-
new(data).run opts, &block
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
attr_accessor :actions
|
43
|
-
|
44
|
-
##
|
45
|
-
# Create a new Transaction instance with a the data object to perform
|
46
|
-
# operations on.
|
47
|
-
|
48
|
-
def initialize data
|
49
|
-
@data = data
|
50
|
-
@new_data = nil
|
51
|
-
@actions = []
|
52
|
-
|
53
|
-
@make_array = {}
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
##
|
58
|
-
# Run operations as a transaction.
|
59
|
-
# See Transaction#results for supported options.
|
60
|
-
|
61
|
-
def run opts={}, &block
|
62
|
-
clear
|
63
|
-
yield self if block_given?
|
64
|
-
results opts
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
##
|
69
|
-
# Returns the results of the transaction operations.
|
70
|
-
# To keep the original indicies of modified arrays, and return them as hashes,
|
71
|
-
# pass the :keep_indicies => true option.
|
72
|
-
|
73
|
-
def results opts={}
|
74
|
-
new_data = @data
|
75
|
-
|
76
|
-
@actions.each do |type, paths|
|
77
|
-
new_data = send("transaction_#{type}", new_data, *paths)
|
78
|
-
end
|
79
|
-
|
80
|
-
remake_arrays new_data, opts[:keep_indicies]
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
def remake_arrays new_data, except_modified=false # :nodoc:
|
85
|
-
remake_paths = @make_array.keys.sort{|p1, p2| p2.length <=> p1.length}
|
86
|
-
|
87
|
-
remake_paths.each do |path_arr|
|
88
|
-
key = path_arr.last
|
89
|
-
obj = Kronk::Path.data_at_path path_arr[0..-2], new_data
|
90
|
-
|
91
|
-
next unless obj && Hash === obj[key]
|
92
|
-
|
93
|
-
if except_modified
|
94
|
-
data_at_path = Kronk::Path.data_at_path(path_arr, @data)
|
95
|
-
next if !data_at_path || obj[key].length != data_at_path.length
|
96
|
-
end
|
97
|
-
|
98
|
-
obj[key] = hash_to_ary obj[key]
|
99
|
-
end
|
100
|
-
|
101
|
-
new_data = hash_to_ary new_data if
|
102
|
-
(remake_paths.last == [] || Array === @data && Hash === new_data) &&
|
103
|
-
(!except_modified || @data.length == new_data.length)
|
104
|
-
|
105
|
-
new_data
|
106
|
-
end
|
107
|
-
|
108
|
-
|
109
|
-
def remap_make_arrays new_path, old_path # :nodoc:
|
110
|
-
@make_array[new_path] = true and return if @make_array[old_path]
|
111
|
-
|
112
|
-
@make_array.keys.each do |path|
|
113
|
-
if path[0...old_path.length] == old_path
|
114
|
-
path[0...old_path.length] = new_path
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
def transaction_select data, *data_paths # :nodoc:
|
121
|
-
return data if data_paths.empty?
|
122
|
-
|
123
|
-
transaction data, data_paths, true do |sdata, cdata, key, path, tpath|
|
124
|
-
sdata[key] = cdata[key]
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
|
129
|
-
def transaction_delete data, *data_paths # :nodoc:
|
130
|
-
transaction data, data_paths do |new_curr_data, curr_data, key|
|
131
|
-
new_curr_data.delete key
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
|
136
|
-
def transaction_move data, *path_pairs # :nodoc:
|
137
|
-
return data if path_pairs.empty?
|
138
|
-
path_val_hash = {}
|
139
|
-
|
140
|
-
new_data =
|
141
|
-
transaction data, path_pairs do |sdata, cdata, key, path, tpath|
|
142
|
-
path_val_hash[tpath] = sdata.delete key
|
143
|
-
remap_make_arrays(tpath, path)
|
144
|
-
end
|
145
|
-
|
146
|
-
force_assign_paths new_data, path_val_hash
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
def transaction_map data, *path_pairs # :nodoc:
|
151
|
-
return data if path_pairs.empty?
|
152
|
-
path_val_hash = {}
|
153
|
-
|
154
|
-
transaction data, path_pairs do |sdata, cdata, key, path, tpath|
|
155
|
-
tpath ||= path
|
156
|
-
path_val_hash[tpath] = sdata.delete key
|
157
|
-
remap_make_arrays(tpath, path)
|
158
|
-
end
|
159
|
-
|
160
|
-
force_assign_paths [], path_val_hash
|
161
|
-
end
|
162
|
-
|
163
|
-
|
164
|
-
def transaction data, data_paths, create_empty=false # :nodoc:
|
165
|
-
data_paths = data_paths.compact
|
166
|
-
return @new_data || data if data_paths.empty?
|
167
|
-
|
168
|
-
@new_data = create_empty ? Hash.new : data.dup
|
169
|
-
|
170
|
-
if Array === @new_data
|
171
|
-
@new_data = ary_to_hash @new_data
|
172
|
-
end
|
173
|
-
|
174
|
-
data_paths.each do |data_path|
|
175
|
-
# If data_path is an array, the second element is the path where the value
|
176
|
-
# should be mapped to.
|
177
|
-
data_path, target_path = data_path
|
178
|
-
|
179
|
-
Kronk::Path.find data_path, data do |obj, k, path|
|
180
|
-
curr_data = data
|
181
|
-
new_curr_data = @new_data
|
182
|
-
|
183
|
-
path.each_with_index do |key, i|
|
184
|
-
break unless new_curr_data
|
185
|
-
|
186
|
-
if i == path.length - 1
|
187
|
-
tpath = path.make_path target_path if target_path
|
188
|
-
yield new_curr_data, curr_data, key, path, tpath if block_given?
|
189
|
-
|
190
|
-
else
|
191
|
-
if create_empty
|
192
|
-
new_curr_data[key] ||= Hash.new
|
193
|
-
|
194
|
-
elsif new_curr_data[key] == curr_data[key]
|
195
|
-
new_curr_data[key] = Array === new_curr_data[key] ?
|
196
|
-
ary_to_hash(curr_data[key]) :
|
197
|
-
curr_data[key].dup
|
198
|
-
end
|
199
|
-
|
200
|
-
@make_array[path[0..i]] = true if Array === curr_data[key]
|
201
|
-
|
202
|
-
new_curr_data = new_curr_data[key]
|
203
|
-
curr_data = curr_data[key]
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
@new_data
|
210
|
-
end
|
211
|
-
|
212
|
-
|
213
|
-
def force_assign_paths data, path_val_hash # :nodoc:
|
214
|
-
return data if path_val_hash.empty?
|
215
|
-
@new_data = (data.dup rescue [])
|
216
|
-
|
217
|
-
path_val_hash.each do |path, value|
|
218
|
-
curr_data = data
|
219
|
-
new_curr_data = @new_data
|
220
|
-
prev_data = nil
|
221
|
-
prev_key = nil
|
222
|
-
prev_path = []
|
223
|
-
|
224
|
-
path.each_with_index do |key, i|
|
225
|
-
if Array === new_curr_data
|
226
|
-
new_curr_data = ary_to_hash new_curr_data
|
227
|
-
prev_data[prev_key] = new_curr_data if prev_data
|
228
|
-
@new_data = new_curr_data if i == 0
|
229
|
-
@make_array[prev_path] = true if i == 0
|
230
|
-
end
|
231
|
-
|
232
|
-
last = i == path.length - 1
|
233
|
-
prev_path = path[0..(i-1)] if i > 0
|
234
|
-
curr_path = path[0..i]
|
235
|
-
next_key = path[i+1]
|
236
|
-
|
237
|
-
# new_curr_data is a hash from here on
|
238
|
-
|
239
|
-
@make_array.delete prev_path unless is_integer?(key)
|
240
|
-
|
241
|
-
new_curr_data[key] = value and break if last
|
242
|
-
|
243
|
-
if ary_or_hash?(curr_data) && child_ary_or_hash?(curr_data, key)
|
244
|
-
new_curr_data[key] ||= curr_data[key]
|
245
|
-
|
246
|
-
elsif !ary_or_hash?(new_curr_data[key])
|
247
|
-
new_curr_data[key] = is_integer?(next_key) ? [] : {}
|
248
|
-
end
|
249
|
-
|
250
|
-
@make_array[curr_path] = true if Array === new_curr_data[key]
|
251
|
-
|
252
|
-
prev_key = key
|
253
|
-
prev_data = new_curr_data
|
254
|
-
new_curr_data = new_curr_data[key]
|
255
|
-
curr_data = curr_data[key] if ary_or_hash?(curr_data) rescue nil
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
@new_data
|
260
|
-
end
|
261
|
-
|
262
|
-
|
263
|
-
def is_integer? item # :nodoc:
|
264
|
-
item.to_s.to_i.to_s == item.to_s
|
265
|
-
end
|
266
|
-
|
267
|
-
|
268
|
-
def ary_or_hash? obj # :nodoc:
|
269
|
-
Array === obj || Hash === obj
|
270
|
-
end
|
271
|
-
|
272
|
-
|
273
|
-
def child_ary_or_hash? obj, key
|
274
|
-
ary_or_hash?(obj[key]) rescue false
|
275
|
-
end
|
276
|
-
|
277
|
-
|
278
|
-
def ary_to_hash ary # :nodoc:
|
279
|
-
hash = {}
|
280
|
-
ary.each_with_index{|val, i| hash[i] = val}
|
281
|
-
hash
|
282
|
-
end
|
283
|
-
|
284
|
-
|
285
|
-
def hash_to_ary hash # :nodoc:
|
286
|
-
hash.keys.sort.map{|k| hash[k] }
|
287
|
-
end
|
288
|
-
|
289
|
-
|
290
|
-
##
|
291
|
-
# Clears the queued actions and cache.
|
292
|
-
|
293
|
-
def clear
|
294
|
-
@new_data = nil
|
295
|
-
@actions.clear
|
296
|
-
@make_array.clear
|
297
|
-
end
|
298
|
-
|
299
|
-
|
300
|
-
##
|
301
|
-
# Queues path selects for transaction.
|
302
|
-
|
303
|
-
def select *paths
|
304
|
-
if @actions.last && @actions.last[0] == :select
|
305
|
-
@actions.last[1].concat paths
|
306
|
-
else
|
307
|
-
@actions << [:select, paths]
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
|
312
|
-
##
|
313
|
-
# Queues path deletes for transaction.
|
314
|
-
|
315
|
-
def delete *paths
|
316
|
-
@actions << [:delete, paths]
|
317
|
-
end
|
318
|
-
|
319
|
-
|
320
|
-
##
|
321
|
-
# Queues path moving for transaction. Moving a path will attempt to
|
322
|
-
# keep the original data structure and only affect the given paths.
|
323
|
-
# Empty hashes or arrays after a move are deleted.
|
324
|
-
# t.move "my/path/1..4/key" => "new_path/%1/key",
|
325
|
-
# "other/path/*" => "moved/%1"
|
326
|
-
|
327
|
-
def move path_maps
|
328
|
-
@actions << [:move, Array(path_maps)]
|
329
|
-
end
|
330
|
-
|
331
|
-
|
332
|
-
##
|
333
|
-
# Queues path mapping for transaction. Mapping a path will only keep the
|
334
|
-
# mapped values, completely replacing the original data structure.
|
335
|
-
# t.map "my/path/1..4/key" => "new_path/%1/key",
|
336
|
-
# "other/path/*" => "moved/%1"
|
337
|
-
|
338
|
-
def map path_maps
|
339
|
-
@actions << [:map, Array(path_maps)]
|
340
|
-
end
|
341
|
-
end
|