Class: Path
- Inherits:
-
Object
- Object
- Path
- Defined in:
- lib/path.rb
Overview
Finds specific data points from a nested Hash or Array data structure through the use of a file-glob-like path selector.
Special characters are: “/ * ? = | \ . , \ ( )” and are interpreted as follows:
:foo/ - walk down tree by one level from key “foo” :*/foo - walk down tree from any parent with key “foo” as a child :foo1|foo2 - return elements with key value of “foo1” or “foo2” :foo(1|2) - same behavior as above :foo=val - return elements where key has a value of val :foo* - return root-level element with key “foo*” (‘*’ char is escaped) :**/foo - recursively search for key “foo” :foo? - return keys that match /Afoo.?Z/ :2..10 - match any integer from 2 to 10 :2…10 - match any integer from 2 to 9 :2,5 - match any integer from 2 to 7
Examples:
# Recursively look for elements with value "val" under top element "root"
Path.find "root/**=val", data
# Find child elements of "root" that have a key of "foo" or "bar"
Path.find "root/foo|bar", data
# Recursively find child elements of root whose value is 1, 2, or 3.
Path.find "root/**=1..3", data
# Recursively find child elements of root of literal value "1..3"
Path.find "root/**=\\1..3", data
Defined Under Namespace
Modules: DataExt, PARENT Classes: Match, Matcher, Transaction
Constant Summary collapse
- VERSION =
'1.0.2'
- REGEX_OPTS =
Mapping of letters to Regexp options.
{ "i" => Regexp::IGNORECASE, "m" => Regexp::MULTILINE, "u" => (Regexp::FIXEDENCODING if defined?(Regexp::FIXEDENCODING)), "x" => Regexp::EXTENDED }
- DCH =
The path item delimiter character “/”
"/"
- RCH =
The replacement character “%” for path mapping
"%"
- VCH =
The path character to assign value “=”
"="
- ECH =
The escape character to use any PATH_CHARS as its literal.
"\\"
- RECH =
The Regexp escaped version of ECH.
Regexp.escape ECH
- EOP =
The EndOfPath delimiter after which regex opt chars may be specified.
DCH + DCH
- PARENT_KEY =
The key string that represents PARENT.
".."
- RECUR_KEY =
The key string that indicates recursive lookup.
"**"
- SPECIAL_CHARS =
"*?()|/."
- R_SPECIAL_CHARS =
/[\0#{Regexp.escape SPECIAL_CHARS}]/u
Class Method Summary collapse
-
.assign_find(matches, data, matcher) ⇒ Object
Common find functionality that assigns to the matches hash.
-
.data_at_path(path_arr, data) ⇒ Object
Returns the data object found at the given path array.
-
.find(path_str, data, regex_opts = nil, &block) ⇒ Object
Fully streamed version of: Path.new(str_path).find_in data.
-
.join(path_arr, escape = true) ⇒ Object
Joins an Array into a path String.
-
.parse_path_str(path, regex_opts = nil) ⇒ Object
Parses a path String into an Array of arrays containing matchers for key, value, and any special modifiers such as recursion.
-
.parse_regex_opts!(path, default = nil) ⇒ Object
Parses the tail end of a path String to determine regexp matching flags.
-
.pathed(data, escape = true) ⇒ Object
Returns a path-keyed data hash.
Instance Method Summary collapse
-
#find_in(data) ⇒ Object
Finds the current path in the given data structure.
-
#initialize(path_str, regex_opts = nil, &block) ⇒ Path
constructor
Instantiate a Path object with a String data path.
Constructor Details
#initialize(path_str, regex_opts = nil, &block) ⇒ Path
Instantiate a Path object with a String data path.
Path.new "/path/**/to/*=bar/../../**/last"
85 86 87 88 |
# File 'lib/path.rb', line 85 def initialize path_str, regex_opts=nil, &block path_str = path_str.dup @path = self.class.parse_path_str path_str, regex_opts, &block end |
Class Method Details
.assign_find(matches, data, matcher) ⇒ Object
Common find functionality that assigns to the matches hash.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/path.rb', line 192 def self.assign_find matches, data, matcher matches.keys.each do |path| pdata = matches.delete path if matcher.key == PARENT path = path[0..-2] subdata = data_at_path path[0..-2], data #!! Avoid yielding parent more than once next if matches[path] yield subdata, path.last, path if block_given? matches[path] = subdata[path.last] next end matcher.find_in pdata, path do |sdata, key, spath| yield sdata, key, spath if block_given? matches[spath] = sdata[key] end end end |
.data_at_path(path_arr, data) ⇒ Object
Returns the data object found at the given path array. Returns nil if not found.
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/path.rb', line 220 def self.data_at_path path_arr, data c_data = data path_arr.each do |key| c_data = c_data[key] end c_data rescue NoMethodError, TypeError nil end |
.find(path_str, data, regex_opts = nil, &block) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/path.rb', line 176 def self.find path_str, data, regex_opts=nil, &block matches = {[] => data} parse_path_str path_str, regex_opts do |matcher, last_item| assign_find matches, data, matcher do |sdata, key, spath| yield sdata, key, spath if last_item && block_given? end end matches end |
.join(path_arr, escape = true) ⇒ Object
Joins an Array into a path String.
159 160 161 162 163 164 165 166 167 |
# File 'lib/path.rb', line 159 def self.join path_arr, escape=true path_str = path_arr.join("\0") if escape path_str.gsub!(R_SPECIAL_CHARS){|c| c == "\0" ? DCH : "\\#{c}"} else path_str.gsub! "\0", DCH end path_str end |
.parse_path_str(path, regex_opts = nil) ⇒ Object
Parses a path String into an Array of arrays containing matchers for key, value, and any special modifiers such as recursion.
Path.parse_path_str "/path/**/to/*=bar/../../**/last"
#=> [["path",ANY_VALUE,false],["to",ANY_VALUE,true],[/.*/,"bar",false],
# [PARENT,ANY_VALUE,false],[PARENT,ANY_VALUE,false],
# ["last",ANY_VALUE,true]]
Note: Path.parse_path_str will slice the original path string until it is empty.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/path.rb', line 247 def self.parse_path_str path, regex_opts=nil path = path.dup regex_opts = parse_regex_opts! path, regex_opts parsed = [] escaped = false key = "" value = nil recur = false next_item = false until path.empty? char = path.slice!(0..0) case char when DCH next_item = true char = "" when VCH value = "" next when ECH escaped = true next end unless escaped char = "#{ECH}#{char}" if escaped if value value << char else key << char end next_item = true if path.empty? if next_item next_item = false if key == RECUR_KEY key = "*" recur = true key = "" and next unless value || path.empty? elsif key == PARENT_KEY key = PARENT if recur key = "" and next unless value key = "*" end end # Make sure we're not trying to access /./thing unless key =~ /^\.?$/ && !value matcher = Matcher.new :key => key, :value => value, :recursive => recur, :regex_opts => regex_opts parsed << matcher yield matcher, path.empty? if block_given? end key = "" value = nil recur = false end escaped = false end parsed end |
.parse_regex_opts!(path, default = nil) ⇒ Object
Parses the tail end of a path String to determine regexp matching flags.
329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/path.rb', line 329 def self.parse_regex_opts! path, default=nil opts = default || 0 return default unless path =~ %r{[^#{RECH}]#{EOP}[#{REGEX_OPTS.keys.join}]+\Z} path.slice!(%r{#{EOP}[#{REGEX_OPTS.keys.join}]+\Z}).to_s. each_char{|c| opts |= REGEX_OPTS[c] || 0} opts if opts > 0 end |
.pathed(data, escape = true) ⇒ Object
Returns a path-keyed data hash. Be careful of mixed key types in hashes as Symbols and Strings both use #to_s.
Path.pathed {'foo' => %w{thing bar}, 'fizz' => {'buzz' => 123}}
#=> {
# '/foo/0' => 'thing',
# '/foo/1' => 'bar',
# '/fizz/buzz' => 123
# }
140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/path.rb', line 140 def self.pathed data, escape=true new_data = {} find "**", data do |subdata, key, path| next if Array === subdata[key] || Hash === subdata[key] path_str = "#{DCH}#{join(path, escape)}" new_data[path_str] = subdata[key] end new_data end |
Instance Method Details
#find_in(data) ⇒ Object
Finds the current path in the given data structure. Returns a Hash of path_ary => data pairs for each match.
If a block is given, yields the parent data object matched, the key, and the path array.
data = {:path => {:foo => :bar, :sub => {:foo => :bar2}}, :other => nil}
path = Path.new "path/**/foo"
all_args = []
path.find_in data |*args|
all_args << args
end
#=> {[:path, :foo] => :bar, [:path, :sub, :foo] => :bar2}
all_args
#=> [
#=> [{:foo => :bar, :sub => {:foo => :bar2}}, :foo, [:path, :foo]],
#=> [{:foo => :bar2}, :foo, [:path, :sub, :foo]]
#=> ]
114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/path.rb', line 114 def find_in data matches = {[] => data} @path.each_with_index do |matcher, i| last_item = i == @path.length - 1 self.class.assign_find(matches, data, matcher) do |sdata, key, spath| yield sdata, key, spath if last_item && block_given? end end matches end |