# = opencascade.rb # # == Copyright (c) 2006 Thomas Sawyer # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # # == Author(s) # # * Thomas Sawyer # # == Developer Notes # # TODO Think about this more! # # What about parent when descending downward? # Should parent even be part of OpenObject? # Maybe that should be in a differnt class? # # Should cascading work via hash on the fly like this? # Or perhaps converted all at once? # # Returning nil doesn't work if assigning! # Author:: Thomas Sawyer # Copyright:: Copyright (c) 2006 Thomas Sawyer # License:: Ruby License require 'facets/core/kernel/bool.rb' require 'facets/more/openobject.rb' #require 'facets/more/nullclass' # = OpenCascade # # OpenCascade is subclass of OpenObject, but differs in a few # significant ways. # # Mainly, and why it's labeled a "cascade", every internal Hash # is trandformed into an OpenCascade dynamically upon access. # This makes it easy to create "cascading" references. # # h = { :a => { :b => { :c => 1 } } } # o = OpenCascade.new(h) # o.a.b.c #=> 1 # # OpenCascade also has an inheritance feature which allows lookup # to be passed on to a "parent" if a key is not found. Simple use # the keyword option ++:inherit++ (or ++:parent++). # # p = { :a => 1 } # o = OpenCascade.new( {}, :inherit => p } # o.a #=> 1 # # OpenCascade also supports acquisition which applies inheritance # to every sub-access. Eg. # # o = OpenCascade.new( { :a => 1, :q => {} }, :acquisition => true } # o.q.a #=> 1 # # The two can, of course, be used together. #-- # 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 of nil? 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. #++ class OpenCascade < OpenObject def initialize( init=nil, opt={} ) super( init ) @parent = opt[:inherit] || opt[:parent] @acquire = opt[:acquisition] ? true : false end def method_missing( sym, *args ) type = sym.to_s[-1,1] name = sym.to_s.gsub(/[=!?]$/, '').to_sym if type == '=' @table[name] = args[0] elsif type == '!' and args.size > 0 @table[name] = args[0] self else if @table.key?(name) if Hash === @table[name] if @acquire self.__class__.new( @table[name], :inherit => self, :acquisition => true ) else self.__class__.new( @table[name] ) end else @table[name] end elsif @parent # error catch? @parent.__send__(name) else nil # Kernel.null # @table[name] = instance.class.new end end end def __parent__ ; @parent ; end def __parent__=(x) ; @parent = x ; end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin testing require 'test/unit' class TC01 < Test::Unit::TestCase def test_01_001 f0 = OpenCascade.new f0[:a] = 1 assert_equal( [1], f0.to_a ) assert_equal( {:a=>1}, f0.to_h ) end def test_01_002 f0 = OpenCascade.new( {:a=>1} ) f0[:b] = 2 assert_equal( {:a=>1,:b=>2}, f0.to_h ) end end class TC02 < Test::Unit::TestCase def test_02_001 f0 = OpenCascade.new( :f0=>"f0" ) h0 = { :h0=>"h0" } assert_equal( OpenCascade.new( :f0=>"f0", :h0=>"h0" ), f0.__merge__( h0 ) ) assert_equal( {:f0=>"f0", :h0=>"h0"}, h0.merge( f0 ) ) end def test_02_002 f1 = OpenCascade.new( :f1=>"f1" ) h1 = { :h1=>"h1" } f1.__update__( h1 ) h1.update( f1 ) assert_equal( OpenCascade.new( :f1=>"f1", :h1=>"h1" ), f1 ) assert_equal( {:f1=>"f1", :h1=>"h1"}, h1 ) end end class TC03 < Test::Unit::TestCase def test_01_001 fo = OpenCascade.new 99.times{ |i| fo.__send__( "n#{i}=", 1 ) } 99.times{ |i| assert_equal( 1, fo.__send__( "n#{i}" ) ) } end end =end