lib/dense/path.rb in dense-1.0.0 vs lib/dense/path.rb in dense-1.1.0

- old
+ new

@@ -9,13 +9,14 @@ fail ArgumentError.new( "Argument is a #{s.class}, not a String" ) unless s.is_a?(String) - s = ".#{s}" unless s[0, 1] == '[' || s[0, 2] == '.[' + s = ".#{s}" \ + unless s[0, 1] == '[' || s[0, 2] == '.[' -#Raabro.pp(Parser.parse(s, debug: 3)) +#Raabro.pp(Parser.parse(s, debug: 3), colors: true) @path = Parser.parse(s) #Raabro.pp(Parser.parse(s, debug: 3), colors: true) unless @path fail ArgumentError.new( "couldn't determine path from #{s.inspect}" @@ -32,108 +33,37 @@ path.instance_eval { @original = path.to_s } path end - module Parser include Raabro + def single? - # piece parsers bottom to top + ! @path.find { |e| e.is_a?(Symbol) || e.is_a?(Hash) || e.is_a?(Array) } + end - def dqname(i) + def multiple? - rex(:qname, i, %r{ - "( - \\["\\\/bfnrt] | - \\u[0-9a-fA-F]{4} | - [^"\\\b\f\n\r\t] - )*" - }x) - end - - def sqname(i) - - rex(:qname, i, %r{ - '( - \\['\\\/bfnrt] | - \\u[0-9a-fA-F]{4} | - [^'\\\b\f\n\r\t] - )*' - }x) - end - - def dot(i); str(nil, i, '.'); end - def comma(i); rex(nil, i, / *, */); end - def bend(i); str(nil, i, ']'); end - def bstart(i); str(nil, i, '['); end - def blank(i); str(:blank, i, ''); end - - def name(i); rex(:name, i, /[-+%^<>a-zA-Z0-9_\/\\=?]+/); end - def off(i); rex(:off, i, /-?\d+/); end - - def star(i); str(:star, i, '*'); end - - def ses(i) # start:end:step - rex( - :ses, - i, - /( - (-?\d+)?:(-?\d+)?:(-?\d+)? | - (-?\d+)?:(-?\d+)? | - -?\d+ - )/x) - end - - def escape(i); rex(:esc, i, /\\[.*]/); end - - def bindex(i); alt(:index, i, :dqname, :sqname, :star, :ses, :name, :blank); end - def bindexes(i); jseq(:bindexes, i, :bindex, :comma); end - def bracket_index(i); seq(nil, i, :bstart, :bindexes, :bend); end - def simple_index(i); alt(:index, i, :off, :escape, :star, :name); end - - def dotdot(i); str(:dotdot, i, '.'); end - - def dot_then_index(i); seq(nil, i, :dot, :simple_index); end - def index(i); alt(nil, i, :dot_then_index, :bracket_index, :dotdot); end - - def path(i); rep(:path, i, :index, 1); end - - # rewrite parsed tree - - def rewrite_ses(t) - a = t.string.split(':').collect { |e| e.empty? ? nil : e.to_i } - return a[0] if a[1] == nil && a[2] == nil - { start: a[0], end: a[1], step: a[2] } - end - def rewrite_esc(t); t.string[1, 1]; end - def rewrite_star(t); :star; end - def rewrite_dotdot(t); :dot; end - def rewrite_off(t); t.string.to_i; end - def rewrite_index(t); rewrite(t.sublookup); end - def rewrite_bindexes(t); - indexes = t.subgather.collect { |tt| rewrite(tt) } - indexes.length == 1 ? indexes[0] : indexes.compact - end - - def rewrite_blank(t); nil; end - - def rewrite_qname(t); t.string[1..-2]; end - def rewrite_name(t); t.string; end - - def rewrite_path(t) - t.subgather.collect { |tt| rewrite(tt) } - end - + ! single? end def to_a @path end def length; @path.length; end alias size length + def any?; @path.any?; end + def empty?; @path.empty?; end + + def first; @path.first; end + def last; @path.last; end + + def pop; @path.pop; end + def shift; @path.shift; end + def to_s o = StringIO.new @path.each { |e| @@ -144,24 +74,10 @@ s = o.string s[0, 2] == '..' ? s[1..-1] : s end - def walk(data, default=nil, &block) - - _walk(data, @path) - - rescue IndexError => ie - - return yield(@original, self) if block - return default if default != nil && default != IndexError - - fail ie.expand(self) if ie.respond_to?(:expand) - - raise - end - def [](offset, count=nil) if count == nil && offset.is_a?(Integer) @path[offset] elsif count @@ -175,74 +91,154 @@ other.class == self.class && other.to_a == @path end - def last + def -(path) - @path.last + self.class.make(subtract(@path.dup, path.to_a.dup)) end - def pop + def narrow(outcome) - @path.pop + single? ? outcome.first : outcome end - def -(path) + def gather(data) - self.class.make(subtract(@path.dup, path.to_a.dup)) + _gather(0, [], nil, data, @path, []) + .inject({}) { |h, hit| h[(hit[1] + [ hit[3] ]).inspect] ||= hit; h } + .values end protected - class NotIndexableError < ::IndexError + def _keys(o) - attr_reader :container_class, :root_path, :remaining_path + return (0..o.length - 1).to_a if o.is_a?(Array) + return o.keys if o.is_a?(Hash) + nil + end - def initialize(container, root_path, remaining_path, message=nil) + def _resolve_hash_key(o, k) - @container_class = container.is_a?(Class) ? container : container.class + return [ nil ] unless o.is_a?(Array) - @root_path = Dense::Path.make(root_path) - @remaining_path = Dense::Path.make(remaining_path) + be = k[:start] || 0 + en = k[:end] || o.length - 1 + st = k[:step] || 1 - if message - super( - message) - elsif @root_path - super( - "Found nothing at #{fail_path.to_s.inspect} " + - "(#{@remaining_path.original.inspect} remains)") - else - super( - "Cannot index instance of #{container_class} " + - "with #{@remaining_path.original.inspect}") - end - end + Range.new(be, en).step(st).to_a + end - def expand(root_path) + def _resolve_key(o, k) - err = self.class.new(container_class, root_path, remaining_path, nil) - err.set_backtrace(self.backtrace) + return _resolve_hash_key(o, k) if k.is_a?(Hash) - err + return [ k.to_s ] if o.is_a?(Hash) + + case k + when /\Afirst\z/i then [ 0 ] + when /\Alast\z/i then [ -1 ] + else [ k ] end + end - def relabel(message) + def _resolve_keys(o, k) - err = self.class.new(container_class, root_path, remaining_path, message) - err.set_backtrace(self.backtrace) + ks = k.is_a?(Hash) ? [ k ] : Array(k) + ks = ks.inject([]) { |a, kk| a.concat(_resolve_key(o, kk)) } + end - err + def _stars(data0, data, key, path=[], acc=[]) + +#p [ :_stars, key, path, data0, data ] + acc.push([ path, data0, data ]) if path.any? + + return acc unless data.is_a?(Hash) || data.is_a?(Array) + + return acc unless key + key = key == :dotstar ? key : nil + + if data.is_a?(Array) + data.each_with_index { |e, i| _stars(data, e, key, path + [ i ], acc) } + else + data.each { |k, v| _stars(data, v, key, path + [ k ], acc) } end - def fail_path + acc + end - @fail_path ||= (@root_path ? @root_path - @remaining_path : nil) + def _dot_gather(depth, path0, data0, data, path, acc) + +#ind = ' ' * depth +#puts ind + "+--- _dot_gather()" +#puts ind + "| path0: #{path0.inspect}" +#puts ind + "| data: #{data.inspect}" +#puts ind + "| depth: #{depth} / path: #{path.inspect}" + + a = _gather(depth, path0, data0, data, path, []).select { |r| r.first } + return acc.concat(a) if a.any? + + keys = _keys(data) + + return acc unless keys + + keys.each { |k| + _dot_gather(depth + 1, path0 + [ k ], data, data[k], path, acc) } + + acc + end + + def _index(o, k) + + case o + when Array then k.is_a?(Integer) ? o[k] : nil + when Hash then o[k] + else nil end end + def _gather(depth, path0, data0, data, path, acc) + + k = path.first +#ind = ' ' * depth +#print [ LG, DG, LB ][depth % 3] +#puts ind + "+--- _gather()" +#puts ind + "| path0: #{path0.inspect}" +#puts ind + "| data: #{data.inspect}" +#puts ind + "| depth: #{depth} / path: #{path.inspect}" +#puts ind + "| k: " + k.inspect + +#puts RD + ind + "| -> " + [ false, path0[0..-2], data0, path0.last, path ].inspect if k.nil? && data.nil? + return acc.push([ false, path0[0..-2], data0, path0.last, path ]) \ + if data.nil? + +#puts GN + ind + "| -> " + [ true, path0[0..-2], data0, path0.last ].inspect if k.nil? && data.nil? + return acc.push([ true, path0[0..-2], data0, path0.last ]) \ + if k.nil? + +#puts RD + ind + "| -> " + [ false, path0[0..-2], data0, path0.last, path ].inspect unless data.is_a?(Array) || data.is_a?(Hash) + return acc.push([ false, path0[0..-2], data0, path0.last, path ]) \ + unless data.is_a?(Array) || data.is_a?(Hash) + + return _dot_gather(depth, path0, data0, data, path[1..-1], acc) \ + if k == :dot + +#puts ind + "| stars:\n" + _stars(data0, data, k).collect(&:first).to_pp if k == :star || k == :dotstar + return _stars(data0, data, k).inject(acc) { |a, (pa, da0, da)| + _gather(depth + 1, path0 + pa, da0, da, path[1..-1], a) + } if k == :star || k == :dotstar + + keys = _resolve_keys(data, k) +#puts ind + "| keys: " + keys.inspect + + keys.inject(acc) { |a, kk| + _gather( + depth + 1, path0 + [ kk ], data, _index(data, kk), path[1..-1], a) } + end + def subtract(apath0, apath1) while apath0.any? && apath1.any? && apath0.last == apath1.last apath0.pop apath1.pop @@ -258,17 +254,17 @@ s = [ "#{elt[:start]}:#{elt[:end]}", elt[:step] ].compact.join(':') in_array ? s : "[#{s}]" when Array "[#{elt.map { |e| _to_s(e, true) }.join(',')}#{elt.size < 2 ? ',' : ''}]" when String - #in_array ? elt.inspect : elt.to_s - #in_array ? _quote_s(elt) : _maybe_quote_s(elt) _str_to_s(elt, in_array) when :star '*' when :dot '.' + when :dotstar + '..*' else elt.to_s end end @@ -280,105 +276,7 @@ return "\\#{s}" if s == '.' || s == '*' return "[#{elt.inspect}]" if s =~ /["']/ s end - - def _walk(data, path) - - return data if path.empty? - - case pa = path.first - when :dot then _walk_dot(data, pa, path) - when :star then _walk_star(data, pa, path) - when Hash then _walk_start_end_step(data, pa, path) - when Integer then _walk_int(data, pa, path) - when String then _walk(_sindex(data, pa), path[1..-1]) - else fail IndexError.new("Unwalkable index in path: #{pa.inspect}") - end - end - - def _walk_star(data, pa, path) - - case data - when Array then data.collect { |d| _walk(d, path[1..-1]) } - when Hash then data.values.collect { |d| _walk(d, path[1..-1]) } - else data - end - end - - def _walk_dot(data, pa, path) - - _run(data, path[1]) - .inject([]) { |a, d| - a.concat( - begin - [ _walk(d, path[2..-1]) ] - rescue NotIndexableError - [] - end) } - end - - def _walk_start_end_step(data, pa, path) - - be = pa[:start] || 0 - en = pa[:end] || data.length - 1 - st = pa[:step] || 1 - Range.new(be, en).step(st).collect { |i| _walk(data[i], path[1..-1]) } - end - - def _walk_int(data, pa, path) - - if data.is_a?(Array) - return _walk(data[pa], path[1..-1]) - end - - if data.is_a?(Hash) - return _walk(data[pa], path[1..-1]) if data.has_key?(pa) - pa = pa.to_s - return _walk(data[pa], path[1..-1]) if data.has_key?(pa) - end - - fail NotIndexableError.new(data, nil, path) - end - - def _sindex(data, key) - - case data - when Hash - data[key] - when Array - case key - when /\Afirst\z/i then data[0] - when /\Alast\z/i then data[-1] - else fail IndexError.new("Cannot index array with #{key.inspect}") - end - else - fail IndexError.new("Cannot index #{data.class} with #{key.inspect}") - end - end - - def _run(d, key) - - case d - when Hash then _run_hash(d, key) - when Array then _run_array(d, key) - else key == :star ? [ d ] : [] - end - end - - def _run_hash(d, key) - - if key == :star - [ d ] + d.values.inject([]) { |a, v| a.concat(_run(v, key)) } - else - d.inject([]) { |a, (k, v)| a.concat(k == key ? [ v ] : _run(v, key)) } - end - end - - def _run_array(d, key) - - (key == :star ? [ d ] : []) + - d.inject([]) { |r, e| r.concat(_run(e, key)) } - end -end +end # Dense::Path