trinkets 0.4.0 → 0.5.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b95bff0eaa948f0edc0375709fd3ec786ad10e970f5b5abcd501247dceb1e74
4
- data.tar.gz: 3d4d1bce01e034167209cad41d9bf4ad356d79ddf100cfc1968d16b9686a06f9
3
+ metadata.gz: 2e815777ee25a04feffd792fc975e68256e346eb3ff77126bd3610b949deadb2
4
+ data.tar.gz: 77a0c78f5dc7699e2d90e7db69333b246023145c66e408901ae328d29d377385
5
5
  SHA512:
6
- metadata.gz: 5b3a7cc743fae16ee12cfab9d9efc4236b31d69d8a570fe1bbf009cc3558be08d3cef3a38aa110403fb40dea019a3236adbf4a00c983c5f1f4e148a909bfb884
7
- data.tar.gz: e94881a3dc54ca93be7ea759b0b6aff8607b9d91d7d1247d3ec2f59d265ecb2ea4c7f014754ca9a4a351ee6994eabb2fcae0e93fe0d14d27a46b01985370c3f6
6
+ metadata.gz: e57c30e56c22137542e58d2128d9a8ddb31e78acca1756c3337fa54a906046790997fecb8737b2c442528920756cfc8ccab465ac47489bb44257c4cecb9c9d03
7
+ data.tar.gz: 49ed92fdb96d888727ebc972cd71520f08fb603cad2345d9832cf9f8704d99e979cf02cd10c268dea5c12e8681f4bccdd49b8c9eca7a68a2025db002b349c74b
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
1
 
2
+ ### [0.5.0](https://github.com/SilverPhoenix99/trinkets/tree/v0.5.0)
3
+ * `Class::init`: Added `:super` option.
4
+
2
5
  ### [0.4.0](https://github.com/SilverPhoenix99/trinkets/tree/v0.4.0)
3
6
  * `Class::init`: Allow default values for keyword arguments.
4
7
 
data/doc/class/init.md CHANGED
@@ -14,6 +14,9 @@ To use it, define a class and call `::init` like you would call `::attr` methods
14
14
  * when it's a hash, like `{ default: <VALUE> }`, it's an optional keyword argument
15
15
  * an empty hash `{}` is equivalent to `{ default: nil }`
16
16
  * defaults to `false`
17
+ * `super` : if the argument should be passed to `super()`
18
+ * the super class can have a `initialize` or call `init`
19
+ * default: `false`
17
20
 
18
21
  The same options can be used per individual argument.
19
22
 
@@ -44,8 +47,9 @@ end
44
47
  class Test
45
48
  attr_accessor :a, :b
46
49
  def initialize(a, b)
47
- @a = a
48
- @b = b
50
+ super()
51
+ @a = a unless instance_variable_defined?(:@a)
52
+ @b = b unless instance_variable_defined?(:@b)
49
53
  end
50
54
  end
51
55
 
@@ -71,8 +75,9 @@ end
71
75
  class TestAttr
72
76
  attr_reader :a, :b
73
77
  def initialize(a, b)
74
- @a = a
75
- @b = b
78
+ super()
79
+ @a = a unless instance_variable_defined?(:@a)
80
+ @b = b unless instance_variable_defined?(:@b)
76
81
  end
77
82
  end
78
83
 
@@ -98,8 +103,9 @@ end
98
103
  class TestKW
99
104
  attr_accessor :a, :b
100
105
  def initialize(a:, b:)
101
- @a = a
102
- @b = b
106
+ super()
107
+ @a = a unless instance_variable_defined?(:@a)
108
+ @b = b unless instance_variable_defined?(:@b)
103
109
  end
104
110
  end
105
111
 
@@ -126,10 +132,11 @@ class TestAttrOptions
126
132
  attr_reader :a
127
133
  attr_accessor :b, :c
128
134
  def initialize(b, d, a:, c:)
129
- @a = a
130
- @b = b
131
- @c = c
132
- @d = d
135
+ super()
136
+ @a = a unless instance_variable_defined?(:@a)
137
+ @b = b unless instance_variable_defined?(:@b)
138
+ @c = c unless instance_variable_defined?(:@c)
139
+ @d = d unless instance_variable_defined?(:@d)
133
140
  end
134
141
  end
135
142
 
@@ -152,7 +159,6 @@ test.a = 5
152
159
  ```
153
160
 
154
161
  ## Default values for keyword arguments
155
-
156
162
  ```ruby
157
163
  class TestDefaultKw
158
164
  init [:a, kw: true],
@@ -163,9 +169,10 @@ end
163
169
  # would be the same as
164
170
  class TestDefaultKw
165
171
  attr_accessor :a, :b
166
- def initialize(a: , b: 3)
167
- @a = a
168
- @b = b
172
+ def initialize(a:, b: 3)
173
+ super()
174
+ @a = a unless instance_variable_defined?(:@a)
175
+ @b = b unless instance_variable_defined?(:@b)
169
176
  end
170
177
  end
171
178
 
@@ -178,6 +185,27 @@ test.b
178
185
  # 3
179
186
  ```
180
187
 
188
+ ## Super
189
+ ```ruby
190
+ class TestParent
191
+ init :a # also works with a plain initialize()
192
+ end
193
+
194
+ class TestChild
195
+ init [:a, super: true], :b
196
+ end
197
+
198
+ # would be the same as
199
+ class TestChild
200
+ attr_accessor :a, :b
201
+ def initialize(a, b)
202
+ super(a)
203
+ @a = a unless instance_variable_defined?(:@a)
204
+ @b = b unless instance_variable_defined?(:@b)
205
+ end
206
+ end
207
+ ```
208
+
181
209
  ## Mixed together
182
210
  ```ruby
183
211
  class TestMixed
@@ -191,8 +219,9 @@ class TestMixed
191
219
  attr_reader :a
192
220
  attr_accessor :b
193
221
  def initialize(a:, b:)
194
- @a = a
195
- @b = b
222
+ super()
223
+ @a = a unless instance_variable_defined?(:@a)
224
+ @b = b unless instance_variable_defined?(:@b)
196
225
  end
197
226
  end
198
227
 
@@ -5,107 +5,188 @@ module Trinkets
5
5
  module Init
6
6
  ATTR = %i[accessor reader writer none].freeze
7
7
 
8
- def init(*attrs, attr: ATTR.first, kw: false)
9
- attrs = Init.send(:sanitize_attrs, attrs, attr: attr, kw: kw)
10
-
11
- # @type [Hash[Symbol, Method]]
12
- attr_methods = (ATTR - [:none])
13
- .each_with_object({}) do |name, h|
14
- h[name] = method("attr_#{name}")
15
- end
16
-
17
- # even though options like `kw` aren't used, they serve here to validate the `attrs` options
18
- attr_init = ->(name, attr: ATTR.first, kw: false) do
19
- unless ATTR.include?(attr)
20
- raise ArgumentError, "wrong `attr` type for `#{name.inspect}` (given #{attr.inspect}, expected :accessor (default), :reader, :writer or :none)"
21
- end
22
- attr_methods[attr].call(name) unless attr == :none
8
+ class Parameter
9
+ attr_reader :name, :attr, :kw, :super
10
+ def initialize(name:, attr:, kw:, super:)
11
+ @name = name
12
+ @attr = attr
13
+ @kw = kw
14
+ @super = [super:].first[:super]
23
15
  end
16
+ end
24
17
 
25
- attrs.each { |name, opts| attr_init.call(name, **opts) }
26
-
27
- # hash with 3 keys: {
28
- # FalseClass => [] # positional args
29
- # TrueClass => [] # mandatory kw args
30
- # Hash => [] # optional kw args with default value
31
- # }
32
- grouped_params = attrs
33
- .map { |name, opts| [name, opts[:kw] || false] }
34
- .group_by { _1.last.class }
18
+ class BoundParameter < Parameter
35
19
 
36
- pos_params = [*grouped_params[FalseClass]].map(&:first)
37
- kw_params = [*grouped_params[TrueClass]].map(&:first)
38
- opt_kw_params = [*grouped_params[Hash]].to_h
39
- .transform_values! { _1[:default] }
20
+ attr_reader :value
40
21
 
41
- init_method = Init.send(:define_initialize, pos_params, kw_params, opt_kw_params)
42
- define_method :initialize, init_method
22
+ def initialize(name:, attr:, kw:, super:, value:)
23
+ super(name:, attr:, kw:, super:)
24
+ @value = value
25
+ end
43
26
  end
44
27
 
45
- class << self
46
- private def sanitize_attrs(attrs, **default_options)
28
+ # @!attribute r req
29
+ # @return [Array[Parameter]]
30
+ # @!attribute r key_req
31
+ # @return [Array[Parameter]]
32
+ # @!attribute r key_opt
33
+ # @return [Array[Parameter]]
34
+ class Parameters < Struct.new(:req, :key_req, :key_opt, keyword_init: true)
47
35
 
48
- raise ArgumentError, 'At least 1 attribute is required.' if attrs.empty?
36
+ #@return [Parameters]
37
+ def self.build(params, **default_options)
38
+
39
+ raise ArgumentError, 'At least 1 attribute is required.' if params.empty?
49
40
 
50
41
  unless ::Trinkets::Class::Init::ATTR.include?(default_options[:attr])
51
42
  attr = default_options[:attr].inspect
52
43
  raise ArgumentError, "wrong `attr` type (given #{attr}, expected :accessor (default), :reader, :writer or :none)"
53
44
  end
54
45
 
55
- # Normalize attrs into an array: [[:name, **options], ...]
56
- # @type [Array[Array[Symbol, Hash]]]
57
- attrs = attrs.map do |a|
58
- name, opts = [*a]
46
+ # @type [Array[Parameter]]
47
+ params = params.map do |name, opts|
59
48
  name = name.to_s.sub(/^@/, '').to_sym
60
- opts = default_options.merge(opts || {})
61
- [name, opts]
49
+
50
+ opts ||= {}
51
+ opts.reject! { |_, v| v.nil? }
52
+ opts = default_options.merge(opts)
53
+
54
+ Parameter.new(name:, **opts)
62
55
  end
63
56
 
64
- repeated_attrs = attrs.map(&:first)
57
+ repeated_params = params.map(&:name)
65
58
  .tally
66
59
  .select { |_, count| count > 1 }
67
60
  .keys
68
61
 
69
- raise ArgumentError, "duplicated argument names: #{repeated_attrs.join(', ')}" if repeated_attrs.any?
62
+ raise ArgumentError, "duplicated argument names: #{repeated_params.join(', ')}" if repeated_params.any?
63
+
64
+ # hash with 3 keys: {
65
+ # FalseClass => { :name => Parameter } # positional args
66
+ # TrueClass => { :name => Parameter } # mandatory kw args
67
+ # Hash => { :name => Parameter } # optional kw args with default value
68
+ # }
69
+ #@type [Hash[Class, Array[Parameter]]]
70
+ params = params.group_by { |param| param.kw.class }
71
+
72
+ Parameters.new(
73
+ req: [*params[FalseClass]],
74
+ key_req: [*params[TrueClass]],
75
+ key_opt: [*params[Hash]]
76
+ )
70
77
 
71
- attrs.to_h
72
78
  end
73
79
 
74
- # @param [Array[Symbol]] pos_params
75
- # @param [Array[Symbol]] kw_params
76
- # @param [Hash[Symbol, Object]] opt_kw_params
77
- private def define_initialize(pos_params, kw_params, opt_kw_params)
78
- ->(*values, **kw_values) do
80
+ #@return [Parameters]
81
+ def bind(values, kw_values)
79
82
 
80
- unless pos_params.size == values.size
81
- raise ArgumentError, "wrong number of arguments (given #{values.size}, expected #{pos_params.size})"
82
- end
83
+ validate values, kw_values
83
84
 
84
- missing_keys = kw_params - kw_values.keys
85
- unless missing_keys.empty?
86
- missing_keys = missing_keys.map(&:inspect).join(', ')
87
- raise ArgumentError, "missing keywords: #{missing_keys}"
88
- end
85
+ req = self.req.zip(values).map do |(param, value)|
86
+ BoundParameter.new(
87
+ name: param.name,
88
+ attr: param.attr,
89
+ kw: param.kw,
90
+ super: param.super,
91
+ value:
92
+ )
93
+ end
89
94
 
90
- unknown_keywords = kw_values.except(*kw_params, *opt_kw_params.keys)
91
- unless unknown_keywords.empty?
92
- unknown_keywords = unknown_keywords.keys.map(&:to_sym).map(&:inspect).join(', ')
93
- raise ArgumentError, "unknown keywords: #{unknown_keywords}"
94
- end
95
+ key_req = self.key_req.map do |param|
96
+ BoundParameter.new(
97
+ name: param.name,
98
+ attr: param.attr,
99
+ kw: param.kw,
100
+ super: param.super,
101
+ value: kw_values[param.name]
102
+ )
103
+ end
95
104
 
96
- pos_params.zip(values).each do |name, value|
97
- instance_variable_set "@#{name}", value
98
- end
105
+ key_opt = self.key_opt.map do |param|
106
+ key, value = kw_values.assoc(param.name)
107
+ BoundParameter.new(
108
+ name: param.name,
109
+ attr: param.attr,
110
+ kw: param.kw,
111
+ super: param.super,
112
+ value: key ? value : param.kw[:default]
113
+ )
114
+ end
99
115
 
100
- kw_params.each do |name|
101
- instance_variable_set "@#{name}", kw_values[name]
102
- end
116
+ Parameters.new(req:, key_req:, key_opt:)
103
117
 
104
- opt_kw_params.each do |name, default_value|
105
- value = kw_values.include?(name) ? kw_values[name] : default_value
106
- instance_variable_set "@#{name}", value
107
- end
118
+ end
108
119
 
120
+ def all_params = req + all_key_params
121
+
122
+ def all_key_params = key_req + key_opt
123
+
124
+ private def validate(values, kw_values)
125
+
126
+ unless req.size == values.size
127
+ raise ArgumentError, "wrong number of arguments (given #{values.size}, expected #{req.size})"
128
+ end
129
+
130
+ missing_keys = key_req.map(&:name) - kw_values.keys
131
+ unless missing_keys.empty?
132
+ missing_keys = missing_keys.map(&:inspect).join(', ')
133
+ raise ArgumentError, "missing keywords: #{missing_keys}"
134
+ end
135
+
136
+ unknown_keywords = kw_values.except(*all_key_params.map(&:name))
137
+ unless unknown_keywords.empty?
138
+ unknown_keywords = unknown_keywords.keys.map(&:to_sym).map(&:inspect).join(', ')
139
+ raise ArgumentError, "unknown keywords: #{unknown_keywords}"
140
+ end
141
+
142
+ end
143
+ end
144
+
145
+ def init(*params, attr: ATTR.first, kw: false, super: false)
146
+
147
+ params = Parameters.build(params, attr:, kw:, super:)
148
+
149
+ # @type [Hash[Symbol, Method]]
150
+ attr_methods = (ATTR - [:none])
151
+ .each_with_object({}) do |name, h|
152
+ h[name] = method("attr_#{name}")
153
+ end
154
+
155
+ attr_init = ->(name, attr: ATTR.first) do
156
+ unless ATTR.include?(attr)
157
+ raise ArgumentError, "wrong `attr` type for `#{name.inspect}` (given #{attr.inspect}, expected :accessor (default), :reader, :writer or :none)"
158
+ end
159
+ attr_methods[attr].call(name) unless attr == :none
160
+ end
161
+
162
+ params.all_params.each do |param|
163
+ attr_init.call param.name, attr: param.attr
164
+ end
165
+
166
+ init_method = Init.send(:define_initialize, params)
167
+ define_method :initialize, init_method
168
+ end
169
+
170
+ class << self
171
+
172
+ # @param [Parameters] params
173
+ private def define_initialize(params)
174
+ ->(*values, **kw_values) do
175
+
176
+ params = params.bind(values, kw_values)
177
+
178
+ super_req = params.req.select(&:super).map(&:value)
179
+ super_kws = params.all_key_params.select(&:super)
180
+ .each_with_object({}) do |param, h|
181
+ h[param.name] = param.value
182
+ end
183
+
184
+ super *super_req, **super_kws
185
+
186
+ params.all_params.each do |param|
187
+ name = "@#{param.name}"
188
+ instance_variable_set(name, param.value) unless instance_variable_defined?(name)
189
+ end
109
190
  end
110
191
  end
111
192
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trinkets
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trinkets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SilverPhoenix99