pure-struct 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +7 -1
- data/VERSION +1 -1
- data/lib/pure-struct.rb +152 -82
- data/pure-struct.gemspec +5 -5
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f67ccb48a2f07b33076622ac6493bbb6911097996551228b404a236d3bd8116
|
4
|
+
data.tar.gz: 7421eac80b3a68e526fbc76cb7b61c3a3fc0bf83494fdb6b2c41cab8be07505a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b09942a08d7dec8df2ada8eb67c1be6045cdc1d3db7093b37dcc67501f579d48650846030f04aa5cb1a2b64b82b8a2b3836ef8db5f26b5050eae8ec2b760359b
|
7
|
+
data.tar.gz: cd27449d4554b02ac94c941637691a9309e2e3a7f2813ad8a2fd17c252c6fbef296ed51c00a57177beed89ba96c8d77cbf6f2cac6e54110b58a6b9d7e5b21ab5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 1.0.2
|
4
|
+
|
5
|
+
- Ensure StructClass.inspect shows `(keyword_init: true)` (e.g. `Person(keyword_init: true) `)
|
6
|
+
- Implement to_s (e.g. `#<struct Person full_name=nil, age=nil>` for `Person` class )
|
7
|
+
- Fix issue with ability to handle cycles in to_s/inspect implementation (printing #<struct #<Class:0x00007ff874073590>:...> when self object is encountered within attributes)
|
8
|
+
- Fix issue with ability to handle cycles in hash implementation, including Opal
|
9
|
+
- Fix issue with ability to handle cycles in equality
|
10
|
+
- Fix issue with ability to handle cycles in equivalence
|
11
|
+
|
3
12
|
## 1.0.1
|
4
13
|
|
5
14
|
- Include `Enumerable` just as in native `Struct`
|
data/README.md
CHANGED
@@ -22,7 +22,13 @@ Run:
|
|
22
22
|
Or add to [Gemfile](https://bundler.io/man/gemfile.5.html):
|
23
23
|
|
24
24
|
```ruby
|
25
|
-
gem 'pure-struct', '~> 1.0.
|
25
|
+
gem 'pure-struct', '~> 1.0.2'
|
26
|
+
```
|
27
|
+
|
28
|
+
If you want to use in [Opal](https://opalrb.com/) only inside [Rails](https://rubyonrails.org/), suffix with `require: false`:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'pure-struct', '~> 1.0.2', require: false
|
26
32
|
```
|
27
33
|
|
28
34
|
And, run:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.2
|
data/lib/pure-struct.rb
CHANGED
@@ -19,16 +19,48 @@
|
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
|
+
# Native Ruby built-in Struct implementation aliased as NativeStruct (with Struct pointing to the new pure Ruby implementation)
|
22
23
|
NativeStruct = Struct
|
23
24
|
Object.send(:remove_const, :Struct) if Object.constants.include?(:Struct)
|
24
25
|
|
25
|
-
#
|
26
|
+
# Pure Ruby re-implementation of Struct to ensure cross-Ruby functionality where needed (e.g. Opal)
|
27
|
+
#
|
28
|
+
# Struct class object instances store members in @members Array and member values in @member_values Hash
|
29
|
+
#
|
30
|
+
# API perfectly matches that of Native Ruby built-in Struct.
|
31
|
+
#
|
32
|
+
# Native Ruby built-in Struct implementation is aliased as NativeStruct
|
26
33
|
class Struct
|
27
34
|
include Enumerable
|
28
35
|
|
29
36
|
class << self
|
37
|
+
alias __new__ new
|
38
|
+
send(:private, :__new__)
|
39
|
+
|
40
|
+
def new(class_name_or_attribute, *attributes, keyword_init: false)
|
41
|
+
raise 'Arguments cannot be nil' if ARG_VALIDATION[class_name_or_attribute, *attributes]
|
42
|
+
class_name = CLASS_NAME_EXTRACTION[class_name_or_attribute]
|
43
|
+
attributes.unshift(class_name_or_attribute) if class_name.nil?
|
44
|
+
attributes = attributes.map(&:to_sym)
|
45
|
+
struct_class = Class.new(self, &CLASS_DEFINITION_FOR_ATTRIBUTES[attributes, keyword_init])
|
46
|
+
class_name.nil? ? struct_class : const_set(class_name, struct_class)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
30
51
|
CLASS_DEFINITION_FOR_ATTRIBUTES = lambda do |attributes, keyword_init|
|
31
52
|
lambda do |defined_class|
|
53
|
+
defined_class.class_variable_set(:@@attributes, attributes)
|
54
|
+
defined_class.class_variable_set(:@@keyword_init, keyword_init)
|
55
|
+
|
56
|
+
defined_class.singleton_class.define_method(:new) {|*args, &block| __new__(*args, &block)}
|
57
|
+
defined_class.singleton_class.alias_method(:__inspect__, :inspect)
|
58
|
+
defined_class.private_class_method :__inspect__
|
59
|
+
|
60
|
+
defined_class.singleton_class.define_method(:inspect) do
|
61
|
+
"#{__inspect__}#{'(keyword_init: true)' if keyword_init}"
|
62
|
+
end
|
63
|
+
|
32
64
|
attributes.each do |attribute|
|
33
65
|
define_method(attribute) do
|
34
66
|
self[attribute]
|
@@ -38,74 +70,6 @@ class Struct
|
|
38
70
|
end
|
39
71
|
end
|
40
72
|
|
41
|
-
define_method(:members) do
|
42
|
-
(@members ||= attributes).clone
|
43
|
-
end
|
44
|
-
|
45
|
-
def []=(attribute, value)
|
46
|
-
normalized_attribute = attribute.to_sym
|
47
|
-
raise NameError, "no member #{attribute} in struct" unless @members.include?(normalized_attribute)
|
48
|
-
@member_values[normalized_attribute] = value
|
49
|
-
end
|
50
|
-
|
51
|
-
def [](attribute)
|
52
|
-
normalized_attribute = attribute.to_sym
|
53
|
-
raise NameError, "no member #{attribute} in struct" unless @members.include?(normalized_attribute)
|
54
|
-
@member_values[normalized_attribute]
|
55
|
-
end
|
56
|
-
|
57
|
-
def each(&block)
|
58
|
-
to_a.each(&block)
|
59
|
-
end
|
60
|
-
|
61
|
-
def each_pair(&block)
|
62
|
-
@member_values.each_pair(&block)
|
63
|
-
end
|
64
|
-
|
65
|
-
def to_h
|
66
|
-
@member_values.clone
|
67
|
-
end
|
68
|
-
|
69
|
-
def to_a
|
70
|
-
@member_values.values
|
71
|
-
end
|
72
|
-
|
73
|
-
def size
|
74
|
-
@members.size
|
75
|
-
end
|
76
|
-
alias length size
|
77
|
-
|
78
|
-
def dig(*args)
|
79
|
-
@member_values.dig(*args)
|
80
|
-
end
|
81
|
-
|
82
|
-
def select(&block)
|
83
|
-
to_a.select(&block)
|
84
|
-
end
|
85
|
-
|
86
|
-
def eql?(other)
|
87
|
-
instance_of?(other.class) &&
|
88
|
-
@members.all? { |key| self[key].eql?(other[key]) }
|
89
|
-
end
|
90
|
-
|
91
|
-
def ==(other)
|
92
|
-
other = coerce(other).first if respond_to?(:coerce, true)
|
93
|
-
other.kind_of?(self.class) &&
|
94
|
-
@members.all? { |key| self[key] == other[key] }
|
95
|
-
end
|
96
|
-
|
97
|
-
def hash
|
98
|
-
if RUBY_ENGINE == 'opal'
|
99
|
-
# Opal doesn't implement hash as Integer everywhere, returning strings as themselves,
|
100
|
-
# so build a String everywhere for now as the safest common denominator to be consistent.
|
101
|
-
self.class.hash.to_s +
|
102
|
-
to_a.each_with_index.map {|value, i| (i+1).to_s + value.hash.to_s}.reduce(:+)
|
103
|
-
else
|
104
|
-
self.class.hash +
|
105
|
-
to_a.each_with_index.map {|value, i| i+1 * value.hash}.sum
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
73
|
if keyword_init
|
110
74
|
def initialize(struct_class_keyword_args = {})
|
111
75
|
members
|
@@ -124,7 +88,9 @@ class Struct
|
|
124
88
|
end
|
125
89
|
end
|
126
90
|
end
|
91
|
+
|
127
92
|
end
|
93
|
+
|
128
94
|
end
|
129
95
|
|
130
96
|
ARG_VALIDATION = lambda do |class_name_or_attribute, *attributes|
|
@@ -132,22 +98,126 @@ class Struct
|
|
132
98
|
end
|
133
99
|
|
134
100
|
CLASS_NAME_EXTRACTION = lambda do |class_name_or_attribute|
|
135
|
-
if class_name_or_attribute.is_a?(String) && RUBY_ENGINE != 'opal'
|
136
|
-
raise NameError, "identifier name needs to be constant"
|
101
|
+
if class_name_or_attribute.is_a?(String) && (RUBY_ENGINE != 'opal' || class_name_or_attribute.match(/^[A-Z]/))
|
102
|
+
raise NameError, "identifier name needs to be constant" if RUBY_ENGINE != 'opal' && !class_name_or_attribute.match(/^[A-Z]/)
|
137
103
|
class_name_or_attribute
|
138
104
|
end
|
139
105
|
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns member symbols (strings in Opal) representing Struct attribute names
|
110
|
+
def members
|
111
|
+
(@members ||= self.class.class_variable_get(:@@attributes)).clone
|
112
|
+
end
|
113
|
+
|
114
|
+
def []=(attribute, value)
|
115
|
+
normalized_attribute = attribute.to_sym
|
116
|
+
raise NameError, "no member #{attribute} in struct" unless @members.include?(normalized_attribute)
|
117
|
+
@member_values[normalized_attribute] = value
|
118
|
+
end
|
119
|
+
|
120
|
+
def [](attribute)
|
121
|
+
normalized_attribute = attribute.to_sym
|
122
|
+
raise NameError, "no member #{attribute} in struct" unless @members.include?(normalized_attribute)
|
123
|
+
@member_values[normalized_attribute]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Iterates through each value
|
127
|
+
def each(&block)
|
128
|
+
to_a.each(&block)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Iterates through each member value pair
|
132
|
+
def each_pair(&block)
|
133
|
+
@member_values.each_pair(&block)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns member values Hash (includes member keys)
|
137
|
+
def to_h
|
138
|
+
@member_values.clone
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns values Array (no member keys)
|
142
|
+
def to_a
|
143
|
+
@member_values.values
|
144
|
+
end
|
145
|
+
|
146
|
+
# Original Object #inspect implementation
|
147
|
+
alias __inspect__ inspect
|
148
|
+
send(:private, :__inspect__)
|
149
|
+
|
150
|
+
# Prints Struct member values including class name if set
|
151
|
+
#
|
152
|
+
# #inspect does the same thing
|
153
|
+
#
|
154
|
+
# __inspect__ aliases the original Object inspect implementation
|
155
|
+
def to_s
|
156
|
+
member_values_string = @member_values.map do |member, value|
|
157
|
+
if value.equal?(self)
|
158
|
+
value_class_string = self.class.send(:__inspect__).split.first
|
159
|
+
value_string = "#<struct #{value_class_string}:...>"
|
160
|
+
else
|
161
|
+
value_string = value.inspect
|
162
|
+
end
|
163
|
+
"#{member}=#{value_string}"
|
164
|
+
end.join(', ')
|
165
|
+
class_name_string = "#{self.class.name} " unless self.class.name.nil?
|
166
|
+
"#<struct #{class_name_string}#{member_values_string}>"
|
167
|
+
end
|
168
|
+
alias inspect to_s
|
169
|
+
|
170
|
+
def size
|
171
|
+
@members.size
|
172
|
+
end
|
173
|
+
alias length size
|
174
|
+
|
175
|
+
def dig(*args)
|
176
|
+
@member_values.dig(*args)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Selects values with block (only value is passed in as block arg)
|
180
|
+
def select(&block)
|
181
|
+
to_a.select(&block)
|
182
|
+
end
|
183
|
+
|
184
|
+
def eql?(other)
|
185
|
+
instance_of?(other.class) &&
|
186
|
+
@members.all? { |key| (self[key].equal?(self) && other[key].equal?(self)) || self[key].eql?(other[key]) }
|
187
|
+
end
|
188
|
+
|
189
|
+
def ==(other)
|
190
|
+
other = coerce(other).first if respond_to?(:coerce, true)
|
191
|
+
other.kind_of?(self.class) &&
|
192
|
+
@members.all? { |key| (self[key].equal?(self) && other[key].equal?(self)) || self[key] == other[key] }
|
193
|
+
end
|
194
|
+
|
195
|
+
# Return compound hash value consisting of Struct class hash and all indexed value hashes
|
196
|
+
#
|
197
|
+
# Opal doesn't implement hash as Integer everywhere, returning strings as themselves,
|
198
|
+
# so this returns a String in Opal as a safe common denominator for all object types.
|
199
|
+
def hash
|
200
|
+
return __hash__opal__ if RUBY_ENGINE == 'opal'
|
201
|
+
class_hash = self.class.hash
|
202
|
+
indexed_values = to_a.each_with_index
|
203
|
+
value_hashes = indexed_values.map do |value, i|
|
204
|
+
value_hash = value.equal?(self) ? class_hash : value.hash
|
205
|
+
i+1 * value_hash
|
150
206
|
end
|
151
|
-
|
207
|
+
class_hash + value_hashes.sum
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
def __hash__opal__
|
213
|
+
class_hash = self.class.hash
|
214
|
+
indexed_values = to_a.each_with_index
|
215
|
+
class_hash = class_hash.to_s
|
216
|
+
value_hashes = indexed_values.map do |value, i|
|
217
|
+
value_hash_string = value.equal?(self) ? class_hash : value.hash.to_s
|
218
|
+
(i+1).to_s + value_hash_string
|
219
|
+
end
|
220
|
+
class_hash + value_hashes.reduce(:+)
|
152
221
|
end
|
222
|
+
|
153
223
|
end
|
data/pure-struct.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: pure-struct 1.0.
|
5
|
+
# stub: pure-struct 1.0.2 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "pure-struct".freeze
|
9
|
-
s.version = "1.0.
|
9
|
+
s.version = "1.0.2"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2021-01-
|
14
|
+
s.date = "2021-01-10"
|
15
15
|
s.description = "Pure Ruby re-implementation of Struct to ensure cross-Ruby functionality where needed (e.g. Opal)".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -38,7 +38,7 @@ Gem::Specification.new do |s|
|
|
38
38
|
|
39
39
|
if s.respond_to? :add_runtime_dependency then
|
40
40
|
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
41
|
-
s.add_development_dependency(%q<rdoc>.freeze, ["
|
41
|
+
s.add_development_dependency(%q<rdoc>.freeze, [">= 6.3.0"])
|
42
42
|
s.add_development_dependency(%q<bundler>.freeze, [">= 1.0"])
|
43
43
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
44
44
|
s.add_development_dependency(%q<simplecov>.freeze, ["~> 0.16.0"])
|
@@ -46,7 +46,7 @@ Gem::Specification.new do |s|
|
|
46
46
|
s.add_development_dependency(%q<puts_debuggerer>.freeze, [">= 0"])
|
47
47
|
else
|
48
48
|
s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
49
|
-
s.add_dependency(%q<rdoc>.freeze, ["
|
49
|
+
s.add_dependency(%q<rdoc>.freeze, [">= 6.3.0"])
|
50
50
|
s.add_dependency(%q<bundler>.freeze, [">= 1.0"])
|
51
51
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
52
52
|
s.add_dependency(%q<simplecov>.freeze, ["~> 0.16.0"])
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pure-struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: rdoc
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.3.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 6.3.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|