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

- old
+ new

@@ -1,143 +1,174 @@ module Dense; class << self def get(o, path) - Dense::Path.new(path).walk(o) { nil } + pa = Dense::Path.new(path) + r = pa.gather(o).inject([]) { |a, e| a << e[2][e[3]] if e.first; a } + + pa.narrow(r) end - def fetch(o, path, default=IndexError, &block) + def fetch(o, path, default=::KeyError, &block) - Dense::Path.new(path).walk(o, default, &block) - end + pa = Dense::Path.new(path) + r = pa.gather(o).partition(&:first) - def set(o, path, value) + if r[0].empty? - path = Dense::Path.new(path) - key = path.pop + return pa.narrow( + r[1].collect { |m| call_default_block(o, path, block, m) } + ) if block - case c = path.walk(o) - when Array then array_set(c, key, value) - when Hash then c[key.to_s] = value - else fail KeyError.new("Found no collection at #{path.to_s.inspect}") + return pa.narrow( + r[1].collect { |m| default } + ) if default != KeyError + + fail miss_error(path, r[1].first) end + pa.narrow(r[0].collect { |e| e[2][e[3]] }) + end + + def set(o, path, value) + + Dense::Path.new(path) + .gather(o) + .each { |hit| + fail_miss_error(path, hit) if hit[0] == false + hit[2][hit[3]] = value } + value end - def unset(o, path) + def unset(o, path, nofail=false) - path = Dense::Path.new(path) - key = path.pop + pa = Dense::Path.new(path) + hits = pa.gather(o) - case c = path.walk(o) - when Array then array_unset(c, key) - when Hash then hash_unset(c, key.to_s) - else fail KeyError.new("Found no collection at #{path.to_s.inspect}") - end + hits.each { |h| fail miss_error(path, h) unless h[0] } unless nofail + + r = hits + .sort_by { |e| "#{e[2].hash}|#{e[3]}" } + .reverse + .inject([]) { |a, e| + next a.push(nil) unless e[0] + k = e[3] + a.push(e[2].is_a?(Array) ? e[2].delete_at(k) : e[2].delete(k)) } + .reverse + + pa.narrow(r) end def insert(o, path, value) - path = Dense::Path.new(path) - key = path.pop + Dense::Path.new(path) + .gather(o) + .each { |hit| + fail_miss_error(path, hit) if hit[0] == false + if hit[2].is_a?(Array) + hit[2].insert(hit[3], value) + else + hit[2][hit[3]] = value + end } - case c = path.walk(o) - when Array then array_insert(c, key, value) - when Hash then c[key.to_s] = value - else fail KeyError.new("Found no collection at #{path.to_s.inspect}") - end - value end def has_key?(o, path) - path = Dense::Path.new(path) - key = path.pop - - case c = path.walk(o) - when Array then array_has_key?(c, key) - when Hash then hash_has_key?(c, key) - else fail IndexError.new("Found no collection at #{path.to_s.inspect}") - end + !! Dense::Path.new(path).gather(o).find { |m| m[0] } end - protected + def path(path) - def array_i(k, may_fail=true) - - case k - when 'first' then 0 - when 'last' then -1 - when Integer then k - else - may_fail ? - fail(IndexError.new("Cannot index array at #{k.inspect}")) : - nil - end + Dense::Path.new(path) end - def array_index(a, k) + def gather(o, path) - i = array_i(k) - i = a.length + i if i < 0 + Dense::Path.new(path).gather(o) + end - fail IndexError.new( - "Array has length of #{a.length}, index is at #{k.inspect}" - ) if i < 0 || i >= a.length + protected - i - end + module DenseError - def array_set(a, k, v) + attr_accessor :full_path, :miss - i = array_index(a, k) + # Used by some "clients" (like flor) to relabel (change the error message) + # a reraise. + # + def relabel(message) - a[i] = v + err = self.class.new(message) + class << err; include DenseError; end + err.set_backtrace(self.backtrace) + err.full_path = self.full_path + err.miss = self.miss + + err + end end - def array_unset(a, k) + def make_error(error_class, message, path, miss) - i = array_index(a, k) + err = error_class.new(message) + class << err; include DenseError; end + err.full_path = path + err.miss = miss - a.delete_at(i) + err end - def hash_unset(h, k) + def key_error(path, miss) - fail KeyError.new("No key #{k.inspect} for hash") unless h.has_key?(k) + path1 = Dense::Path.make(miss[1] + [ miss[3] ]).to_s.inspect + path2 = Dense::Path.make(miss[4]).to_s.inspect - h.delete(k) + msg = "Found nothing at #{path1}" + msg = "#{msg} (#{path2} remains)" if path2 != '""' + + make_error(KeyError, msg, path, miss) end - def array_insert(a, k, v) + def type_error(path, miss) - i = array_i(k) + key = miss[3].inspect + cla = miss[2].class + pat = miss[1].empty? ? 'root' : Dense::Path.make(miss[1]).to_s.inspect - a.insert(i, v) + make_error(TypeError, "No key #{key} for #{cla} at #{pat}", path, miss) end - def array_has_key?(a, k) + def miss_error(path, miss) - i = - array_i(k, false) - i = - if i.nil? - -1 - elsif i < 0 - a.length + i - else - i - end + if miss[2].is_a?(Array) && ! miss[3].is_a?(Integer) + type_error(path, miss) + else + key_error(path, miss) + end + end - i > -1 && i < a.length + def fail_miss_error(path, miss) + + fail miss_error(path, miss) \ + if miss[4].any? + fail type_error(path, miss) \ + if miss[2].is_a?(Array) && ! miss[2].is_a?(Integer) end - def hash_has_key?(h, k) + def call_default_block(o, path, block, miss) - return true if k.is_a?(Integer) && h.has_key?(k.to_s) - h.has_key?(k) + # [ collection, path, + # path before miss, collection at miss, key at miss, path after miss ] + # + args = [ + o, path, + Dense::Path.make(miss[1]), miss[2], miss[3], Dense::Path.make(miss[4]) + ][0, block.arity] + + block.call(*args) end -end; end +end; end # Dense