pure-struct 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62640381beae69f1f6332211fa9569853d48822e84c6c243b81d49312f059cb6
4
- data.tar.gz: 70cb2eeb0153967d2864594ebdf504ee31014f6c9e7da8d733eb1f6b7a51b1b0
3
+ metadata.gz: 8f67ccb48a2f07b33076622ac6493bbb6911097996551228b404a236d3bd8116
4
+ data.tar.gz: 7421eac80b3a68e526fbc76cb7b61c3a3fc0bf83494fdb6b2c41cab8be07505a
5
5
  SHA512:
6
- metadata.gz: 407a0c254b2b8178e11626d73f64f6fc7ba31b1d27ba7e4157029073d543dba3fdf98e2ccf1c3a8ed97ba278b8f77ce729c736ff90cd2fd729e1994d425ab00e
7
- data.tar.gz: 174c6340a217b17904fa37e44747929f8d87fa1a3aac81d1da1d546aa0ad33b570eb564b7e4503dd254ba1aa2e15d47dbe94cc7921b22e4ea3dae76af1191ea5
6
+ metadata.gz: b09942a08d7dec8df2ada8eb67c1be6045cdc1d3db7093b37dcc67501f579d48650846030f04aa5cb1a2b64b82b8a2b3836ef8db5f26b5050eae8ec2b760359b
7
+ data.tar.gz: cd27449d4554b02ac94c941637691a9309e2e3a7f2813ad8a2fd17c252c6fbef296ed51c00a57177beed89ba96c8d77cbf6f2cac6e54110b58a6b9d7e5b21ab5
@@ -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.1'
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
+ 1.0.2
@@ -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
- # Optional re-implmentation of Struct in Pure Ruby (to get around JS issues in Opal Struct)
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" unless class_name_or_attribute.match(/^[A-Z]/)
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
- alias __new__ new
142
- def new(class_name_or_attribute, *attributes, keyword_init: false)
143
- raise 'Arguments cannot be nil' if ARG_VALIDATION[class_name_or_attribute, *attributes]
144
- class_name = CLASS_NAME_EXTRACTION[class_name_or_attribute]
145
- attributes.unshift(class_name_or_attribute) if class_name.nil?
146
- attributes = attributes.map(&:to_sym)
147
- struct_class = Class.new(self, &CLASS_DEFINITION_FOR_ATTRIBUTES[attributes, keyword_init])
148
- struct_class.singleton_class.define_method(:new) {|*args, &block| __new__(*args, &block)}
149
- class_name.nil? ? struct_class : const_set(class_name, struct_class)
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
@@ -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.1 ruby lib
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.1"
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-09"
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, ["~> 3.12"])
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, ["~> 3.12"])
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.1
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-09 00:00:00.000000000 Z
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: '3.12'
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: '3.12'
40
+ version: 6.3.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement