require 'hashery/open_hash' module Hashery # OpenCascade is subclass of OpenHash. It differs in a few # significant ways. The reason this class is called "cascade" is that # every internal Hash is transformed into an OpenCascade dynamically # upon access. This makes it easy to create "cascading" references. # # h = { :x => { :y => { :z => 1 } } } # c = OpenCascade[h] # c.x.y.z #=> 1 # # As soon as you access a node it automatically becomes an OpenCascade. # # c = OpenCascade.new #=> # # c.r #=> # # c.a.b #=> # # # But if you set a node, then that will be that value. # # c.a.b = 4 #=> 4 # # To query a node without causing the auto-creation of an OpenCasade # instance, use the `?`-mark. # # c.a.z? #=> nil # # OpenCascade also transforms Hashes within Arrays. # # h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 } # c = OpenCascade[h] # c.x.first.a.assert == 1 # c.x.last.a.assert == 2 # # Finally, you can set call a private method via bang methods using the `!`-mark. # # c = OpenCascade.new #=> # # c.each = 4 # c.each! do |k,v| # ... # end # # c.x!(4).y!(3) #=> #4, :y=>3}> # class OpenCascade < OpenHash # #def self.[](hash) # oc = new # hash.each{ |(k,v)| oc.store(k,v) } # oc #end # # Initialize new OpenCascade instance. # # default - The usual default object. # def initialize(*default) @read = {} leet = lambda { |h,k| h[k] = OpenCascade.new(&leet) } super(*default, &leet) end # # Alias for original read method. # alias :read! :read # # Read value given a +key+. # # key - Index keey to lookup. # # Returns value. # def read(key) if @read[cast_key(key)] super(key) else @read[cast_key(key)] = store(key, cast_value(super(key))) end end # # # def method_missing(sym, *args, &blk) type = sym.to_s[-1,1] name = sym.to_s.gsub(/[=!?]$/, '').to_sym case type when '=' store(name, args.first) when '?' key?(name) ? read!(name) : nil # key?(name) when '!' __send__(name, *args, &blk) else #if key?(name) read(name) #else # #default = OpenCascade.new #self.class.new # #default = default_proc ? default_proc.call(self, name) : default # store(name, read(name)) #end end end #def each # super do |key, entry| # yield([key, transform_entry(entry)]) # end #end private # # Cast value, such that Hashes are converted to OpenCascades. # And Hashes in Arrays are converted to OpenCascades as well. # def cast_value(entry) case entry when Hash OpenCascade[entry] #self.class.new(val) when Array entry.map{ |e| cast_value(e) } else entry end end end end #-- # Last, when an entry is not found, 'null' is returned rather then 'nil'. # This allows for run-on entries withuot error. Eg. # # o = OpenCascade.new # o.a.b.c #=> null # # Unfortuately this requires an explict test for null? in 'if' conditions. # # if o.a.b.c.null? # true if null # if o.a.b.c.nil? # true if nil or null # if o.a.b.c.not? # true if nil or null or false # # So be sure to take that into account. #++