1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
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 #=> #<OpenCascade:0x7fac3680ccf0 {}>
# c.r #=> #<OpenCascade:0x7fac368084c0 {}>
# c.a.b #=> #<OpenCascade:0x7fac3680a4f0 {}>
#
# 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 #=> #<OpenCascade:0x7fac3680ccf0 {}>
# c.each = 4
# c.each! do |k,v|
# ...
# end
#
# c.x!(4).y!(3) #=> #<OpenCascade:0x7fac3680ccf0 {:x=>4, :y=>3}>
#
# Subclassing OpenCascade with cause the new subclass to become the class that
# is auto-created. If this is not the behavior desired, consider using delegation
# instead of subclassing.
#
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] = self.class.new(&leet) }
super(*default, &leet)
end
#
# Alias for original read method.
#
alias :retrieve! :retrieve
#
# Read value given a +key+.
#
# key - Index key to lookup.
#
# Returns value.
#
def retrieve(key)
ckey = cast_key(key)
if @read[ckey]
super(key)
else
@read[ckey] = 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) ? retrieve!(name) : nil # key?(name)
when '!'
__send__(name, *args, &blk)
else
#if key?(name)
retrieve(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 respond_to?(sym, include_private = false)
sym != :to_ary && super
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
e = OpenCascade.new
e.key_proc = key_proc if key_proc
e.merge!(entry)
e
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.
#++
|