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.
@@ -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