|
# frozen_string_literal: true
|
|
# An attempt to bring OpenStruct closer to reasonable performance.
|
|
|
|
class OpenStruct
|
|
def initialize(hash=nil)
|
|
@__table = {}
|
|
if hash
|
|
hash.each_pair do |k, v|
|
|
k = k.to_sym
|
|
add_ostruct_member!(k, v)
|
|
end
|
|
end
|
|
end
|
|
|
|
def to_h
|
|
table = {}
|
|
@__table.each_pair do |key, instance_key|
|
|
table[key] = instance_variable_get(instance_key)
|
|
end
|
|
table
|
|
end
|
|
|
|
def each_pair
|
|
return to_enum(__method__) { @__table.size } unless block_given?
|
|
|
|
@__table.each_pair do |key, instance_key|
|
|
yield key, instance_variable_get(instance_key)
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
alias :marshal_dump :to_h
|
|
alias :marshal_load :initialize
|
|
|
|
def add_ostruct_member!(key, name, initial)
|
|
@__table[key] = :"@#{name}"
|
|
instance_variable_set(name, initial)
|
|
class << self
|
|
attr_accessor key
|
|
end
|
|
end
|
|
private :add_ostruct_member!
|
|
|
|
def method_missing(mid, *args)
|
|
mid_str = mid.to_s
|
|
if mid_str[-1] == '='
|
|
if args.length != 1
|
|
raise ArgumentError,
|
|
"wrong number of arguments (#{args.length} for 1)",
|
|
caller(1)
|
|
end
|
|
add_ostruct_member!(mid_str[0, mid_str.size - 1].to_sym, args[0])
|
|
elsif args.length == 0 # and /\A[a-z_]\w*\z/ =~ mid #
|
|
nil
|
|
else
|
|
begin
|
|
super
|
|
rescue NoMethodError => err
|
|
err.backtrace.shift
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
def [](name)
|
|
instance_key = @__table[name.to_sym]
|
|
instance_key && instance_variable_get(instance_key)
|
|
end
|
|
|
|
def []=(name, value)
|
|
name = name.to_sym
|
|
instance_key = @__table[name]
|
|
if instance_key
|
|
instance_variable_set(instance_key, value)
|
|
else
|
|
add_ostruct_member!(name, value)
|
|
end
|
|
end
|
|
|
|
def dig(name, *names)
|
|
result = self
|
|
while names.size != 0 && OpenStruct === result
|
|
begin
|
|
name = name.to_sym
|
|
rescue NoMethodError
|
|
raise TypeError, "#{name} is not a symbol nor a string"
|
|
end
|
|
instance_key = @__table[name]
|
|
result = instance_key && instance_variable_get(instance_key)
|
|
name = names.shift
|
|
end
|
|
result = result.dig(*names) if names.size != 0 && nil != result
|
|
result
|
|
end
|
|
|
|
def delete_field(name)
|
|
name = name.to_sym
|
|
instance_key = @__table.delete(key)
|
|
if instance_key
|
|
remove_instance_variable(instance_key)
|
|
class << self
|
|
remove_method(name, "#{name}=", instance_key, "#{instance_key}=")
|
|
end
|
|
else
|
|
raise NameError.new("no field `#{name}' in #{self}", name)
|
|
end
|
|
end
|
|
|
|
InspectKey = :__inspect_key__
|
|
|
|
def inspect
|
|
ids = (Thread.current[InspectKey] ||= [])
|
|
str = +'#<'
|
|
str << self.class
|
|
if ids.include?(object_id)
|
|
str << ' ...'
|
|
else
|
|
ids << object_id
|
|
comma = false
|
|
begin
|
|
@__table.each_pair do |key, instance_key|
|
|
value = instance_variable_get(instance_key)
|
|
str << ',' if comma
|
|
str << " #{key.inspect}=#{value.inspect}"
|
|
comma = true
|
|
end
|
|
ensure
|
|
ids.pop
|
|
end
|
|
end
|
|
str << '>'
|
|
str
|
|
end
|
|
alias :to_s :inspect
|
|
|
|
def ==(other)
|
|
return false unless other.kind_of?(OpenStruct)
|
|
return false unless @__table == other.instance_variable_get(:@__table)
|
|
@__table.each_value do |key|
|
|
unless instance_variable_get(key) == other.instance_variable_get(key)
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
def eql?(other)
|
|
return false unless other.kind_of?(OpenStruct)
|
|
return false unless @__table == other.instance_variable_get(:@__table)
|
|
@__table.each_value do |key|
|
|
unless instance_variable_get(key).eql? other.instance_variable_get(key)
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
def hash
|
|
acc = super ^ @__table.hash
|
|
@__table.each_value do |key|
|
|
acc ^= instance_variable_get(key).hash
|
|
end
|
|
acc
|
|
end
|
|
end
|