kronk 1.8.5 → 1.8.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,110 +0,0 @@
1
- class Kronk
2
-
3
- ##
4
- # Data manipulation and retrieval methods for Array and Hash classes.
5
-
6
- module DataExt
7
-
8
- ##
9
- # Checks if the given path exists and returns the first matching path
10
- # as an array of keys. Returns false if no path is found.
11
-
12
- def has_path? path
13
- Kronk::Path.find path, self do |d,k,p|
14
- return true
15
- end
16
-
17
- false
18
- end
19
-
20
-
21
- ##
22
- # Looks for data at paths matching path. Returns a hash of
23
- # path array => data value pairs.
24
- #
25
- # If given a block will pass the parent data structure, the key
26
- # or index of the item at given path, and the full path
27
- # as an array of keys for each found path.
28
- #
29
- # data = {:foo => "bar", :foobar => [:a, :b, {:foo => "other bar"}, :c]}
30
- # data.find_data "**/foo" do |parent, key, path|
31
- # p path
32
- # p parent[key]
33
- # puts "---"
34
- # end
35
- #
36
- # # outputs:
37
- # # [:foo]
38
- # # "bar"
39
- # # ---
40
- # # [:foobar, 2, :foo]
41
- # # "other bar"
42
- # # ---
43
- #
44
- # # returns:
45
- # # {[:foo] => "bar", [:foobar, 2, :foo] => "other bar"}
46
-
47
- def find_data path, &block
48
- Kronk::Path.find path, self, &block
49
- end
50
-
51
-
52
- ##
53
- # Finds and replaces the value of any match with the given new value.
54
- # Returns true if matches were replaced, otherwise false.
55
- #
56
- # data = {:foo => "bar", :foobar => [:a, :b, {:foo => "other bar"}, :c]}
57
- # data.replace_at_path "**=*bar", "BAR"
58
- # #=> true
59
- #
60
- # data
61
- # #=> {:foo => "BAR", :foobar => [:a, :b, {:foo => "BAR"}, :c]}
62
- #
63
- # Note: Specifying a limit will allow only "limit" number of items to be
64
- # set but may yield unpredictible results for non-ordered Hashes.
65
- # It's also important to realize that arrays are modified starting with
66
- # the last index, going down.
67
-
68
- def replace_at_path path, value, limit=nil
69
- count = 0
70
-
71
- Kronk::Path.find path, self do |data, key, path_arr|
72
- count = count.next
73
- data[key] = value
74
-
75
- return true if limit && count >= limit
76
- end
77
-
78
- return count > 0
79
- end
80
-
81
-
82
- ##
83
- # Similar to DataExt#replace_at_path but deletes found items.
84
- # Returns a hash of path/value pairs of deleted items.
85
- #
86
- # data = {:foo => "bar", :foobar => [:a, :b, {:foo => "other bar"}, :c]}
87
- # data.replace_at_path "**=*bar", "BAR"
88
- # #=> {[:foo] => "bar", [:foobar, 2, :foo] => "other bar"}
89
-
90
- def delete_at_path path, limit=nil
91
- count = 0
92
- out = {}
93
-
94
- Kronk::Path.find path, self do |data, key, path_arr|
95
- count = count.next
96
- out[path_arr] = data[key]
97
-
98
- data.respond_to(:delete_at) ? data.delete_at(key) : data.delete(key)
99
-
100
- return true if limit && count >= limit
101
- end
102
-
103
- return count > 0
104
- end
105
- end
106
- end
107
-
108
-
109
- Array.send :include, Kronk::DataExt
110
- Hash.send :include, Kronk::DataExt
data/lib/kronk/path.rb DELETED
@@ -1,334 +0,0 @@
1
- class Kronk
2
-
3
- ##
4
- # Finds specific data points from a nested Hash or Array data structure
5
- # through the use of a file-glob-like path selector.
6
- #
7
- # Special characters are: "/ * ? = | \ . , \ ( )"
8
- # and are interpreted as follows:
9
- #
10
- # :foo/ - walk down tree by one level from key "foo"
11
- # :*/foo - walk down tree from any parent with key "foo" as a child
12
- # :foo1|foo2 - return elements with key value of "foo1" or "foo2"
13
- # :foo(1|2) - same behavior as above
14
- # :foo=val - return elements where key has a value of val
15
- # :foo\* - return root-level element with key "foo*" ('*' char is escaped)
16
- # :**/foo - recursively search for key "foo"
17
- # :foo? - return keys that match /\Afoo.?\Z/
18
- # :2..10 - match any integer from 2 to 10
19
- # :2...10 - match any integer from 2 to 9
20
- # :2,5 - match any integer from 2 to 7
21
- #
22
- # Examples:
23
- #
24
- # # Recursively look for elements with value "val" under top element "root"
25
- # Path.find "root/**=val", data
26
- #
27
- # # Find child elements of "root" that have a key of "foo" or "bar"
28
- # Path.find "root/foo|bar", data
29
- #
30
- # # Recursively find child elements of root whose value is 1, 2, or 3.
31
- # Path.find "root/**=1..3", data
32
- #
33
- # # Recursively find child elements of root of literal value "1..3"
34
- # Path.find "root/**=\\1..3", data
35
-
36
- class Path
37
-
38
- # Used as path instruction to go up one path level.
39
- module PARENT; end
40
-
41
- # Mapping of letters to Regexp options.
42
- REGEX_OPTS = {
43
- "i" => Regexp::IGNORECASE,
44
- "m" => Regexp::MULTILINE,
45
- "u" => (Regexp::FIXEDENCODING if defined?(Regexp::FIXEDENCODING)),
46
- "x" => Regexp::EXTENDED
47
- }
48
-
49
- # The path item delimiter character "/"
50
- DCH = "/"
51
-
52
- # The replacement character "%" for path mapping
53
- RCH = "%"
54
-
55
- # The path character to assign value "="
56
- VCH = "="
57
-
58
- # The escape character to use any PATH_CHARS as its literal.
59
- ECH = "\\"
60
-
61
- # The Regexp escaped version of ECH.
62
- RECH = Regexp.escape ECH
63
-
64
- # The EndOfPath delimiter after which regex opt chars may be specified.
65
- EOP = DCH + DCH
66
-
67
- # The key string that represents PARENT.
68
- PARENT_KEY = ".."
69
-
70
- # The key string that indicates recursive lookup.
71
- RECUR_KEY = "**"
72
-
73
-
74
- ##
75
- # Instantiate a Path object with a String data path.
76
- # Path.new "/path/**/to/*=bar/../../**/last"
77
-
78
- def initialize path_str, regex_opts=nil, &block
79
- path_str = path_str.dup
80
- @path = self.class.parse_path_str path_str, regex_opts, &block
81
- end
82
-
83
-
84
- ##
85
- # Finds the current path in the given data structure.
86
- # Returns a Hash of path_ary => data pairs for each match.
87
- #
88
- # If a block is given, yields the parent data object matched,
89
- # the key, and the path array.
90
- #
91
- # data = {:path => {:foo => :bar, :sub => {:foo => :bar2}}, :other => nil}
92
- # path = Path.new "path/**/foo"
93
- #
94
- # all_args = []
95
- #
96
- # path.find_in data |*args|
97
- # all_args << args
98
- # end
99
- # #=> {[:path, :foo] => :bar, [:path, :sub, :foo] => :bar2}
100
- #
101
- # all_args
102
- # #=> [
103
- # #=> [{:foo => :bar, :sub => {:foo => :bar2}}, :foo, [:path, :foo]],
104
- # #=> [{:foo => :bar2}, :foo, [:path, :sub, :foo]]
105
- # #=> ]
106
-
107
- def find_in data
108
- matches = {[] => data}
109
-
110
- @path.each_with_index do |matcher, i|
111
- last_item = i == @path.length - 1
112
-
113
- self.class.assign_find(matches, data, matcher) do |sdata, key, spath|
114
- yield sdata, key, spath if last_item && block_given?
115
- end
116
- end
117
-
118
- matches
119
- end
120
-
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
- # Path.pathed {'foo' => %w{thing bar}, 'fizz' => {'buzz' => 123}}
127
- # #=> {
128
- # # '/foo/0' => 'thing',
129
- # # '/foo/1' => 'bar',
130
- # # '/fizz/buzz' => 123
131
- # # }
132
-
133
- def self.pathed data, escape=true
134
- new_data = {}
135
-
136
- find "**", data do |subdata, key, path|
137
- next if Array === subdata[key] || Hash === subdata[key]
138
- path_str = "#{DCH}#{join(path, escape)}"
139
- new_data[path_str] = subdata[key]
140
- end
141
-
142
- new_data
143
- end
144
-
145
-
146
- SPECIAL_CHARS = "*?()|/."
147
- R_SPECIAL_CHARS = /[\0#{Regexp.escape SPECIAL_CHARS}]/u
148
-
149
- ##
150
- # Joins an Array into a path String.
151
-
152
- def self.join path_arr, escape=true
153
- path_str = path_arr.join("\0")
154
- if escape
155
- path_str.gsub!(R_SPECIAL_CHARS){|c| c == "\0" ? DCH : "\\#{c}"}
156
- else
157
- path_str.gsub! "\0", DCH
158
- end
159
- path_str
160
- end
161
-
162
-
163
- ##
164
- # Fully streamed version of:
165
- # Path.new(str_path).find_in data
166
- #
167
- # See Path#find_in for usage.
168
-
169
- def self.find path_str, data, regex_opts=nil, &block
170
- matches = {[] => data}
171
-
172
- parse_path_str path_str, regex_opts do |matcher, last_item|
173
- assign_find matches, data, matcher do |sdata, key, spath|
174
- yield sdata, key, spath if last_item && block_given?
175
- end
176
- end
177
-
178
- matches
179
- end
180
-
181
-
182
- ##
183
- # Common find functionality that assigns to the matches hash.
184
-
185
- def self.assign_find matches, data, matcher
186
- matches.keys.each do |path|
187
- pdata = matches.delete path
188
-
189
- if matcher.key == PARENT
190
- path = path[0..-2]
191
- subdata = data_at_path path[0..-2], data
192
-
193
- #!! Avoid yielding parent more than once
194
- next if matches[path]
195
-
196
- yield subdata, path.last, path if block_given?
197
- matches[path] = subdata[path.last]
198
- next
199
- end
200
-
201
- matcher.find_in pdata, path do |sdata, key, spath|
202
- yield sdata, key, spath if block_given?
203
- matches[spath] = sdata[key]
204
- end
205
- end
206
- end
207
-
208
-
209
- ##
210
- # Returns the data object found at the given path array.
211
- # Returns nil if not found.
212
-
213
- def self.data_at_path path_arr, data
214
- c_data = data
215
-
216
- path_arr.each do |key|
217
- c_data = c_data[key]
218
- end
219
-
220
- c_data
221
-
222
- rescue NoMethodError, TypeError
223
- nil
224
- end
225
-
226
-
227
- ##
228
- # Parses a path String into an Array of arrays containing
229
- # matchers for key, value, and any special modifiers
230
- # such as recursion.
231
- #
232
- # Path.parse_path_str "/path/**/to/*=bar/../../**/last"
233
- # #=> [["path",ANY_VALUE,false],["to",ANY_VALUE,true],[/.*/,"bar",false],
234
- # # [PARENT,ANY_VALUE,false],[PARENT,ANY_VALUE,false],
235
- # # ["last",ANY_VALUE,true]]
236
- #
237
- # Note: Path.parse_path_str will slice the original path string
238
- # until it is empty.
239
-
240
- def self.parse_path_str path, regex_opts=nil
241
- path = path.dup
242
- regex_opts = parse_regex_opts! path, regex_opts
243
-
244
- parsed = []
245
-
246
- escaped = false
247
- key = ""
248
- value = nil
249
- recur = false
250
- next_item = false
251
-
252
- until path.empty?
253
- char = path.slice!(0..0)
254
-
255
- case char
256
- when DCH
257
- next_item = true
258
- char = ""
259
-
260
- when VCH
261
- value = ""
262
- next
263
-
264
- when ECH
265
- escaped = true
266
- next
267
- end unless escaped
268
-
269
- char = "#{ECH}#{char}" if escaped
270
-
271
- if value
272
- value << char
273
- else
274
- key << char
275
- end
276
-
277
- next_item = true if path.empty?
278
-
279
- if next_item
280
- next_item = false
281
-
282
- if key == RECUR_KEY
283
- key = "*"
284
- recur = true
285
- key = "" and next unless value || path.empty?
286
-
287
- elsif key == PARENT_KEY
288
- key = PARENT
289
-
290
- if recur
291
- key = "" and next unless value
292
- key = "*"
293
- end
294
- end
295
-
296
- # Make sure we're not trying to access /./thing
297
- unless key =~ /^\.?$/ && !value
298
- matcher = Matcher.new :key => key,
299
- :value => value,
300
- :recursive => recur,
301
- :regex_opts => regex_opts
302
-
303
- parsed << matcher
304
- yield matcher, path.empty? if block_given?
305
- end
306
-
307
- key = ""
308
- value = nil
309
- recur = false
310
- end
311
-
312
- escaped = false
313
- end
314
-
315
- parsed
316
- end
317
-
318
-
319
- ##
320
- # Parses the tail end of a path String to determine regexp matching flags.
321
-
322
- def self.parse_regex_opts! path, default=nil
323
- opts = default || 0
324
-
325
- return default unless
326
- path =~ %r{[^#{RECH}]#{EOP}[#{REGEX_OPTS.keys.join}]+\Z}
327
-
328
- path.slice!(%r{#{EOP}[#{REGEX_OPTS.keys.join}]+\Z}).to_s.
329
- each_char{|c| opts |= REGEX_OPTS[c] || 0}
330
-
331
- opts if opts > 0
332
- end
333
- end
334
- end
@@ -1,130 +0,0 @@
1
- ##
2
- # Represents the single match of a relative path against a data set.
3
-
4
- class Kronk::Path::Match < Array
5
-
6
- attr_accessor :matches, :splat
7
-
8
- def initialize *args
9
- @matches = []
10
- @splat = []
11
- super
12
- end
13
-
14
-
15
- def [] selector
16
- path_match = super
17
-
18
- if self.class === path_match
19
- path_match.matches = @matches.dup
20
- path_match.splat = @splat.map{|key, sp| [key, sp.dup]}
21
- end
22
-
23
- path_match
24
- end
25
-
26
-
27
- def append_splat id, key # :nodoc:
28
- if @splat[-1] && @splat[-1][0] == id
29
- @splat[-1][1] << key
30
- else
31
- @splat << [id, [key]]
32
- end
33
- end
34
-
35
-
36
- def dup # :nodoc:
37
- path_match = super
38
- path_match.matches = @matches.dup
39
- path_match.splat = @splat.map{|key, sp| [key, sp.dup]}
40
- path_match
41
- end
42
-
43
-
44
- def append_match_for str, path # :nodoc:
45
- match = @matches[str.to_i-1]
46
- if match && !(String === match) && path[-1].empty?
47
- path[-1] = match
48
- else
49
- path[-1] << match.to_s
50
- end
51
- end
52
-
53
-
54
- ##
55
- # Builds a path array by replacing %n and %% values with matches and splat.
56
- #
57
- # matches = Path.find_in "**/foo=bar", data
58
- # # [["path", "to", "foo"]]
59
- #
60
- # matches.first.make_path "root/%%/foo"
61
- # # ["root", "path", "to", "foo"]
62
- #
63
- # matches = Path.find_in "path/*/(foo)=bar", data
64
- # # [["path", "to", "foo"]]
65
- #
66
- # matches.first.make_path "root/%1/%2"
67
- # # ["root", "to", "foo"]
68
-
69
- def make_path path_map, regex_opts=nil, &block
70
- tmpsplat = @splat.dup
71
- path = []
72
- escape = false
73
- replace = false
74
- new_item = true
75
- rindex = ""
76
-
77
- path_map.to_s.chars do |chr|
78
- case chr
79
- when Kronk::Path::ECH
80
- escape = true
81
-
82
- when Kronk::Path::DCH
83
- new_item = true
84
-
85
- when Kronk::Path::RCH
86
- if replace
87
- if rindex.empty?
88
- unless tmpsplat.empty?
89
- items = tmpsplat.shift[1].dup
90
- if new_item
91
- new_item = false
92
- else
93
- path[-1] = path[-1].dup << items.shift
94
- end
95
- path.concat items
96
- end
97
- replace = false
98
- else
99
- append_match_for(rindex, path)
100
- rindex = ""
101
- end
102
-
103
- next
104
- else
105
- replace = true
106
- end
107
- end and next unless escape
108
-
109
- if replace
110
- if new_item && !rindex.empty? || chr.to_i.to_s != chr || escape
111
- append_match_for(rindex, path) unless rindex.empty?
112
- rindex = ""
113
- replace = false
114
- else
115
- rindex << chr
116
- end
117
- end
118
-
119
- path << "" if new_item
120
- path.last << chr unless replace
121
-
122
- new_item = false
123
- escape = false
124
- end
125
-
126
- append_match_for(rindex, path) unless rindex.empty?
127
-
128
- path
129
- end
130
- end