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