otoroshi 0.0.6 → 0.1.1

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: e31c6e00090297fb9709600d1580a24a4729ee1a9af094015ea7c197fde884d3
4
- data.tar.gz: 033526ab6473e809a19c812104cf05cadba29485d617161c1799f6664777f9fc
3
+ metadata.gz: 8928c55b726e2db1fb3bd19948c23be9c8fb0175650ae1f65cd0489bf7751c38
4
+ data.tar.gz: db9cc8034319672be477f1352508e6258d9ce105d6eedca6501630776e196407
5
5
  SHA512:
6
- metadata.gz: a5e9595b7c246dde6897fc4f314cdf0c9ef00576dd042cc9c4e34c60de249d4d346ebc2f661f1190708a26211cacbdd1a9f457a9e5802e57049b5f5d831dc9d1
7
- data.tar.gz: 42aace4283c39b3d956022e643635d73a9b3a5feb74070f0dd441d8ef165b5d9c76475a7bd274b94e4da8f85aa0ecb08868116842c6af98b1cdf4112a93bbe77
6
+ metadata.gz: 44856f1f0d75272e70a49d07db5d7ce5218849ca9a0a04164c875d9277f18ce2afddc2cb254e8d9079232a23f384ec58c9ea0602ea6e170504ad1bd59e280895
7
+ data.tar.gz: 7978129f51d5ef3b224efa547e0ede889ffd4010ca8f571dde79830190f21b7a69defd70f8f44059ef1daf8647310fd9583944e7bbbcca05753417b1fec9d72a
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Otoroshi entry-point
4
+ module Otoroshi
5
+ require_relative 'otoroshi/sanctuary'
6
+ require_relative 'otoroshi/exceptions'
7
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './initializer'
4
+
5
+ module Otoroshi
6
+ class Error < StandardError; end
7
+
8
+ # Manages errors raised when value is not an instance of the expected class
9
+ class TypeError < Error
10
+ # @param property [Symbol] name of the property
11
+ # @param type [Class] class to match
12
+ # @example
13
+ # ":number is not an instance of Integer"
14
+ def initialize(property, type)
15
+ super ":#{property} is not an instance of #{type}"
16
+ end
17
+ end
18
+
19
+ # Manages errors raised when value is not accepted (not in the "one_of")
20
+ class OneOfError < Error
21
+ # @param property [Symbol] name of the property
22
+ # @param values [Array] accepted values
23
+ # @example
24
+ # ":fruit is not in [:apple, :pear]"
25
+ def initialize(property, values)
26
+ # reintegrate the colon for symbols which is lost during interpolation
27
+ to_s = ->(v) { v.is_a?(Symbol) ? ":#{v}" : v }
28
+ list = values.map { |v| to_s.call(v) }.join(', ')
29
+ super ":#{property} is not in [#{list}]"
30
+ end
31
+ end
32
+
33
+ # Manages errors raised when value does not pass the assertion
34
+ class AssertError < Error
35
+ # @param property [Symbol] name of the property
36
+ # @example
37
+ # ":number does not respect the assertion"
38
+ def initialize(property)
39
+ super ":#{property} does not respect the assertion"
40
+ end
41
+ end
42
+
43
+ module Collection
44
+ # Manages errors raised when value should be an collection
45
+ class ArrayError < Error
46
+ # @param property [Symbol] name of the property
47
+ # @example
48
+ # ":numbers is not an array"
49
+ def initialize(property)
50
+ super ":#{property} is not an array"
51
+ end
52
+ end
53
+
54
+ # Manages errors raised when at least one element of the collection is not an instance of the expected class
55
+ class TypeError < Error
56
+ # @param property [Symbol] name of the property
57
+ # @param type [Class] class to match
58
+ # @example
59
+ # ":numbers contains elements that are not instances of Integer"
60
+ def initialize(property, type)
61
+ super ":#{property} contains elements that are not instances of #{type}"
62
+ end
63
+ end
64
+
65
+ # Manages errors raised when at least one element of the collection is not accepted (not in the "one_of")
66
+ class OneOfError < Error
67
+ # @param property [Symbol] name of the property
68
+ # @param values [Array] accepted values
69
+ # @example
70
+ # ":fruits contains elements that are not in [:apple, :pear]"
71
+ def initialize(property, values)
72
+ # reintegrate the colon for symbols which is lost during interpolation
73
+ to_s = ->(v) { v.is_a?(Symbol) ? ":#{v}" : v }
74
+ list = values.map { |v| to_s.call(v) }.join(', ')
75
+ super ":#{property} contains elements that are not in [#{list}]"
76
+ end
77
+ end
78
+
79
+ # Manages errors raised when value does not pass the assertion
80
+ class AssertError < Error
81
+ # @param property [Symbol] name of the property
82
+ # @example
83
+ # ":numbers contains elements that do not respect the assertion"
84
+ def initialize(property)
85
+ super ":#{property} contains elements that do not respect the assertion"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,125 @@
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(number: 0, message:, fruits: [])
17
+ # self.number = number
18
+ # self.message = message
19
+ # self.fruits = fruits
20
+ # end
21
+ # RUBY
22
+ def draw(properties)
23
+ new(properties).draw
24
+ end
25
+ end
26
+
27
+ # Initialize an instance
28
+ #
29
+ # @param properties [Hash] a description of the class properties
30
+ def initialize(properties = {})
31
+ @properties = properties
32
+ end
33
+
34
+ # Draws a stringified initialize method
35
+ #
36
+ # @return [String]
37
+ #
38
+ # @example
39
+ # <<-RUBY
40
+ # def initialize(foo:, bar: 0)
41
+ # self.foo = foo
42
+ # self.bar = bar
43
+ # end
44
+ # RUBY
45
+ def draw
46
+ <<~RUBY
47
+ def initialize(#{initialize_parameters})
48
+ #{initialize_body}
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
62
+ # "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
+ # Generates 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\""
77
+ # @example when nil is allowed and default is not set
78
+ # " nil"
79
+ # @example when nil is not allowed
80
+ # ""
81
+ def default_parameter_for(options)
82
+ default, allow_nil = options.values_at(:default, :allow_nil)
83
+ if default.nil?
84
+ allow_nil ? ' nil' : ''
85
+ else
86
+ " #{prefix(default)}#{default}#{suffix(default)}"
87
+ end
88
+ end
89
+
90
+ # Generates the characters to put before the value
91
+ # @note it avoids symbol without colon or string without quotes
92
+ # which would be interpreted as methods
93
+ def prefix(default)
94
+ case default
95
+ when Symbol then ':'
96
+ when String then '"'
97
+ end
98
+ end
99
+
100
+ # Generates the characters to put after the value
101
+ # @note it avoids string without quotes which would be interpreted as method
102
+ def suffix(default)
103
+ case default
104
+ when String then '"'
105
+ end
106
+ end
107
+
108
+ # Generates initialize method assignments
109
+ #
110
+ # @return [String]
111
+ #
112
+ # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
113
+ # <<-RUBY
114
+ # self.foo = foo
115
+ # self.bar = bar
116
+ # RUBY
117
+ def initialize_body
118
+ assignments =
119
+ properties.keys.map do |name|
120
+ "self.#{name} = #{name}"
121
+ end
122
+ assignments.join("\n ")
123
+ end
124
+ end
125
+ end
@@ -1,17 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'initializer'
4
+
3
5
  module Otoroshi
4
- # This module is designed to be included in a class. This will provide
6
+ # This module is designed to be in a class. This will provide
5
7
  # the "property" ({Sanctuary::ClassMethods.property}) method for defining class properties.
6
8
  #
7
9
  # @example
8
10
  # class Importer
9
11
  # include Otoroshi::Sanctuary
10
12
  #
11
- # property :file_path, String, validate: ->(v) { v.match? /.+\.csv/ }
12
- # property :headers, [TrueClass, FalseClass], default: false
13
- # property :col_sep, String, validate: ->(v) { v.in? [',', ';', '\s', '\t', '|'] }, default: ','
14
- # property :converters, Symbol, validate: ->(v) { v.in? %i[integer float date] }, allow_nil: true
13
+ # property :whatever
14
+ # property :number, Integer
15
+ # property :numbers, [Integer]
16
+ # property :positive_number, Integer, verify: ->(v) { v >= 0 }
17
+ # property :number_or_nil, Integer, allow_nil: true
18
+ # property :number_with_default, Integer, default: 42
19
+ # property :number_in_collection, Integer, one_of: [1, 2, 3, 5, 8, 13, 21, 34]
15
20
  # end
16
21
  module Sanctuary
17
22
  class << self
@@ -26,218 +31,196 @@ module Otoroshi
26
31
  # Adds a new "property" to the class
27
32
  #
28
33
  # @param name [Symbol] the property name
29
- # @param type [Class] the expected value type
30
- # @param validate [Proc] a lambda processing the value and returning true or false
34
+ # @param type [Class, Array<Class>] the expected value or values type
35
+ # @param one_of [Array] the accepted values
36
+ # @param assert [Proc] a lambda processing the value and returning true or false
31
37
  # @param allow_nil [true, false] allow nil as a value
32
38
  # @param default [Object] default value if not set on initialization
33
39
  #
34
40
  # @return [void]
35
41
  #
36
42
  # @example
37
- # property name, type: String, validate: ->(v) { v.length > 3 }, allow_nil: true
43
+ # property name, type: String, assert: ->(v) { v.length > 3 }, allow_nil: true
38
44
  # @example
39
- # property score, type: Integer, validate: ->(v) { v >= 0 }, default: 0
40
- def property(name, type = Object, validate: ->(_) { true }, allow_nil: false, default: nil)
41
- add_to_properties(name, allow_nil, default)
42
- define_validate_type!(name, type, allow_nil)
43
- define_validate_lambda!(name, validate, allow_nil)
45
+ # property scores, type: [Integer], assert: ->(v) { v >= 0 }, default: []
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
+ expected_type = type.is_a?(Array) ? type.first || Object : type
49
+ collection = expected_type == Array || type.is_a?(Array)
50
+ define_validate_type(name, expected_type, collection, allow_nil)
51
+ define_validate_one_of(name, collection, one_of, allow_nil)
52
+ define_validate_assertion(name, collection, assert, allow_nil)
53
+ define_validate(name)
44
54
  define_getter(name)
45
55
  define_setter(name)
46
- redefine_initialize
56
+ class_eval Initializer.draw(properties), __FILE__, __LINE__ + 1
47
57
  end
48
58
 
49
- # Returns the class properties
59
+ # Class properties
50
60
  #
51
61
  # @return [Hash]
52
62
  #
53
- # @note this method will be updated by {add_to_properties}
63
+ # @example
64
+ # {
65
+ # number: { type: Integer, allow_nil: false, default: 0 },
66
+ # message: { type: Integer, allow_nil: true, default: nil }
67
+ # }
54
68
  def properties
55
69
  {}
56
70
  end
57
71
 
58
72
  private
59
73
 
60
- # Updates {properties} to add new property to the returned ones
61
- #
62
- # @return [void]
63
- def add_to_properties(name, allow_nil, default)
74
+ # Adds a properties to the {properties}
75
+ def add_to_properties(name, type, allow_nil, default)
64
76
  current_state = properties
65
- current_state[name] = { allow_nil: allow_nil, default: default }
77
+ current_state[name] = {
78
+ type: type,
79
+ allow_nil: allow_nil,
80
+ default: default
81
+ }
66
82
  define_singleton_method(:properties) { current_state }
67
83
  end
68
84
 
69
- # Defines a private method that raises an error if type is not respected
85
+ # Defines a private method that validates type condition
70
86
  #
71
- # @param name [Symbol] the property name
72
- # @param type [Class] the type to test against
73
- # @param allow_nil [true, false] allow nil as a value
74
- #
75
- # @return [void]
87
+ # Given name = :score, type = Integer, allow_nil = false
76
88
  #
77
- # @example
78
- # define_validate_type!(score, Integer, false) => def validate_score_type!(value) ...
79
- # @example Generated method
80
- # def validate_score_type!(value)
89
+ # def validate_score_type(value)
81
90
  # return if Integer.nil? || false && value.nil?
82
91
  # return if value.is_a? Integer
83
92
  #
84
- # raise ArgumentError, ":score does not match required type"
93
+ # raise Otoroshi::TypeError, :score, Integer
85
94
  # end
86
- def define_validate_type!(name, type, allow_nil)
87
- lambda = type_validation(type)
88
- define_method :"validate_#{name}_type!" do |value|
89
- return if type.nil? || allow_nil && value.nil?
90
- return if lambda.call(value)
95
+ def define_validate_type(name, type, collection, allow_nil)
96
+ validator = validate_type?(name, type, collection)
97
+ define_method :"validate_#{name}_type" do |value|
98
+ allow_nil && value.nil? || validator.call(value)
99
+ end
100
+ private :"validate_#{name}_type"
101
+ end
91
102
 
92
- raise ArgumentError, ":#{name} does not match required type"
103
+ # Lambda that validates (value) respects the type condition
104
+ # @return [Proc]
105
+ def validate_type?(name, type, collection)
106
+ if collection
107
+ # validate each element of (v) is an instance of the type
108
+ lambda do |v|
109
+ v.is_a?(Array) || raise(Otoroshi::Collection::ArrayError, name)
110
+ v.all? { |elt| elt.is_a? type } || raise(Otoroshi::Collection::TypeError.new(name, type))
111
+ end
112
+ else
113
+ # validate (v) is an instance of the type
114
+ ->(v) { v.is_a?(type) || raise(Otoroshi::TypeError.new(name, type)) }
93
115
  end
94
- private :"validate_#{name}_type!"
95
116
  end
96
117
 
97
- # Defines a lambda to be call to validate that value matches the type
118
+ # Defines a private method that validates one_of condition
98
119
  #
99
- # @param type [Class] the type to test against
120
+ # Given name = :side, collection = false, one_of = [:left, :right], allow_nil = false
100
121
  #
101
- # @return [Proc] the lambda to use in order to test the value matches the type
122
+ # def validate_side_type(value)
123
+ # return if false && value.nil?
124
+ # return if [:left, :right].include? value
102
125
  #
103
- # @example
104
- # type_validation(Integer) #=> ->(v) { v.is_a? Integer }
105
- # @example
106
- # type_validation([String, Symbol]) #=> ->(v) { [String, Symbol].any? { |t| v.is_a? t } }
107
- def type_validation(type)
108
- if type.is_a? Array
109
- ->(v) { type.any? { |t| v.is_a? t } }
126
+ # raise Otoroshi::OneOfError, :side, [:left, :right]
127
+ # end
128
+ def define_validate_one_of(name, collection, one_of, allow_nil)
129
+ validator = validate_one_of?(name, one_of, collection)
130
+ define_method(:"validate_#{name}_one_of") do |value|
131
+ allow_nil && value.nil? || validator.call(value)
132
+ end
133
+ private :"validate_#{name}_one_of"
134
+ end
135
+
136
+ # Lambda that validates (value) respects the one_of condition
137
+ # @return [Proc]
138
+ def validate_one_of?(name, one_of, collection)
139
+ return ->(_) {} unless one_of
140
+
141
+ if collection
142
+ lambda do |v|
143
+ v.all? { |e| one_of.include? e } || raise(Otoroshi::Collection::OneOfError.new(name, one_of))
144
+ end
110
145
  else
111
- ->(v) { v.is_a? type }
146
+ lambda do |v|
147
+ one_of.include?(v) || raise(Otoroshi::OneOfError.new(name, one_of))
148
+ end
112
149
  end
113
150
  end
114
151
 
115
- # Defines a private method that raises an error if validate block returns false
152
+ # Defines a private method that validates assert condition
116
153
  #
117
- # @param name [Symbol] the property name
118
- # @param validate [Proc] a lambda processing the value and returning true or false
119
- # @param allow_nil [true, false] allow nil as a value
154
+ # Given name = :score, assert = ->(v) { v >= 0 }, allow_nil = false
120
155
  #
121
- # @return [void]
122
- #
123
- # @example
124
- # define_validate_lambda!("score", ->(v) { v >= 0 }, false) #=> def validate_score_lambda!(value) ...
125
- # @example Generated instance method
126
- # def validate_score_lambda!(value)
156
+ # def validate_score_assertion(value)
127
157
  # return if false && value.nil?
128
158
  # return if value >= 0
129
159
  #
130
- # raise ArgumentError, ":score does not match validation"
160
+ # raise Otoroshi::AssertError, :score
131
161
  # end
132
- def define_validate_lambda!(name, validate, allow_nil)
133
- define_method :"validate_#{name}_lambda!" do |value|
134
- return if allow_nil && value.nil?
135
- return if instance_exec(value, &validate)
162
+ def define_validate_assertion(name, collection, assert, allow_nil)
163
+ validator = validate_assert?(name, assert, collection)
164
+ define_method :"validate_#{name}_assertion" do |value|
165
+ allow_nil && value.nil? || validator.call(value)
166
+ end
167
+ private :"validate_#{name}_assertion"
168
+ end
136
169
 
137
- raise ArgumentError, ":#{name} does not match validation"
170
+ # Lambda that validates (value) respects the assert condition
171
+ # @return [Proc]
172
+ def validate_assert?(name, assert, collection)
173
+ if collection
174
+ ->(v) { v.all? { |e| instance_exec(e, &assert) } || raise(Otoroshi::Collection::AssertError, name) }
175
+ else
176
+ ->(v) { instance_exec(v, &assert) || raise(Otoroshi::AssertError, name) }
138
177
  end
139
- private :"validate_#{name}_lambda!"
140
178
  end
141
179
 
142
- # Defines a getter method for the property
180
+ # Defines a private method that calls all validations
143
181
  #
144
- # @param name [Symbol] the property name
182
+ # Given name = :score
145
183
  #
146
- # @return [void]
184
+ # def validate_score!(value)
185
+ # validate_score_type(value)
186
+ # validate_score_one_of(value)
187
+ # validate_score_assert(value)
188
+ # end
189
+ def define_validate(name)
190
+ define_method :"validate_#{name}!" do |value|
191
+ __send__(:"validate_#{name}_type", value)
192
+ __send__(:"validate_#{name}_one_of", value)
193
+ __send__(:"validate_#{name}_assertion", value)
194
+ end
195
+ private :"validate_#{name}!"
196
+ end
197
+
198
+ # Defines a getter
147
199
  #
148
- # @example
149
- # define_getter(:score) #=> def score ...
150
- # @example Generated instance method
151
- # def score
152
- # instance_variable_get(@score)
153
- # end
200
+ # Given name = :score
201
+ #
202
+ # def score
203
+ # instance_variable_get(@score)
204
+ # end
154
205
  def define_getter(name)
155
- define_method(name) { instance_variable_get("@#{name}") }
206
+ define_method(name) { instance_variable_get("@#{name}").clone.freeze }
156
207
  end
157
208
 
158
- # Defines a setter method for the property
159
- #
160
- # @param name [Symbol] the property name
209
+ # Defines a setter
161
210
  #
162
- # @return [void]
211
+ # Given name = :score
163
212
  #
164
- # @example
165
- # define_getter(:score) #=> def score=(value) ...
166
- # @example Generated instance method
167
213
  # def score=(value)
168
- # validate_score_type!(value)
214
+ # validate_score_type(value)
169
215
  # validate_score!(value)
170
216
  # instance_variable_set(@score, value)
171
217
  # end
172
218
  def define_setter(name)
173
219
  define_method :"#{name}=" do |value|
174
- __send__(:"validate_#{name}_type!", value)
175
- __send__(:"validate_#{name}_lambda!", value)
220
+ __send__(:"validate_#{name}!", value)
176
221
  instance_variable_set("@#{name}", value)
177
222
  end
178
223
  end
179
-
180
- # Redefines initialize method
181
- #
182
- # @return [void]
183
- #
184
- # @note method is defined with `class_eval`
185
- #
186
- # @example Generated method
187
- # def initialize(foo:, bar: 0)
188
- # self.foo = foo
189
- # self.bar = bar
190
- # end
191
- def redefine_initialize
192
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
193
- def initialize(#{initialize_parameters})
194
- #{initialize_body}
195
- end
196
- RUBY
197
- end
198
-
199
- # Defines initialize method parameters
200
- #
201
- # @return [String]
202
- #
203
- # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
204
- # redefine_initialize #=> "foo:, bar: 0"
205
- def initialize_parameters
206
- parameters =
207
- properties.map do |key, options|
208
- allow_nil, default = options.values
209
- "#{key}:#{default_parameter_for(allow_nil, default)}"
210
- end
211
- parameters.join(', ')
212
- end
213
-
214
- # Defines the default value of a parameter depending on options
215
- #
216
- # @param options [Hash]
217
- #
218
- # @return [String]
219
- #
220
- # @example when nil is allowed and default is set
221
- # default_parameter_for(true, 0) #=> " 0"
222
- # @example when nil is allowed and default is not set
223
- # default_parameter_for(true, nil) #=> " nil"
224
- # @example when nil is not allowed
225
- # default_parameter_for(false, nil) #=> ""
226
- def default_parameter_for(allow_nil, default)
227
- return " #{default}" if default
228
-
229
- allow_nil ? ' nil' : ''
230
- end
231
-
232
- # Defines initialize method body
233
- #
234
- # @return [String]
235
- #
236
- # @example Given properties { foo: { allow_nil: false, default: nil }, { allow_nil: true, default: 0 } }
237
- # initialize_body #=> "self.foo = foo\nself.bar = bar"
238
- def initialize_body
239
- properties.keys.map { |key| "self.#{key} = #{key}" }.join("\n")
240
- end
241
224
  end
242
225
  end
243
226
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otoroshi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edouard Piron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-10 00:00:00.000000000 Z
11
+ date: 2020-10-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Help defining class properties
14
14
  email: ed.piron@gmail.com
@@ -16,6 +16,9 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - lib/otoroshi.rb
20
+ - lib/otoroshi/exceptions.rb
21
+ - lib/otoroshi/initializer.rb
19
22
  - lib/otoroshi/sanctuary.rb
20
23
  homepage: https://rubygems.org/gems/otoroshi
21
24
  licenses: