trinkets 0.3.1 → 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: 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