trinkets 0.3.1 → 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: cb82b9c6f72cce48f7d303e989870bc05e77429508c17f266428d15bc0821628
4
- data.tar.gz: 1dc0a8334a869e8be5b4e397caeed6be9ff16b495514425e184938d538b0b2e1
3
+ metadata.gz: 2e815777ee25a04feffd792fc975e68256e346eb3ff77126bd3610b949deadb2
4
+ data.tar.gz: 77a0c78f5dc7699e2d90e7db69333b246023145c66e408901ae328d29d377385
5
5
  SHA512:
6
- metadata.gz: 4b57aecbcc07de7c710804302accad6fe49d610c133fabf74040a0cca376129258dc064eb967c0ff50570d72b46d641160fda6485222d29f47f06117b57db55c
7
- data.tar.gz: 7714dbbd7e295d87d9f6c98aac8d6aade0d6a57d86e69368c039e8022824511f897f1a2d8f90f5f70b1fb598741b4bfbfdbb5308a164f40dc2fcb6da01491148
6
+ metadata.gz: e57c30e56c22137542e58d2128d9a8ddb31e78acca1756c3337fa54a906046790997fecb8737b2c442528920756cfc8ccab465ac47489bb44257c4cecb9c9d03
7
+ data.tar.gz: 49ed92fdb96d888727ebc972cd71520f08fb603cad2345d9832cf9f8704d99e979cf02cd10c268dea5c12e8681f4bccdd49b8c9eca7a68a2025db002b349c74b
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
1
 
2
+ ### [0.5.0](https://github.com/SilverPhoenix99/trinkets/tree/v0.5.0)
3
+ * `Class::init`: Added `:super` option.
4
+
5
+ ### [0.4.0](https://github.com/SilverPhoenix99/trinkets/tree/v0.4.0)
6
+ * `Class::init`: Allow default values for keyword arguments.
7
+
2
8
  ### [0.3.1](https://github.com/SilverPhoenix99/trinkets/tree/v0.3.1)
3
9
  * Fixed repeated arguments in `Class::init`.
4
10
 
data/doc/class/init.md CHANGED
@@ -9,7 +9,14 @@ To use it, define a class and call `::init` like you would call `::attr` methods
9
9
  * can be `:accessor`, `:reader`, `:writer` or `:none`
10
10
  * defaults to `:accessor`
11
11
  * `kw` : if arguments are to be set as keyword arguments
12
+ * when `false`, it's a mandatory positional argument
13
+ * when `true`, it becomes a mandatory keyword argument, like `(a:)`
14
+ * when it's a hash, like `{ default: <VALUE> }`, it's an optional keyword argument
15
+ * an empty hash `{}` is equivalent to `{ default: nil }`
12
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`
13
20
 
14
21
  The same options can be used per individual argument.
15
22
 
@@ -40,8 +47,9 @@ end
40
47
  class Test
41
48
  attr_accessor :a, :b
42
49
  def initialize(a, b)
43
- @a = a
44
- @b = b
50
+ super()
51
+ @a = a unless instance_variable_defined?(:@a)
52
+ @b = b unless instance_variable_defined?(:@b)
45
53
  end
46
54
  end
47
55
 
@@ -67,8 +75,9 @@ end
67
75
  class TestAttr
68
76
  attr_reader :a, :b
69
77
  def initialize(a, b)
70
- @a = a
71
- @b = b
78
+ super()
79
+ @a = a unless instance_variable_defined?(:@a)
80
+ @b = b unless instance_variable_defined?(:@b)
72
81
  end
73
82
  end
74
83
 
@@ -94,8 +103,9 @@ end
94
103
  class TestKW
95
104
  attr_accessor :a, :b
96
105
  def initialize(a:, b:)
97
- @a = a
98
- @b = b
106
+ super()
107
+ @a = a unless instance_variable_defined?(:@a)
108
+ @b = b unless instance_variable_defined?(:@b)
99
109
  end
100
110
  end
101
111
 
@@ -122,10 +132,11 @@ class TestAttrOptions
122
132
  attr_reader :a
123
133
  attr_accessor :b, :c
124
134
  def initialize(b, d, a:, c:)
125
- @a = a
126
- @b = b
127
- @c = c
128
- @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)
129
140
  end
130
141
  end
131
142
 
@@ -147,6 +158,54 @@ test.a = 5
147
158
  # => raises NoMethodError
148
159
  ```
149
160
 
161
+ ## Default values for keyword arguments
162
+ ```ruby
163
+ class TestDefaultKw
164
+ init [:a, kw: true],
165
+ :b,
166
+ kw: {default: 3}
167
+ end
168
+
169
+ # would be the same as
170
+ class TestDefaultKw
171
+ attr_accessor :a, :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)
176
+ end
177
+ end
178
+
179
+ test = TestDefaultKw.new(a: 2)
180
+
181
+ test.a
182
+ # 2
183
+
184
+ test.b
185
+ # 3
186
+ ```
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
+
150
209
  ## Mixed together
151
210
  ```ruby
152
211
  class TestMixed
@@ -160,8 +219,9 @@ class TestMixed
160
219
  attr_reader :a
161
220
  attr_accessor :b
162
221
  def initialize(a:, b:)
163
- @a = a
164
- @b = b
222
+ super()
223
+ @a = a unless instance_variable_defined?(:@a)
224
+ @b = b unless instance_variable_defined?(:@b)
165
225
  end
166
226
  end
167
227
 
@@ -5,93 +5,191 @@ 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
- raise ArgumentError, 'At least 1 attribute is required.' if attrs.empty?
10
- raise ArgumentError, '`attr` must be one of :accessor (default), :reader, :writer or :none' unless ATTR.include?(attr)
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]
15
+ end
16
+ end
11
17
 
12
- default_attr_options = { attr: attr, kw: kw }
18
+ class BoundParameter < Parameter
13
19
 
14
- attrs = ::Trinkets::Class.send(:sanitize_attrs, attrs, default_attr_options)
20
+ attr_reader :value
15
21
 
16
- attr_methods = (ATTR - [:none])
17
- .each_with_object({}) do |name, h|
18
- h[name] = method("attr_#{name}")
22
+ def initialize(name:, attr:, kw:, super:, value:)
23
+ super(name:, attr:, kw:, super:)
24
+ @value = value
25
+ end
26
+ end
27
+
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)
35
+
36
+ #@return [Parameters]
37
+ def self.build(params, **default_options)
38
+
39
+ raise ArgumentError, 'At least 1 attribute is required.' if params.empty?
40
+
41
+ unless ::Trinkets::Class::Init::ATTR.include?(default_options[:attr])
42
+ attr = default_options[:attr].inspect
43
+ raise ArgumentError, "wrong `attr` type (given #{attr}, expected :accessor (default), :reader, :writer or :none)"
19
44
  end
20
45
 
21
- # even though options like `kw` aren't used, they serve here to validate the `attrs` options
22
- attr_init = ->(name, attr: ATTR.first, kw: false) do
23
- unless ATTR.include?(attr)
24
- raise ArgumentError, "attr `#{name}`, option attr` must be one of :accessor (default), :reader, :writer or :none"
46
+ # @type [Array[Parameter]]
47
+ params = params.map do |name, opts|
48
+ name = name.to_s.sub(/^@/, '').to_sym
49
+
50
+ opts ||= {}
51
+ opts.reject! { |_, v| v.nil? }
52
+ opts = default_options.merge(opts)
53
+
54
+ Parameter.new(name:, **opts)
25
55
  end
26
- attr_methods[attr].call(name) unless attr == :none
27
- end
28
56
 
29
- attrs.each { |name, opts| attr_init.call(name, **opts) }
57
+ repeated_params = params.map(&:name)
58
+ .tally
59
+ .select { |_, count| count > 1 }
60
+ .keys
30
61
 
31
- # 2 hashes: { :name => bool }
32
- kw_attrs, attrs = attrs
33
- .map { |name, opts| [name, opts[:kw]] }
34
- .partition { |_, kw_opt| kw_opt }
35
- .map(&:to_h)
62
+ raise ArgumentError, "duplicated argument names: #{repeated_params.join(', ')}" if repeated_params.any?
36
63
 
37
- init_method = ::Trinkets::Class.send(:define_initialize, attrs, kw_attrs)
38
- define_method :initialize, init_method
39
- end
40
- end
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
+ )
41
77
 
42
- class << self
43
- private def sanitize_attrs(attrs, default_attr_options)
44
- # Normalize attrs into an array: [[:name, **options], ...]
45
- # @type [Array[Array[Symbol, Hash]]]
46
- attrs = attrs.map do |a|
47
- name, opts = [*a]
48
- name = name.to_s.sub(/^@/, '').to_sym
49
- opts = default_attr_options.merge(opts || {})
50
- [name, opts]
51
78
  end
52
79
 
53
- repeated_attrs = attrs.map(&:first)
54
- .tally
55
- .select { |_, count| count > 1 }
56
- .keys
80
+ #@return [Parameters]
81
+ def bind(values, kw_values)
57
82
 
58
- raise ArgumentError, "duplicated argument names: #{repeated_attrs.join(', ')}" if repeated_attrs.any?
83
+ validate values, kw_values
59
84
 
60
- attrs.to_h
61
- 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
94
+
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
104
+
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
115
+
116
+ Parameters.new(req:, key_req:, key_opt:)
62
117
 
63
- # @param [Hash[Symbol Boolean]] attrs
64
- # @param [Hash[Symbol Boolean]] kw_attrs
65
- private def define_initialize(attrs, kw_attrs)
66
- ->(*values, **kw_values) do
118
+ end
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)
67
125
 
68
- unless attrs.size == values.size
69
- raise ArgumentError, "wrong number of arguments (given #{values.size}, expected #{attrs.size})"
126
+ unless req.size == values.size
127
+ raise ArgumentError, "wrong number of arguments (given #{values.size}, expected #{req.size})"
70
128
  end
71
129
 
72
- missing_keys = kw_attrs.except(*kw_values.keys)
130
+ missing_keys = key_req.map(&:name) - kw_values.keys
73
131
  unless missing_keys.empty?
74
- missing_keys = missing_keys.keys.map(&:inspect).join(', ')
132
+ missing_keys = missing_keys.map(&:inspect).join(', ')
75
133
  raise ArgumentError, "missing keywords: #{missing_keys}"
76
134
  end
77
135
 
78
- unknown_keywords = kw_values.except(*kw_attrs.keys)
136
+ unknown_keywords = kw_values.except(*all_key_params.map(&:name))
79
137
  unless unknown_keywords.empty?
80
138
  unknown_keywords = unknown_keywords.keys.map(&:to_sym).map(&:inspect).join(', ')
81
139
  raise ArgumentError, "unknown keywords: #{unknown_keywords}"
82
140
  end
83
141
 
84
- attrs.keys.zip(values).each do |name, value|
85
- instance_variable_set "@#{name}", value
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}")
86
153
  end
87
154
 
88
- kw_values.each do |name, value|
89
- instance_variable_set "@#{name}", value
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)"
90
158
  end
159
+ attr_methods[attr].call(name) unless attr == :none
160
+ end
91
161
 
162
+ params.all_params.each do |param|
163
+ attr_init.call param.name, attr: param.attr
92
164
  end
165
+
166
+ init_method = Init.send(:define_initialize, params)
167
+ define_method :initialize, init_method
93
168
  end
94
- end
95
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
190
+ end
191
+ end
192
+ end
193
+ end
96
194
  end
97
195
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trinkets
4
- VERSION = '0.3.1'
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.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SilverPhoenix99
@@ -65,7 +65,6 @@ homepage: https://github.com/SilverPhoenix99/trinkets
65
65
  licenses:
66
66
  - MIT
67
67
  metadata:
68
- homepage_uri: https://github.com/SilverPhoenix99/trinkets
69
68
  source_code_uri: https://github.com/SilverPhoenix99/trinkets
70
69
  changelog_uri: https://github.com/SilverPhoenix99/trinkets/blob/master/CHANGELOG.md
71
70
  bug_tracker_uri: https://github.com/SilverPhoenix99/trinkets/issues