require 'singleton' class SafeHash < BasicObject attr_reader :hash def initialize hash = {} reinitialize hash end def []= key, value value = SafeHash.new value if value.is_a? Hash @hash[key.to_s] = value end def include? key @hash.include? key.to_s end def [] key, *args key = key.to_s if key.last == '!' key = key[0..-2] if @hash.include? key @hash[key] else raise "No key #{key}" end elsif key.last == '?' key = key[0..-2] @hash.include? key else if @hash.include? key @hash[key] elsif args.empty? SafeNil.instance else return *args end end end def reinitialize hash @hash = {} hash.each do |k, v| v = SafeHash.new v if v.is_a? Hash @hash[k.to_s] = v end # @hash.freeze end def method_missing m, *args m = m.to_s if m.last == '=' self[m[0..-2]] = *args else self[m, *args] end end def to_yaml *args @hash.to_yaml *args end def inspect @hash.inspect end def delete key @hash.delete key.to_s end # deep conversion, check and converts nested SafeHashes to Hashes def to_hash r = {} @hash.each do |k, v| r[k] = if v.respond_to :is_a_safe_hash? v.to_hash else v end end r end alias_method :to_h, :to_hash def is_a_safe_hash? true end class SafeNil < BasicObject include Singleton def [] key, *args key = key.to_s if key.last == '!' raise "No key #{key}" elsif key.last == '?' false elsif args.empty? SafeNil.instance else return *args end end def method_missing m, *args m = m.to_s if m.last == '=' raise "No such key!" else self[m, *args] end end def include? key false end def to_b false end def to_yaml *args nil.to_yaml *args end def to_h {} end def inspect nil.inspect end end end