adaptiveconfiguration 1.0.0.beta04 → 1.0.0.beta07

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: a11fad3ccce7d331a27190c743c44688ad95900a7cc8f168a4fdcb824b625bce
4
- data.tar.gz: aa7ed25488d182dcaf59ec3f2f0a6442c9004660d9c22b8b1ba73b0e730b190c
3
+ metadata.gz: 1bddcf16845596d56add432ccd7aefe97a9764b1e99a644b9947427ef49819a2
4
+ data.tar.gz: bf5c024845348795609942aace53b06da90b5f0cfa8e531bf26569554e57c2cb
5
5
  SHA512:
6
- metadata.gz: 82df20c8476433d0cec6cb10df0242b5b2b9acfe426741727871f67bde4b46b1bff4e3ab56c1bd8553284d5336074f48abad92ba30f3cc4ef2f27ca41e5b2429
7
- data.tar.gz: c83a76b55815d78ddc19cce6a118ce6ce26f4c3a4288837e40ee9b565db1d13c329d8dfed04ef1f42923d76b703d20742b73de652a4857fd2f9b0f158e64c850
6
+ metadata.gz: a4d2af44187899024e1c2a4edbb7e811a613b438ee87a63badc87a3574a8740b6217289463cdc286d0312f4e353958c00a89c69b4dab9e6f93221446dc7ed1f8
7
+ data.tar.gz: 7a247388fdad9d6c99e2c2f6dd78e8490a0cfe5ce8172fb0388c25e19f5e2e4ef579c5613b614c29ce8cb56bc231f605473a064594c110307adc282722f2f924
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do | spec |
2
2
 
3
3
  spec.name = 'adaptiveconfiguration'
4
- spec.version = '1.0.0.beta04'
4
+ spec.version = '1.0.0.beta07'
5
5
  spec.authors = [ 'Kristoph Cichocki-Romanov' ]
6
6
  spec.email = [ 'rubygems.org@kristoph.net' ]
7
7
 
@@ -1,5 +1,5 @@
1
1
  require_relative 'group_builder'
2
- require_relative 'context'
2
+ require_relative 'scaffold'
3
3
 
4
4
  # types must be included to support conversation
5
5
  require 'time'
@@ -16,6 +16,7 @@ module AdaptiveConfiguration
16
16
  Time => ->( v ) { v.respond_to?( :to_time ) ? v.to_time : Time.parse( v.to_s ) },
17
17
  URI => ->( v ) { URI.parse( v.to_s ) },
18
18
  String => ->( v ) { String( v ) },
19
+ Symbol => ->( v ) { v.respond_to?( :to_sym ) ? v.to_sym : nil },
19
20
  Rational => ->( v ) { Rational( v ) },
20
21
  Float => ->( v ) { Float( v ) },
21
22
  Integer => ->( v ) { Integer( v ) },
@@ -48,19 +49,123 @@ module AdaptiveConfiguration
48
49
  end
49
50
 
50
51
  def build( values = nil, &block )
51
- context = AdaptiveConfiguration::Context.new(
52
+ scaffold = AdaptiveConfiguration::Scaffold.new(
52
53
  values,
53
54
  converters: @converters,
54
55
  definitions: @definitions
55
56
  )
56
- context.instance_eval( &block ) if block
57
- context
57
+ scaffold.instance_eval( &block ) if block
58
+ scaffold.to_h
58
59
  end
59
60
 
60
61
  def build!( values = nil, &block )
61
- context = self.build( values, &block )
62
- context.validate!
63
- context
62
+ scaffold = AdaptiveConfiguration::Scaffold.new(
63
+ values,
64
+ converters: @converters,
65
+ definitions: @definitions
66
+ )
67
+ scaffold.instance_eval( &block ) if block
68
+ result = scaffold.to_h
69
+ validate!( result )
70
+ result
71
+ end
72
+
73
+ def validate!( values )
74
+ traverse_and_validate_values( values, definitions: @definitions ) { | error |
75
+ raise error
76
+ }
77
+ end
78
+
79
+ def validate( values )
80
+ errors = []
81
+ traverse_and_validate_values( values, definitions: @definitions ) { | error |
82
+ errors << error
83
+ }
84
+ errors
85
+ end
86
+
87
+ def valid?( values )
88
+ traverse_and_validate_values( values, definitions: @definitions ) {
89
+ return false
90
+ }
91
+ return true
92
+ end
93
+
94
+ protected
95
+
96
+ def value_matches_types?( value, types )
97
+ type_match = false
98
+ Array( types ).each do | type |
99
+ type_match = value.is_a?( type )
100
+ break if type_match
101
+ end
102
+ type_match
103
+ end
104
+
105
+ def traverse_and_validate_values( values, definitions:, path: nil, options: nil, &block )
106
+
107
+ path.chomp( '/' ) if path
108
+ unless values.is_a?( Hash )
109
+ # TODO: raise error
110
+ return
111
+ end
112
+
113
+ definitions.each do | key, definition |
114
+
115
+ name = definition[ :as ] || key
116
+ value = values[ name ]
117
+
118
+ if definition[ :required ] &&
119
+ ( !value || ( value.respond_to?( :empty ) && value.empty? ) )
120
+ block.call( RequirementUnmetError.new( path: path, key: key ) )
121
+ elsif !definition[ :default_assigned ] && !value.nil?
122
+ unless definition[ :array ]
123
+ if definition[ :type ] == :group
124
+ traverse_and_validate_values(
125
+ values[ name ],
126
+ definitions: definition[ :definitions ],
127
+ path: "#{ ( path || '' ) + ( path ? '/' : '' ) + key.to_s }",
128
+ &block
129
+ )
130
+ else
131
+ if definition[ :type ] && value && !definition[ :default_assigned ]
132
+ unless value_matches_types?( value, definition[ :type ] )
133
+ block.call( IncompatibleTypeError.new(
134
+ path: path, key: key, type: definition[ :type ], value: value
135
+ ) )
136
+ end
137
+ end
138
+ end
139
+ else
140
+ if definition[ :type ] == :group
141
+ groups = Array( value )
142
+ groups.each do | group |
143
+ traverse_and_validate_values(
144
+ group,
145
+ definitions: definition[ :definitions ],
146
+ path: "#{ ( path || '' ) + ( path ? '/' : '' ) + key.to_s }",
147
+ &block
148
+ )
149
+ end
150
+ else
151
+ if definition[ :type ] && !definition[ :default_assigned ]
152
+ value_array = Array( value )
153
+ value_array.each do | v |
154
+ unless value_matches_types?( v, definition[ :type ] )
155
+ block.call( IncompatibleTypeError.new(
156
+ path: path, key: key, type: definition[ :type ], value: v
157
+ ) )
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ nil
168
+
64
169
  end
65
170
 
66
171
  end
@@ -1,4 +1,4 @@
1
- require_relative 'context'
1
+ require_relative 'scaffold'
2
2
 
3
3
  module AdaptiveConfiguration
4
4
  class GroupBuilder
@@ -14,8 +14,8 @@ module AdaptiveConfiguration
14
14
  name = name.to_sym
15
15
  options = nil
16
16
 
17
- raise NameError, "The parameter #{name} cannot be used." \
18
- if AdaptiveConfiguration::Context.instance_methods.include?( name )
17
+ raise NameError, "The name '#{name}' is reserved and cannot be used for parameters." \
18
+ if AdaptiveConfiguration::Scaffold.instance_methods.include?( name )
19
19
 
20
20
  if args.first.is_a?( ::Hash )
21
21
  # when called without type: parameter :stream, as: :streams
@@ -30,12 +30,17 @@ module AdaptiveConfiguration
30
30
  end
31
31
 
32
32
  def group( name, options = {}, &block )
33
+
34
+ raise NameError, "The name '#{name}' is reserved and cannot be used for parameters." \
35
+ if AdaptiveConfiguration::Scaffold.instance_methods.include?( name )
36
+
33
37
  builder = GroupBuilder.new
34
38
  builder.instance_eval( &block ) if block
35
39
  @definitions[ name ] = options.merge( {
36
40
  type: :group,
37
41
  definitions: builder.definitions
38
42
  } )
43
+
39
44
  end
40
45
 
41
46
  end
@@ -0,0 +1,177 @@
1
+ module AdaptiveConfiguration
2
+ class Scaffold < BasicObject
3
+
4
+ include ::PP::ObjectMixin if defined?( ::PP )
5
+
6
+ attr_reader :errors
7
+
8
+ def initialize( values = nil, definitions:, converters: )
9
+ raise ArgumentError, 'The Scaffold initialitization attributes must be a Hash pr Hash-like.'\
10
+ unless values.nil? || ( values.respond_to?( :[] ) && values.respond_to?( :key? ) )
11
+
12
+ @converters = converters&.dup
13
+ @definitions = definitions&.dup
14
+ @values = {}
15
+ @errors = []
16
+
17
+ @definitions.each do | key, definition |
18
+ name = definition[ :as ] || key
19
+ if definition.key?( :default )
20
+ self.__send__( key, definition[ :default ] )
21
+ # note: this is needed to know when an array parameter which was initially assigned
22
+ # to a default should be replaced rather than appended
23
+ definition[ :default_assigned ] = true
24
+ end
25
+ self.__send__( key, values[ key ] ) if values && values.key?( key )
26
+ end
27
+
28
+ end
29
+
30
+ def nil?
31
+ false
32
+ end
33
+
34
+ def empty?
35
+ @values.empty?
36
+ end
37
+
38
+ def to_h
39
+ recursive_to_h = ->( object ) do
40
+ case object
41
+ when ::NilClass
42
+ nil
43
+ when ::AdaptiveConfiguration::Scaffold
44
+ recursive_to_h.call( object.to_h )
45
+ when ::Hash
46
+ object.transform_values { | value | recursive_to_h.call( value ) }
47
+ when ::Array
48
+ object.map { | element | recursive_to_h.call( element ) }
49
+ else
50
+ object.respond_to?( :to_h ) ? object.to_h : object
51
+ end
52
+ end
53
+
54
+ recursive_to_h.call( @values )
55
+ end
56
+
57
+ def to_s
58
+ inspect
59
+ end
60
+
61
+ def inspect
62
+ { values: @values, definitions: @definitions }.inspect
63
+ end
64
+
65
+ if defined?( ::PP )
66
+ def pretty_print( pp )
67
+ pp.pp( { values: @values, definitions: @definitions } )
68
+ end
69
+ end
70
+
71
+ def class
72
+ ::AdaptiveConfiguration::Scaffold
73
+ end
74
+
75
+ def is_a?( klass )
76
+ klass == ::AdaptiveConfiguration::Scaffold || klass == ::BasicObject
77
+ end
78
+
79
+ alias :kind_of? :is_a?
80
+
81
+ def method_missing( method, *args, &block )
82
+
83
+ if @definitions.key?( method )
84
+ definition = @definitions[ method ]
85
+ name = definition[ :as ] || method
86
+
87
+ unless definition[ :array ]
88
+ if definition[ :type ] == :group
89
+ value = args.first
90
+ context = @values[ name ]
91
+ if context.nil? || value
92
+ context =
93
+ Scaffold.new(
94
+ value,
95
+ converters: @converters,
96
+ definitions: definition[ :definitions ]
97
+ )
98
+ end
99
+ context.instance_eval( &block ) if block
100
+ @values[ name ] = context
101
+ else
102
+ value = args.first
103
+ new_value = definition[ :type ] ? __coerce_value( definition[ :type ], value ) : value
104
+ @values[ name ] = new_value.nil? ? value : new_value
105
+ end
106
+ else
107
+ @values[ name ] = definition[ :default_assigned ] ?
108
+ ::Array.new :
109
+ @values[ name ] || ::Array.new
110
+ if definition[ :type ] == :group
111
+ values = [ args.first ].flatten
112
+ values = values.map do | v |
113
+ context = Scaffold.new(
114
+ v,
115
+ converters: @converters,
116
+ definitions: definition[ :definitions ]
117
+ )
118
+ context.instance_eval( &block ) if block
119
+ context
120
+ end
121
+ @values[ name ].concat( values )
122
+ else
123
+ values = ::Kernel.method( :Array ).call( args.first )
124
+ if type = definition[ :type ]
125
+ values = values.map do | v |
126
+ new_value = __coerce_value( type, v )
127
+ new_value.nil? ? v : new_value
128
+ end
129
+ end
130
+ @values[ name ].concat( values )
131
+ end
132
+ end
133
+
134
+ definition[ :default_assigned ] = false
135
+ @values[ name ]
136
+ else
137
+ super
138
+ end
139
+
140
+ end
141
+
142
+ def respond_to?( method, include_private = false )
143
+ @definitions.key?( method ) || self.class.instance_methods.include?( method )
144
+ end
145
+
146
+ def respond_to_missing?( method, include_private = false )
147
+ @definitions.key?( method ) || self.class.instance_methods.include?( method )
148
+ end
149
+
150
+ protected
151
+
152
+ def __coerce_value( types, value )
153
+
154
+ return value unless types && !value.nil?
155
+
156
+ types = ::Kernel.method( :Array ).call( types )
157
+ result = nil
158
+
159
+ if value.respond_to?( :is_a? )
160
+ types.each do | type |
161
+ result = value.is_a?( type ) ? value : nil
162
+ break unless result.nil?
163
+ end
164
+ end
165
+
166
+ if result.nil?
167
+ types.each do | type |
168
+ result = @converters[ type ].call( value ) rescue nil
169
+ break unless result.nil?
170
+ end
171
+ end
172
+
173
+ result
174
+ end
175
+
176
+ end
177
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adaptiveconfiguration
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta04
4
+ version: 1.0.0.beta07
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kristoph Cichocki-Romanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-08 00:00:00.000000000 Z
11
+ date: 2024-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -57,9 +57,9 @@ files:
57
57
  - lib/adaptive_configuration.rb
58
58
  - lib/adaptive_configuration/builder.rb
59
59
  - lib/adaptive_configuration/configurable.rb
60
- - lib/adaptive_configuration/context.rb
61
60
  - lib/adaptive_configuration/errors.rb
62
61
  - lib/adaptive_configuration/group_builder.rb
62
+ - lib/adaptive_configuration/scaffold.rb
63
63
  homepage: https://github.com/EndlessInternational/adaptive_configuration
64
64
  licenses:
65
65
  - MIT
@@ -1,271 +0,0 @@
1
- module AdaptiveConfiguration
2
- class Context < BasicObject
3
-
4
- attr_reader :errors
5
-
6
- def initialize( values = nil, definitions:, converters: )
7
-
8
- values = values ? values.transform_keys( &:to_sym ) : {}
9
-
10
- @converters = converters&.dup
11
- @definitions = definitions&.dup
12
- @values = {}
13
- @errors = []
14
-
15
- @definitions.each do | key, definition |
16
- name = definition[ :as ] || key
17
- if definition[ :type ] == :group
18
- context = Context.new(
19
- values[ key ] || {},
20
- converters: @converters, definitions: definition[ :definitions ],
21
- )
22
- @values[ name ] = context unless context.empty?
23
- elsif definition.key?( :default )
24
- @values[ name ] = definition[ :array ] ?
25
- ::Kernel.method( :Array ).call( definition[ :default ] ) :
26
- definition[ :default ]
27
- # note: this is needed to know when an array paramter which was initially assigned
28
- # to a default should be replaced rather than appended
29
- definition[ :default_assigned ] = true
30
- end
31
- self.__send__( key, values[ key ] ) if values[ key ]
32
- end
33
-
34
- end
35
-
36
- def nil?
37
- false
38
- end
39
-
40
- def empty?
41
- @values.empty?
42
- end
43
-
44
- def []( key )
45
- @values[ key ]
46
- end
47
-
48
- def []=( key, value )
49
- @values[ key ] = value
50
- end
51
-
52
- def each( &block )
53
- @values.each( &block )
54
- end
55
-
56
-
57
- def merge( hash )
58
- self.to_h.merge( hash )
59
- end
60
-
61
- def valid?
62
- __validate_values
63
- @errors.empty?
64
- end
65
-
66
- def validate!
67
- __validate_values { | error | ::Kernel.raise error }
68
- end
69
-
70
- def to_h
71
- recursive_to_h = ->( object ) do
72
- case object
73
- when ::AdaptiveConfiguration::Context
74
- recursive_to_h.call( object.to_h )
75
- when ::Hash
76
- object.transform_values { | value | recursive_to_h.call( value ) }
77
- when ::Array
78
- object.map { | element | recursive_to_h.call( element ) }
79
- else
80
- object.respond_to?( :to_h ) ? object.to_h : object
81
- end
82
- end
83
-
84
- recursive_to_h.call( @values )
85
- end
86
-
87
- def to_s
88
- inspect
89
- end
90
-
91
- def to_yaml
92
- self.to_h.to_yaml
93
- end
94
-
95
- def inspect
96
- @values.inspect
97
- end
98
-
99
- def class
100
- ::AdaptiveConfiguration::Context
101
- end
102
-
103
- def is_a?( klass )
104
- klass == ::AdaptiveConfiguration::Context || klass == ::BasicObject
105
- end
106
-
107
- alias :kind_of? :is_a?
108
-
109
- def method_missing( method, *args, &block )
110
-
111
- if @definitions.key?( method )
112
-
113
- definition = @definitions[ method ]
114
- name = definition[ :as ] || method
115
-
116
- unless definition[ :array ]
117
- if definition[ :type ] == :group
118
- context =
119
- @values[ name ] || Context.new(
120
- args.first,
121
- converters: @converters,
122
- definitions: definition[ :definitions ]
123
- )
124
- context.instance_eval( &block ) if block
125
- @values[ name ] = context
126
- else
127
- value = args.first
128
- new_value = definition[ :type ] ? __coerce_value( definition[ :type ], value ) : value
129
- @values[ name ] = new_value.nil? ? value : new_value
130
- end
131
- else
132
- @values[ name ] = definition[ :default_assigned ] ?
133
- ::Array.new :
134
- @values[ name ] || ::Array.new
135
- if definition[ :type ] == :group
136
- context = Context.new(
137
- args.first,
138
- converters: @converters,
139
- definitions: definition[ :definitions ]
140
- )
141
- context.instance_eval( &block ) if block
142
- @values[ name ] << context
143
- else
144
- values = ::Kernel.method( :Array ).call( args.first )
145
- if type = definition[ :type ]
146
- values = values.map do | v |
147
- new_value = __coerce_value( type, v )
148
- new_value.nil? ? v : new_value
149
- end
150
- end
151
- @values[ name ].concat( values )
152
- end
153
- end
154
-
155
- definition[ :default_assigned ] = false
156
- @values[ name ]
157
-
158
- else
159
- super
160
- end
161
-
162
- end
163
-
164
- def respond_to?( method, include_private = false )
165
- @definitions.key?( method ) || self.class.instance_methods.include?( method )
166
- end
167
-
168
- def respond_to_missing?( method, include_private = false )
169
- @definitions.key?( method ) || self.class.instance_methods.include?( method )
170
- end
171
-
172
- protected
173
-
174
- def __coerce_value( types, value )
175
-
176
- return value unless types && !value.nil?
177
-
178
- types = ::Kernel.method( :Array ).call( types )
179
- result = nil
180
-
181
- if value.respond_to?( :is_a? )
182
- types.each do | type |
183
- result = value.is_a?( type ) ? value : nil
184
- break unless result.nil?
185
- end
186
- end
187
-
188
- if result.nil?
189
- types.each do | type |
190
- result = @converters[ type ].call( value ) rescue nil
191
- break unless result.nil?
192
- end
193
- end
194
-
195
- result
196
- end
197
-
198
- def __validate_values( path = nil, &block )
199
-
200
- path.chomp( '/' ) if path
201
- @errors = []
202
-
203
- is_of_matching_types = ::Proc.new do | value, types |
204
- type_match = false
205
- ::Kernel.method( :Array ).call( types ).each do | type |
206
- type_match = value.is_a?( type )
207
- break if type_match
208
- end
209
- type_match
210
- end
211
-
212
- @definitions.each do | key, definition |
213
-
214
- name = definition[ :as ] || key
215
- value = @values[ name ]
216
-
217
- if definition[ :required ] &&
218
- ( !value || ( value.respond_to?( :empty ) && value.empty? ) )
219
-
220
- error = RequirementUnmetError.new( path: path, key: key )
221
- block.call( error ) if block
222
- @errors << error
223
-
224
- elsif !definition[ :default_assigned ] && !value.nil?
225
-
226
- unless definition[ :array ]
227
-
228
- if definition[ :type ] == :group
229
- value.__validate_values( "#{ ( path || '' ) + ( path ? '/' : '' ) + key.to_s }", &block )
230
- @errors.concat( value.errors )
231
- else
232
- if definition[ :type ] && value && !definition[ :default_assigned ]
233
- unless is_of_matching_types.call( value, definition[ :type ] )
234
- error = IncompatibleTypeError.new(
235
- path: path, key: key, type: definition[ :type ], value: value
236
- )
237
- block.call( error ) if block
238
- @errors << error
239
- end
240
- end
241
- end
242
-
243
- else
244
-
245
- if definition[ :type ] == :group
246
- groups.each do | group |
247
- group.__validate_values( "#{ ( path || '' ) + ( path ? '/' : '' ) + key.to_s }", &block )
248
- @errors.concat( group.errors )
249
- end
250
- else
251
- if definition[ :type ] && !definition[ :default_assigned ]
252
- values = ::Kernel.method( :Array ).call( value )
253
- values.each do | v |
254
- unless is_of_matching_types.call( v, definition[ :type ] )
255
- error = IncompatibleTypeError.new(
256
- path: path, key: key, type: definition[ :type ], value: v
257
- )
258
- block.call( error ) if block
259
- @errors << error
260
- end
261
- end
262
- end
263
- end
264
-
265
- end
266
- end
267
- end
268
- end
269
-
270
- end
271
- end