adaptiveconfiguration 1.0.0.beta04 → 1.0.0.beta07

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: 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