Project

General

Profile

Feature #14555 ยป ostruct.rb

isiahmeadows (Isiah Meadows), 02/27/2018 12:22 AM

 
1
# frozen_string_literal: true
2
# An attempt to bring OpenStruct closer to reasonable performance.
3

    
4
class OpenStruct
5
  def initialize(hash=nil)
6
    @__table = {}
7
    if hash
8
      hash.each_pair do |k, v|
9
        k = k.to_sym
10
        add_ostruct_member!(k, v)
11
      end
12
    end
13
  end
14

    
15
  def to_h
16
    table = {}
17
    @__table.each_pair do |key, instance_key|
18
      table[key] = instance_variable_get(instance_key)
19
    end
20
    table
21
  end
22

    
23
  def each_pair
24
    return to_enum(__method__) { @__table.size } unless block_given?
25

    
26
    @__table.each_pair do |key, instance_key|
27
      yield key, instance_variable_get(instance_key)
28
    end
29

    
30
    self
31
  end
32

    
33
  alias :marshal_dump :to_h
34
  alias :marshal_load :initialize
35

    
36
  def add_ostruct_member!(key, name, initial)
37
    @__table[key] = :"@#{name}"
38
    instance_variable_set(name, initial)
39
    class << self
40
      attr_accessor key
41
    end
42
  end
43
  private :add_ostruct_member!
44

    
45
  def method_missing(mid, *args)
46
    mid_str = mid.to_s
47
    if mid_str[-1] == '='
48
      if args.length != 1
49
        raise ArgumentError,
50
          "wrong number of arguments (#{args.length} for 1)",
51
          caller(1)
52
      end
53
      add_ostruct_member!(mid_str[0, mid_str.size - 1].to_sym, args[0])
54
    elsif args.length == 0 # and /\A[a-z_]\w*\z/ =~ mid #
55
      nil
56
    else
57
      begin
58
        super
59
      rescue NoMethodError => err
60
        err.backtrace.shift
61
        raise
62
      end
63
    end
64
  end
65

    
66
  def [](name)
67
    instance_key = @__table[name.to_sym]
68
    instance_key && instance_variable_get(instance_key)
69
  end
70

    
71
  def []=(name, value)
72
    name = name.to_sym
73
    instance_key = @__table[name]
74
    if instance_key
75
      instance_variable_set(instance_key, value)
76
    else
77
      add_ostruct_member!(name, value)
78
    end
79
  end
80

    
81
  def dig(name, *names)
82
    result = self
83
    while names.size != 0 && OpenStruct === result
84
      begin
85
        name = name.to_sym
86
      rescue NoMethodError
87
        raise TypeError, "#{name} is not a symbol nor a string"
88
      end
89
      instance_key = @__table[name]
90
      result = instance_key && instance_variable_get(instance_key)
91
      name = names.shift
92
    end
93
    result = result.dig(*names) if names.size != 0 && nil != result
94
    result
95
  end
96

    
97
  def delete_field(name)
98
    name = name.to_sym
99
    instance_key = @__table.delete(key)
100
    if instance_key
101
      remove_instance_variable(instance_key)
102
      class << self
103
        remove_method(name, "#{name}=", instance_key, "#{instance_key}=")
104
      end
105
    else
106
      raise NameError.new("no field `#{name}' in #{self}", name)
107
    end
108
  end
109

    
110
  InspectKey = :__inspect_key__
111

    
112
  def inspect
113
    ids = (Thread.current[InspectKey] ||= [])
114
    str = +'#<'
115
    str << self.class
116
    if ids.include?(object_id)
117
      str << ' ...'
118
    else
119
      ids << object_id
120
      comma = false
121
      begin
122
        @__table.each_pair do |key, instance_key|
123
          value = instance_variable_get(instance_key)
124
          str << ',' if comma
125
          str << " #{key.inspect}=#{value.inspect}"
126
          comma = true
127
        end
128
      ensure
129
        ids.pop
130
      end
131
    end
132
    str << '>'
133
    str
134
  end
135
  alias :to_s :inspect
136

    
137
  def ==(other)
138
    return false unless other.kind_of?(OpenStruct)
139
    return false unless @__table == other.instance_variable_get(:@__table)
140
    @__table.each_value do |key|
141
      unless instance_variable_get(key) == other.instance_variable_get(key)
142
        return false
143
      end
144
    end
145
    return true
146
  end
147

    
148
  def eql?(other)
149
    return false unless other.kind_of?(OpenStruct)
150
    return false unless @__table == other.instance_variable_get(:@__table)
151
    @__table.each_value do |key|
152
      unless instance_variable_get(key).eql? other.instance_variable_get(key)
153
        return false
154
      end
155
    end
156
    return true
157
  end
158

    
159
  def hash
160
    acc = super ^ @__table.hash
161
    @__table.each_value do |key|
162
      acc ^= instance_variable_get(key).hash
163
    end
164
    acc
165
  end
166
end