forked from aws/aws-sdk-ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstructure.rb
188 lines (176 loc) · 5.08 KB
/
structure.rb
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
require 'thread'
module Aws
# A utilty class that makes it easier to work with Struct objects.
#
# ## Construction
#
# You can construct a Structure with a simple hash.
#
# person = Structure.new(name: 'John Doe', age: 40)
# #=> #<struct name="John Doe", age=40>
#
# ## Empty Structures
#
# The stdlib Struct class does not work with empty member lists.
# Structure solves this by introducing the EmptyStructure class.
#
# struct = Structure.new({})
# #=> #<struct>
#
# ## Structure Classes
#
# In addition to simpler object construction, struct classes are re-used
# automatically.
#
# person1 = Structure.new(name: 'John Doe', age: 40)
# person2 = Structure.new(name: 'Jane Doe', age: 40)
#
# person1.class == person2.class
#
# ## Hash Conversion
#
# Calling {#to_h} or {#to_hash} on a Structure object performs a deep
# conversion of Structure objects into hashes.
#
# person = Structure.new(
# name: "John",
# age: 40,
# friend: Structure.new(name: "Jane", age: 40, friend: nil)
# )
# person.to_h
# #=> {:name=>"John", :age=>40, :friend=>{:name=>"Jane", :age=>40}}
#
class Structure < Struct
@@classes = {}
@@classes_mutex = Mutex.new
if Struct.instance_methods.include?(:to_h)
alias orig_to_h to_h
end
# Deeply converts the Structure into a hash. Structure members that
# are `nil` are omitted from the resultant hash.
#
# You can call #orig_to_h to get vanilla #to_h behavior as defined
# in stdlib Struct.
#
# @return [Hash]
def to_h(obj = self)
case obj
when Struct
obj.members.each.with_object({}) do |member, hash|
value = obj[member]
hash[member] = to_hash(value) unless value.nil?
end
when Hash
obj.each.with_object({}) do |(key, value), hash|
hash[key] = to_hash(value)
end
when Array
obj.collect { |value| to_hash(value) }
else
obj
end
end
alias to_hash to_h
class << self
# Defines a Struct class with the given member names. Returns an
# instance of that class with nil member values.
#
# struct = Structure.new([:name, :age])
# struct.members
# #=> [:name, :age]
#
# struct[:name] #=> nil
# struct[:age] #=> nil
#
# You can provide an ordered list of values to initialize structure
# members with:
#
# struct = Structure.new([:name, :age], ['John Doe', 40])
# struct.members
# #=> [:name, :age]
#
# struct[:name] #=> 'John Doe'
# struct[:age] #=> 40
#
# Calling {new} multiple times with the same list of members will
# reuse Struct classes.
#
# struct1 = Structure.new([:name, :age])
# struct2 = Structure.new([:name, :age])
#
# struct1.class.equal?(struct2.class)
# #=> true
#
# Calling {new} without members, or with an empty list of members
# will return an {EmptyStructure}:
#
# struct = Structure.new
# struct.members
# #=> []
#
# You can also create an empty Structure via {EmptyStructure}.
#
# @overload new(member_names)
# @param [Array<Symbol>] member_names An array of member names.
# @return [Struct]
#
# @overload new(*member_names)
# @param [Symbol] member_names A list of member names.
# @return [Struct]
#
# @overload new(members)
# @param [Hash<Symbol,Object>] members A hash of member names
# and values.
# @return [Struct]
#
# @overload new()
# @return [EmptyStructure]
#
def new(*args)
members, values = parse_args(args)
if members.empty? && self == Structure
EmptyStructure.new
else
struct_class = @@classes[members]
if struct_class.nil?
@@classes_mutex.synchronize do
struct_class = members.empty? ? super(:_) : super(*members)
@@classes[members] = struct_class
end
end
struct_class.new(*values)
end
end
# Deeply converts hashes to Structure objects. Hashes with string
# keys are not converted, but their values are.
# @api private
def from_hash(value)
case value
when Hash
data = value.each.with_object({}) do |(key, value), hash|
hash[key] = from_hash(value)
end
Symbol === data.keys.first ? new(data) : data
when Array
value.map { |v| from_hash(v) }
else value
end
end
private
def parse_args(args)
case args.count
when 0 then [[], []]
when 1 then parse_single_arg(args.first)
else [args, []]
end
end
def parse_single_arg(arg)
case arg
when Array then [arg, []]
when Hash then [arg.keys, arg.values]
else [[arg], []]
end
end
end
end
end