otoroshi 0.0.7 → 0.0.8

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: cded2f7b556f68f150dbccb50c566a6b131dac220eedd220b039ee96f17fb083
4
- data.tar.gz: 2ab1413e813f5cf917bed90e0bd8f7c6928827af24acdf6618e1f29e66c82dc9
3
+ metadata.gz: fba1623080c055e87c2055001a7f2370e459b5fe1a28774375cef3dd9e7da802
4
+ data.tar.gz: 4c01c52a123157e572b4fdcef7c2e7eb6f0cc6ef2162e49237b5a49785708b70
5
5
  SHA512:
6
- metadata.gz: 11a8543961a2de58d5004968557c68db65f85251403a552a5f7a50c5b946234f215d812859f72a7fa044a03f4116d61b14b01116f9986676a0d0945a9f8f719f
7
- data.tar.gz: e27749e5ce0065a0ae3ee551538a1bbf7f191bb33a29f34cdcad1f173b463d429e846b1973d2b096b5004bce450a625ffbe8d407a301535eca51d8ff6f618dab
6
+ metadata.gz: 8b89127bff4d54b14be27fe723db61e6f0786b7f0f9f6d4df612ed49b3d86d59f1a1df55038178662e5ce247c4b2f6af16b35e05650730d8b9889debd354ed48
7
+ data.tar.gz: 177fbc07e846a1cb9cbc7b3e595ce5c0878ff153d6293d9e0ccf986745a0b898e3b2a9be144218b97425ad87b78b4170848b20496ca39bf232ee898f5b1fcb19
@@ -9,11 +9,11 @@ module Otoroshi
9
9
  #
10
10
  # @param property [Symbol] name of the property
11
11
  # @param type [Class] class to match
12
- # @param array [true, false] define if it is an array
13
- def initialize(property, type, array: false)
12
+ # @param collection [true, false] define if it is a collection
13
+ def initialize(property, type, collection: false)
14
14
  expected_type = type.is_a?(Array) ? type.first || Object : type
15
15
  msg =
16
- if array
16
+ if collection
17
17
  ":#{property} contains elements that are not instances of #{expected_type}"
18
18
  else
19
19
  ":#{property} is not an instance of #{expected_type}"
@@ -22,7 +22,7 @@ module Otoroshi
22
22
  end
23
23
  end
24
24
 
25
- # Manages errors raised when value should be an array
25
+ # Manages errors raised when value should be an collection
26
26
  class NotAnArray < Error
27
27
  # Initialize an error
28
28
  #
@@ -39,13 +39,13 @@ module Otoroshi
39
39
  #
40
40
  # @param property [Symbol] name of the property
41
41
  # @param accepted_values [Array] accepted values
42
- # @param array [true, false] define if it is an array
43
- def initialize(property, accepted_values, array: false)
42
+ # @param collection [true, false] define if it is an collection
43
+ def initialize(property, accepted_values, collection: false)
44
44
  # reintegrate the colon for symbols which is lost during interpolation
45
45
  to_s = ->(v) { v.is_a?(Symbol) ? ":#{v}" : v }
46
46
  accepted_values_list = accepted_values.map { |v| to_s.call(v) }.join(', ')
47
47
  msg =
48
- if array
48
+ if collection
49
49
  ":#{property} contains elements that are not included in [#{accepted_values_list}]"
50
50
  else
51
51
  ":#{property} is not included in [#{accepted_values_list}]"
@@ -59,10 +59,10 @@ module Otoroshi
59
59
  # Initialize an error
60
60
  #
61
61
  # @param property [Symbol] name of the property
62
- # @param array [true, false] define if it is an array
63
- def initialize(property, array: false)
62
+ # @param collection [true, false] define if it is an collection
63
+ def initialize(property, collection: false)
64
64
  msg =
65
- if array
65
+ if collection
66
66
  ":#{property} contains elements that do not respect the assertion"
67
67
  else
68
68
  ":#{property} does not respect the assertion"
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Otoroshi
4
+ # Drawing of #initialize method
5
+ #
6
+ class Initializer
7
+ class << self
8
+ # Draw a stringified initialize method
9
+ #
10
+ # @param properties [Hash] a description of the class properties
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @example
15
+ # <<-RUBY
16
+ # def initialize(foo:, bar: 0)
17
+ # self.foo = foo
18
+ # self.bar = bar
19
+ # end
20
+ # RUBY
21
+ def draw(properties)
22
+ new(properties).draw
23
+ end
24
+ end
25
+
26
+ # Initialize an instance
27
+ #
28
+ # @param properties [Hash] a description of the class properties
29
+ def initialize(properties = {})
30
+ @properties = properties
31
+ end
32
+
33
+ # Draws a stringified initialize method
34
+ #
35
+ # @return [String]
36
+ #
37
+ # @example
38
+ # <<-RUBY
39
+ # def initialize(foo:, bar: 0)
40
+ # self.foo = foo
41
+ # self.bar = bar
42
+ # end
43
+ # RUBY
44
+ def draw
45
+ <<-RUBY
46
+ def initialize(#{initialize_parameters})
47
+ #{initialize_assignments}
48
+ #{initialize_push_singletons}
49
+ end
50
+ RUBY
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :properties
56
+
57
+ # Generates initialize method parameters
58
+ #
59
+ # @return [String]
60
+ #
61
+ # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
62
+ # redefine_initialize #=> "foo:, bar: 0"
63
+ def initialize_parameters
64
+ parameters =
65
+ properties.map do |key, options|
66
+ "#{key}:#{default_parameter_for(options)}"
67
+ end
68
+ parameters.join(', ')
69
+ end
70
+
71
+ # Fixes the default value of a parameter depending on options
72
+ #
73
+ # @return [String]
74
+ #
75
+ # @example when nil is allowed and default is set
76
+ # default_parameter_for #=> " 0"
77
+ # @example when nil is allowed and default is not set
78
+ # default_parameter_for #=> " nil"
79
+ # @example when nil is not allowed
80
+ # default_parameter_for #=> ""
81
+ def default_parameter_for(options)
82
+ default, allow_nil = options.values_at(:default, :allow_nil)
83
+ if default
84
+ symbol_prefix = default.is_a?(Symbol) ? ':' : ''
85
+ " #{symbol_prefix}#{default}"
86
+ else
87
+ allow_nil ? ' nil' : ''
88
+ end
89
+ end
90
+
91
+ # Generates initialize method assignments
92
+ #
93
+ # @return [String]
94
+ #
95
+ # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
96
+ # initialize_body #=> "self.foo = foo\nself.bar = bar"
97
+ def initialize_assignments
98
+ assignments =
99
+ properties.keys.map do |name|
100
+ "self.#{name} = #{name}"
101
+ end
102
+ assignments.join("\n")
103
+ end
104
+
105
+ # Generates push singleton for each array property
106
+ #
107
+ # @return [String]
108
+ def initialize_push_singletons
109
+ collections =
110
+ properties.select do |_, options|
111
+ options[:type].is_a?(Array) || options[:type] == Array
112
+ end
113
+ singletons =
114
+ collections.keys.map do |name|
115
+ initialize_push_singleton(name)
116
+ end
117
+ singletons.join("\n")
118
+ end
119
+
120
+ # Generates singleton on collection instance variable to overide <<
121
+ # so value is validated before being added to the collection
122
+ #
123
+ # @return [String]
124
+ def initialize_push_singleton(name)
125
+ <<-RUBY
126
+ bind = self
127
+ @#{name}.singleton_class.send(:define_method, :<<) do |v|
128
+ bind.send(:"validate_#{name}_type!", [v])
129
+ bind.send(:"validate_#{name}_inclusion!", [v])
130
+ bind.send(:"validate_#{name}_assertion!", [v])
131
+
132
+ push(v)
133
+ end
134
+ RUBY
135
+ end
136
+ end
137
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'awesome_print'
3
+ require_relative 'initializer'
4
4
 
5
5
  module Otoroshi
6
6
  # This module is designed to be included in a class. This will provide
@@ -16,7 +16,7 @@ module Otoroshi
16
16
  # property :positive_number, Integer, verify: ->(v) { v >= 0 }
17
17
  # property :number_or_nil, Integer, allow_nil: true
18
18
  # property :number_with_default, Integer, default: 42
19
- # property :number_from_collection, Integer, one_of: [1, 2, 3, 5, 8, 13, 21, 34]
19
+ # property :number_in_collection, Integer, one_of: [1, 2, 3, 5, 8, 13, 21, 34]
20
20
  # end
21
21
  module Sanctuary
22
22
  class << self
@@ -27,7 +27,7 @@ module Otoroshi
27
27
  end
28
28
 
29
29
  # Class methods extended for the base class
30
- module ClassMethods # rubocop:disable Metrics/ModuleLength
30
+ module ClassMethods
31
31
  # Adds a new "property" to the class
32
32
  #
33
33
  # @param name [Symbol] the property name
@@ -43,30 +43,15 @@ module Otoroshi
43
43
  # property name, type: String, assert: ->(v) { v.length > 3 }, allow_nil: true
44
44
  # @example
45
45
  # property scores, type: [Integer], assert: ->(v) { v >= 0 }, default: []
46
- def property( # rubocop:disable Metrics/ParameterLists
47
- name,
48
- type = Object,
49
- one_of: nil,
50
- assert: ->(_) { true },
51
- allow_nil: false,
52
- default: nil
53
- )
54
- add_to_properties(name, type, one_of, assert, allow_nil, default)
55
- define_validate_type!(name, type, allow_nil)
56
- define_validate_inclusion!(name, type, one_of, allow_nil)
57
- define_validate_assertion!(name, type, assert, allow_nil)
46
+ def property(name, type = Object, one_of: nil, assert: ->(_) { true }, allow_nil: false, default: nil)
47
+ add_to_properties(name, type, allow_nil, default)
48
+ collection = type == Array || type.is_a?(Array)
49
+ define_validate_type!(name, type, collection, allow_nil)
50
+ define_validate_inclusion!(name, collection, one_of, allow_nil)
51
+ define_validate_assertion!(name, collection, assert, allow_nil)
58
52
  define_getter(name)
59
53
  define_setter(name)
60
- redefine_initialize
61
- end
62
-
63
- # Checks the type is an array
64
- #
65
- # @param type [Class, Array<Class>] the tested type
66
- #
67
- # @return [true, false]
68
- def collection?(type)
69
- type == Array || type.is_a?(Array)
54
+ class_eval Initializer.draw(properties), __FILE__, __LINE__ + 1
70
55
  end
71
56
 
72
57
  # Returns the class properties
@@ -83,12 +68,10 @@ module Otoroshi
83
68
  # Updates {properties} to add new property to the returned ones
84
69
  #
85
70
  # @return [void]
86
- def add_to_properties(name, type, one_of, assert, allow_nil, default) # rubocop:disable Metrics/ParameterLists
71
+ def add_to_properties(name, type, allow_nil, default)
87
72
  current_state = properties
88
73
  current_state[name] = {
89
74
  type: type,
90
- one_of: one_of,
91
- assert: assert,
92
75
  allow_nil: allow_nil,
93
76
  default: default
94
77
  }
@@ -97,48 +80,42 @@ module Otoroshi
97
80
 
98
81
  # Defines a private method that raises an error if type is not respected
99
82
  #
100
- # @param name [Symbol] the property name
101
- # @param type [Class] the type to test against
102
- # @param array [true, false] define if the value is an array
103
- # @param allow_nil [true, false] allow nil as a value
104
- #
105
83
  # @return [void]
106
84
  #
107
85
  # @example
108
- # define_validate_type!(score, Integer, false) => def validate_score_type!(value) ...
86
+ # define_validate_type!(:score, Integer, false) => def validate_score_type!(value) ...
109
87
  # @example Generated method
88
+ # # given name: :score, type: Integer, allow_nil: false
110
89
  # def validate_score_type!(value)
111
90
  # return if Integer.nil? || false && value.nil?
112
91
  # return if value.is_a? Integer
113
92
  #
114
93
  # raise ArgumentError, ":score does not match required type"
115
94
  # end
116
- def define_validate_type!(name, type, allow_nil)
95
+ def define_validate_type!(name, type, collection, allow_nil)
117
96
  lambda = type_validation(type)
118
- is_array = collection?(type)
119
- check_array = ->(v) { v.is_a?(Array) || raise(Otoroshi::NotAnArray, name) }
97
+ check_collection = ->(v) { v.is_a?(Array) || raise(Otoroshi::NotAnArray, name) }
120
98
  define_method :"validate_#{name}_type!" do |value|
121
99
  return if allow_nil && value.nil?
122
100
 
123
- is_array && check_array.call(value)
101
+ collection && check_collection.call(value)
124
102
  return if lambda.call(value)
125
103
 
126
- raise Otoroshi::WrongTypeError.new(name, type, array: is_array)
104
+ raise Otoroshi::WrongTypeError.new(name, type, collection: collection)
127
105
  end
128
106
  private :"validate_#{name}_type!"
129
107
  end
130
108
 
131
109
  # Defines a lambda to be called to validate that value matches the type
132
110
  #
133
- # @param type [Class] the type to test against
134
- # @param array [true, false] define if the value is an array
135
- #
136
111
  # @return [Proc] the lambda to use in order to test that the value matches the type
137
112
  #
138
113
  # @example
139
114
  # type_validation(Integer) #=> ->(v) { v.is_a? Integer }
140
115
  # @example
141
116
  # type_validation([String, Symbol]) #=> ->(v) { [String, Symbol].any? { |t| v.is_a? t } }
117
+ #
118
+ # @note params are used for binding in define_method scope
142
119
  def type_validation(type)
143
120
  if type == Array
144
121
  # no expected type for each element, return nil
@@ -158,25 +135,22 @@ module Otoroshi
158
135
 
159
136
  # Defines a private method that raises an error if value is not included in the accepted ones
160
137
  #
161
- # @param name [Symbol] the property name
162
- # @param array [true, false] define if the value is an array
163
- # @param values [Array, nil] the values to test against
164
- # @param allow_nil [true, false] allow nil as a value
165
- #
166
138
  # @return [void]
167
139
  #
168
140
  # @example
169
- # define_validate_inclusion!(side, [:left, :right], false) => def validate_side_type!(value) ...
141
+ # # given @name = :side
142
+ # define_validate_inclusion!(:side, ...) => def validate_side_type!(value) ...
170
143
  # @example Generated method
144
+ # # given name: :side, collection: false, one_of: [:left, :right], allow_nil: false
171
145
  # def validate_side_type!(value)
172
146
  # return if false && value.nil?
173
147
  # return if [:left, :right].include? value
174
148
  #
175
149
  # raise ArgumentError, ":side is not included in accepted values"
176
150
  # end
177
- def define_validate_inclusion!(name, type, values, allow_nil)
178
- validator = collection?(type) ? each_inside?(name, values) : inside?(name, values)
179
- if values
151
+ def define_validate_inclusion!(name, collection, one_of, allow_nil)
152
+ validator = collection ? each_inside?(name, one_of) : inside?(name, one_of)
153
+ if one_of
180
154
  define_method(:"validate_#{name}_inclusion!") do |value|
181
155
  allow_nil && value.nil? || validator.call(value)
182
156
  end
@@ -188,47 +162,38 @@ module Otoroshi
188
162
 
189
163
  # Defines a lambda to be called to validate that value is included in accepted ones
190
164
  #
191
- # @param name [Symbol] the property name
192
- # @param values [Array, nil] the values to test against
193
- #
194
165
  # @return [Proc] the lambda to use in order to test that value is included in accepted ones
195
- def inside?(name, values)
166
+ def inside?(name, one_of)
196
167
  lambda do |v|
197
- values.include?(v) || raise(Otoroshi::NotAcceptedError.new(name, values))
168
+ one_of.include?(v) || raise(Otoroshi::NotAcceptedError.new(name, one_of))
198
169
  end
199
170
  end
200
171
 
201
172
  # Defines a lambda to be called to validate that each value is included in accepted ones
202
173
  #
203
- # @param name [Symbol] the property name
204
- # @param values [Array, nil] the values to test against
205
- #
206
174
  # @return [Proc] the lambda to use in order to test that each value is included in accepted ones
207
- def each_inside?(name, values)
175
+ def each_inside?(name, one_of)
208
176
  lambda do |v|
209
- v.all? { |e| values.include? e } || raise(Otoroshi::NotAcceptedError.new(name, values, array: true))
177
+ v.all? { |e| one_of.include? e } || raise(Otoroshi::NotAcceptedError.new(name, one_of, collection: true))
210
178
  end
211
179
  end
212
180
 
213
181
  # Defines a private method that raises an error if assert lambda returns false
214
182
  #
215
- # @param name [Symbol] the property name
216
- # @param assert [Proc] a lambda processing the value and returning true or false
217
- # @param allow_nil [true, false] allow nil as a value
218
- #
219
183
  # @return [void]
220
184
  #
221
185
  # @example
222
- # define_validate_assertion!("score", ->(v) { v >= 0 }, false) #=> def validate_score_assertion!(value) ...
186
+ # define_validate_assertion!(:score, ...) #=> def validate_score_assertion!(value) ...
223
187
  # @example Generated instance method
188
+ # # given name: :score, assert: >(v) { v >= 0 }, allow_nil: false
224
189
  # def validate_score_assertion!(value)
225
190
  # return if false && value.nil?
226
191
  # return if value >= 0
227
192
  #
228
193
  # raise ArgumentError, ":score does not match validation"
229
194
  # end
230
- def define_validate_assertion!(name, type, assert, allow_nil)
231
- validator = collection?(type) ? each_assert?(name, assert) : assert?(name, assert)
195
+ def define_validate_assertion!(name, collection, assert, allow_nil)
196
+ validator = collection ? each_assert?(name, assert) : assert?(name, assert)
232
197
  define_method :"validate_#{name}_assertion!" do |value|
233
198
  allow_nil && value.nil? || validator.call(value)
234
199
  end
@@ -237,57 +202,46 @@ module Otoroshi
237
202
 
238
203
  # Defines a lambda to be called to validate that value respects the specific
239
204
  #
240
- # @param name [Symbol] the property name
241
- # @param assert [Proc] a lambda processing the value and returning true or false
242
- #
243
205
  # @return [Proc] the lambda to use in order to test that value respects the specific
244
206
  def assert?(name, assert)
245
207
  lambda do |value|
246
- return if instance_exec(value, &assert)
247
-
248
- raise Otoroshi::AssertionError, name
208
+ instance_exec(value, &assert) || raise(Otoroshi::AssertionError, name)
249
209
  end
250
210
  end
251
211
 
252
212
  # Defines a lambda to be called to validate that value respects the specific
253
213
  #
254
- # @param name [Symbol] the property name
255
- # @param validate [Proc] a lambda processing the value and returning true or false
256
- #
257
214
  # @return [Proc] the lambda to use in order to test that each value respects the specific
258
- def each_assert?(name, validate)
215
+ def each_assert?(name, assert)
259
216
  lambda do |value|
260
- return if value.all? { |e| instance_exec(e, &validate) }
261
-
262
- raise Otoroshi::AssertionError.new(name, array: true)
217
+ value.all? { |e| instance_exec(e, &assert) } ||
218
+ raise(Otoroshi::AssertionError.new(name, collection: true))
263
219
  end
264
220
  end
265
221
 
266
222
  # Defines a getter method for the property
267
223
  #
268
- # @param name [Symbol] the property name
269
- #
270
224
  # @return [void]
271
225
  #
272
226
  # @example
273
227
  # define_getter(:score) #=> def score ...
274
228
  # @example Generated instance method
275
- # def score
276
- # instance_variable_get(@score)
277
- # end
229
+ # # given name: :score
230
+ # def score
231
+ # instance_variable_get(@score)
232
+ # end
278
233
  def define_getter(name)
279
234
  define_method(name) { instance_variable_get("@#{name}") }
280
235
  end
281
236
 
282
237
  # Defines a setter method for the property
283
238
  #
284
- # @param name [Symbol] the property name
285
- #
286
239
  # @return [void]
287
240
  #
288
241
  # @example
289
- # define_getter(:score) #=> def score=(value) ...
242
+ # define_setter(:score) #=> def score=(value) ...
290
243
  # @example Generated instance method
244
+ # # given name: :score
291
245
  # def score=(value)
292
246
  # validate_score_type!(value)
293
247
  # validate_score!(value)
@@ -301,99 +255,6 @@ module Otoroshi
301
255
  instance_variable_set("@#{name}", value)
302
256
  end
303
257
  end
304
-
305
- # Redefines initialize method
306
- #
307
- # @return [void]
308
- #
309
- # @note method is defined with `class_eval`
310
- #
311
- # @example Generated method
312
- # def initialize(foo:, bar: 0)
313
- # self.foo = foo
314
- # self.bar = bar
315
- # end
316
- def redefine_initialize
317
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
318
- def initialize(#{initialize_parameters})
319
- #{initialize_assignments}
320
- #{initialize_push_singletons}
321
- end
322
- RUBY
323
- end
324
-
325
- # Generates initialize method parameters
326
- #
327
- # @return [String]
328
- #
329
- # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
330
- # redefine_initialize #=> "foo:, bar: 0"
331
- def initialize_parameters
332
- parameters =
333
- properties.map do |key, options|
334
- allow_nil, default = options.values
335
- "#{key}:#{default_parameter_for(allow_nil, default)}"
336
- end
337
- parameters.join(', ')
338
- end
339
-
340
- # Fixes the default value of a parameter depending on options
341
- #
342
- # @param options [Hash]
343
- #
344
- # @return [String]
345
- #
346
- # @example when nil is allowed and default is set
347
- # default_parameter_for(true, 0) #=> " 0"
348
- # @example when nil is allowed and default is not set
349
- # default_parameter_for(true, nil) #=> " nil"
350
- # @example when nil is not allowed
351
- # default_parameter_for(false, nil) #=> ""
352
- def default_parameter_for(allow_nil, default)
353
- if default
354
- symbol_prefix = default.is_a?(Symbol) ? ':' : ''
355
- " #{symbol_prefix}#{default}"
356
- else
357
- allow_nil ? ' nil' : ''
358
- end
359
- end
360
-
361
- # Generates initialize method assignments
362
- #
363
- # @return [String]
364
- #
365
- # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
366
- # initialize_body #=> "self.foo = foo\nself.bar = bar"
367
- def initialize_assignments
368
- properties.keys.map { |name| "self.#{name} = #{name}" }.join("\n")
369
- end
370
-
371
- # Generates push singleton for each array property
372
- #
373
- # @return [String]
374
- def initialize_push_singletons
375
- collections = properties.select { |_, options| collection?(options[:type]) }
376
- singletons =
377
- collections.keys.map { |name| initialize_push_singleton(name) }
378
- singletons.join("\n")
379
- end
380
-
381
- # Generates singleton on array instance variable to overide <<
382
- # so value is validated before being added to the array
383
- #
384
- # @return [String]
385
- def initialize_push_singleton(name)
386
- <<-RUBY
387
- bind = self
388
- @#{name}.singleton_class.send(:define_method, :<<) do |v|
389
- bind.send(:"validate_#{name}_type!", [v])
390
- bind.send(:"validate_#{name}_inclusion!", [v])
391
- bind.send(:"validate_#{name}_assertion!", [v])
392
-
393
- push(v)
394
- end
395
- RUBY
396
- end
397
258
  end
398
259
  end
399
260
  end