trinkets 0.4.0 → 0.5.0

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: 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