module Dynamic class << self Thread.main[:DYNAMIC] = Hash.new { |hash, key| raise NameError, "no such dynamic variable: #{key}" } def here! Thread.current[:DYNAMIC] = Hash.new { |hash, key| raise NameError, "no such dynamic variable: #{key}" }.update Thread.main[:DYNAMIC] end def variables Thread.current[:DYNAMIC] or here! end def variable(definition) case definition when Symbol if variables.has_key? definition raise NameError, "dynamic variable `#{definition}' already exists" end variables[definition] = nil when Hash definition.each { |key, value| if variables.has_key? key raise NameError, "dynamic variable `#{key}' already exists" end variables[key] = value } else raise ArgumentError, "can't create a new dynamic variable from #{definition.class}" end end def [](key) variables[key] end def []=(key, value) variables[key] = value end def undefine(*keys) keys.each { |key| self[key] variables.delete key } end def let(bindings, &block) save = {} bindings.to_hash.collect { |key, value| save[key] = self[key] self[key] = value } block.call variables.update save end def method_missing(name, *args) if match = /=\Z/.match(name.to_s) # setter? raise ArgumentError, "invalid setter call" unless args.size == 1 self[match.pre_match.intern] = args.first else raise ArgumentError, "invalid getter call" unless args.empty? self[name] end end end end if $0 == __FILE__ require 'test/unit' class DynamicTest < Test::Unit::TestCase def test_01_variable Dynamic.variable :foo assert_nil Dynamic[:foo] Dynamic.variable :bar => 5 assert_equal 5, Dynamic[:bar] end def test_02_raise assert_raise(NameError) { Dynamic[:notyethere] } Dynamic.variable :notyethere assert_nil Dynamic[:notyethere] assert_raise(NameError) { Dynamic.variable :notyethere } assert_raise(NameError) { Dynamic.variable :notyethere => 9 } assert_raise(NameError) { Dynamic.undefine :blub } end def test_03_set Dynamic.variable :setme assert_nil Dynamic[:setme] Dynamic[:setme] = 23 assert_equal 23, Dynamic[:setme] Dynamic[:setme] = 32 assert_equal 32, Dynamic[:setme] end def test_04_convenience Dynamic.variable :bla => 4 assert_equal 4, Dynamic[:bla] assert_equal 4, Dynamic.bla Dynamic.bla = 5 assert_equal 5, Dynamic.bla Dynamic.bla = nil assert_nil Dynamic.bla Dynamic.undefine :bla assert_raise(NameError) { Dynamic.bla } end def test_05_let Dynamic.variable :one => :one Dynamic.variable :two => :two Dynamic.variable :three => :three assert_equal [:one, :two, :three], [Dynamic.one, Dynamic.two, Dynamic.three] Dynamic.let(:one => 1, :two => :zwei) { # Check new binding assert_equal [1, :zwei, :three], [Dynamic.one, Dynamic.two, Dynamic.three] # Check update Dynamic.two = 2 assert_equal [1, 2, :three], [Dynamic.one, Dynamic.two, Dynamic.three] # Check propagation Dynamic.three = 3 assert_equal [1, 2, 3], [Dynamic.one, Dynamic.two, Dynamic.three] } # Only three has propgated assert_equal [:one, :two, 3], [Dynamic.one, Dynamic.two, Dynamic.three] end def test_06_thread Dynamic.variable :threaded_a => :a Dynamic.variable :threaded_b => :b th = Thread.new { assert_equal :a, Dynamic.threaded_a assert_equal :b, Dynamic.threaded_b Dynamic.threaded_a = 1 # Will not propagate, due to thread locality } th.join assert_equal :a, Dynamic.threaded_a assert_equal :b, Dynamic.threaded_b end end end