kronk 1.8.5 → 1.8.6
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 +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
|