configurations 2.2.0 → 2.2.1

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
  SHA1:
3
- metadata.gz: a0d503a4163d4238d71c61d2349a78e5f74b59be
4
- data.tar.gz: 21343c5d0ff8b12c948caced4f73bb74a04d2083
3
+ metadata.gz: 64f706300eff0c3494e39c9e560b63b198513d07
4
+ data.tar.gz: 82d0bbb8af8484bc6dbcd9c1162f659690d867a3
5
5
  SHA512:
6
- metadata.gz: 71026df8af3278a819d01c1ce0729a27d86ef8a83845596a4301730eee4fb585afeb1ca0f6681c23ac68a1fc74237880fb332bb720d7b6cffb9daecac54f445f
7
- data.tar.gz: 354aea5c774097ef557c2ce645f55c124fe2f5f0684fb0c01e205741c8afc8a2134aa00a0994528303c2e9f44d20c4d17387148f1004871ba19d8877d6d2a9be
6
+ metadata.gz: 44aa42ce5c00099190352575d9f76fd20ab2fe9135d97a3a6a1df3a24633e512303f55b4eb96d7e263b9b717c33034728d91bc3f699c6ad282db5060da64dfc4
7
+ data.tar.gz: e42634d42c69e015e6e47b499519c121b0076562983edea0a4f8c40bb0b3cb4e6024bd7f08b489bea51b0b800d1d11315e1df85a705611295ba8ec4a8b745c8a
@@ -3,14 +3,12 @@ branches:
3
3
  only:
4
4
  - master
5
5
  rvm:
6
- - 1.9.2
7
- - 1.9.3
8
6
  - 2.0.0
9
- - 2.1.6
10
- - 2.2.2
11
- - rbx
7
+ - 2.1.10
8
+ - 2.2.5
9
+ - 2.3.1
10
+ - rbx-2
12
11
  - jruby-19mode
13
12
  - jruby-20mode
14
- - jruby-head
15
13
 
16
14
  script: CODECLIMATE_REPO_TOKEN=36cf84c73264d3c361003f66903eec8aa5fb2b3494496f3a9676630518ecc9f9 rake
data/Rakefile CHANGED
@@ -5,4 +5,5 @@ task default: :test
5
5
  Rake::TestTask.new do |t|
6
6
  t.libs << 'test'
7
7
  t.pattern = 'test/**/test*.rb'
8
+ t.warning = true
8
9
  end
@@ -20,6 +20,8 @@ SUMMARY
20
20
  s.test_files = `git ls-files -- test/*`.split("\n")
21
21
 
22
22
  s.add_development_dependency 'minitest', '~> 5.4'
23
+ s.add_development_dependency 'minitest-focus', '~> 1.1'
24
+ s.add_development_dependency 'test-unit', '~> 3'
23
25
  s.add_development_dependency 'yard', '~> 0.8'
24
26
  s.add_development_dependency 'rake', '~> 10'
25
27
  end
@@ -1,9 +1,13 @@
1
- require_relative 'configurations/error'
1
+ require_relative 'configurations/arbitrary'
2
2
  require_relative 'configurations/blank_object'
3
+ require_relative 'configurations/configurable'
3
4
  require_relative 'configurations/configuration'
4
- require_relative 'configurations/arbitrary'
5
+ require_relative 'configurations/data'
6
+ require_relative 'configurations/error'
7
+ require_relative 'configurations/maps'
8
+ require_relative 'configurations/path'
5
9
  require_relative 'configurations/strict'
6
- require_relative 'configurations/configurable'
10
+ require_relative 'configurations/validators'
7
11
 
8
12
  # Configurations provides a unified approach to do configurations
9
13
  # with the flexibility to do everything from arbitrary configurations
@@ -16,5 +20,5 @@ module Configurations
16
20
 
17
21
  # Version number of Configurations
18
22
  #
19
- VERSION = '2.2.0'
23
+ VERSION = '2.2.1'
20
24
  end
@@ -30,7 +30,9 @@ module Configurations
30
30
  elsif __respond_to_method_for_write?(method)
31
31
  @data[method]
32
32
  elsif __respond_to_method_for_read?(method, *args, &block)
33
- @data.fetch(method, &__not_configured_callback_for__(method))
33
+ @data.fetch(method) do
34
+ @not_configured_blocks.evaluate!(@path.add(method), method)
35
+ end
34
36
  else
35
37
  super
36
38
  end
@@ -54,20 +56,13 @@ module Configurations
54
56
  # (in configure block)
55
57
  #
56
58
  def from_h(h)
57
- unless @__writeable__
59
+ unless @writeable
58
60
  fail ::ArgumentError, 'can not dynamically assign values from a hash'
59
61
  end
60
62
 
61
63
  super
62
64
  end
63
65
 
64
- # @param [Symbol] property The property to test for configurability
65
- # @return [Boolean] whether the given property is configurable
66
- #
67
- def __configurable?(_property)
68
- true
69
- end
70
-
71
66
  # Set the configuration to writeable or read only. Access to writer methods
72
67
  # is only allowed within the configure block, this method is used to invoke
73
68
  # writeability for subconfigurations.
@@ -75,8 +70,8 @@ module Configurations
75
70
  # false otherwise
76
71
  #
77
72
  def __writeable__=(data)
78
- @__writeable__ = data
79
- return if @data.nil?
73
+ @writeable = data
74
+ return unless defined?(@data) && @data
80
75
 
81
76
  @data.each do |_k, v|
82
77
  v.__writeable__ = data if v.is_a?(__class__)
@@ -104,7 +99,7 @@ module Configurations
104
99
  # property as a method during writeable state
105
100
  #
106
101
  def __respond_to_method_for_write?(method)
107
- !__is_writer?(method) && @__writeable__ && @data[method].is_a?(__class__)
102
+ !__is_writer?(method) && @writeable && @data[method].is_a?(__class__)
108
103
  end
109
104
 
110
105
  # @param [Symbol] method the method to test for
@@ -120,7 +115,7 @@ module Configurations
120
115
  # state
121
116
  #
122
117
  def __respond_to_writer?(method)
123
- @__writeable__ && __is_writer?(method)
118
+ @writeable && __is_writer?(method)
124
119
  end
125
120
  end
126
121
  end
@@ -31,6 +31,11 @@ module Configurations
31
31
  :object_id,
32
32
  # rbx needs the singleton class to access singleton methods
33
33
  :singleton_class,
34
+ # rbx needs its private methods
35
+ :__instance_variable_defined_p__,
36
+ :__instance_variable_get__,
37
+ :__instance_variable_set__,
38
+ :__instance_variable__,
34
39
  *ALIAS_KERNEL_METHODS.keys
35
40
  ].compact.freeze
36
41
 
@@ -117,10 +117,22 @@ module Configurations
117
117
  # end
118
118
  #
119
119
  def configurable(*properties, &block)
120
- type = properties.shift if properties.first.is_a?(Module)
120
+ @configurable_properties ||= Maps::Properties.new
121
+ @configurable_types ||= Maps::Types.new
122
+ @configurable_blocks ||= Maps::Blocks.new
123
+
124
+ type, properties = extract_type(properties)
125
+ @configurable_properties.add(properties)
126
+ @configurable_types.add(type, properties)
127
+ @configurable_blocks.add(block, properties)
128
+ end
121
129
 
122
- @configurable ||= {}
123
- @configurable.merge! to_configurable_hash(properties, type, &block)
130
+ def extract_type(properties)
131
+ if properties.first.is_a?(Module)
132
+ [properties.first, properties[1...properties.size]]
133
+ else
134
+ [nil, properties]
135
+ end
124
136
  end
125
137
 
126
138
  # returns whether a property is set to be configurable
@@ -128,7 +140,9 @@ module Configurations
128
140
  # @return [Boolean] whether the property is configurable
129
141
  #
130
142
  def configurable?(property)
131
- @configurable.is_a?(Hash) && @configurable.key?(property)
143
+ defined?(@configurable_properties) &&
144
+ @configurable_properties &&
145
+ @configurable_properties.configurable?(Path.new([property]))
132
146
  end
133
147
 
134
148
  # configuration method can be used to retrieve properties
@@ -151,14 +165,8 @@ module Configurations
151
165
  "can't be configuration property and a method"
152
166
  ) if configurable?(method)
153
167
 
154
- @configuration_methods ||= {}
155
- method_hash = if method.is_a?(Hash)
156
- ingest_configuration_block!(method, &block)
157
- else
158
- { method => block }
159
- end
160
-
161
- @configuration_methods.merge! method_hash
168
+ @configuration_method_blocks ||= Maps::Blocks.new
169
+ @configuration_method_blocks.add(block, [method])
162
170
  end
163
171
 
164
172
  # not_configured defines the behaviour when a property has not been
@@ -181,12 +189,11 @@ module Configurations
181
189
  # end
182
190
  #
183
191
  def not_configured(*properties, &block)
184
- @not_configured ||= {}
192
+ @not_configured_blocks ||= Maps::Blocks.new
193
+ @not_configured_blocks.add(block, properties)
185
194
 
186
195
  if properties.empty?
187
- @not_configured.default_proc = ->(h, k) { h[k] = block }
188
- else
189
- nested_merge_not_configured_hash(*properties, &block)
196
+ @not_configured_blocks.add_default(block)
190
197
  end
191
198
  end
192
199
 
@@ -207,41 +214,10 @@ module Configurations
207
214
  # @return the class name of the configuration class to use
208
215
  #
209
216
  def configuration_type
210
- if @configurable.nil? || @configurable.empty?
211
- Configurations::Arbitrary
212
- else
217
+ if defined?(@configurable_properties) && @configurable_properties && !@configurable_properties.empty?
213
218
  Configurations::Strict
214
- end
215
- end
216
-
217
- # Instantiates a configurable hash from a property and a type
218
- # @param [Symbol, Hash, Array] properties configurable properties,
219
- # either single or nested
220
- # @param [Class] type the type to assert, if any
221
- # @return a hash with configurable values pointing to their types
222
- #
223
- def to_configurable_hash(properties, type, &block)
224
- assertion_hash = {}
225
- assertion_hash.merge! block: block if block_given?
226
- assertion_hash.merge! type: type if type
227
-
228
- zip_to_hash(assertion_hash, *properties)
229
- end
230
-
231
- # Makes all values of hash point to block
232
- # @param [Hash] hash the hash to modify
233
- # @param [Proc] block the block to point to
234
- # @return a hash with all previous values being keys pointing to block
235
- #
236
- def ingest_configuration_block!(hash, &block)
237
- hash.each do |k, v|
238
- value = if v.is_a?(Hash)
239
- ingest_configuration_block!(v, &block)
240
- else
241
- zip_to_hash(block, *Array(v))
242
- end
243
-
244
- hash.merge! k => value
219
+ else
220
+ Configurations::Arbitrary
245
221
  end
246
222
  end
247
223
 
@@ -249,47 +225,19 @@ module Configurations
249
225
  #
250
226
  def configuration_options
251
227
  {
252
- defaults: @configuration_defaults,
253
- methods: @configuration_methods,
254
- configurable: @configurable,
255
- not_configured: @not_configured
256
- }.delete_if { |_, value| value.nil? }
257
- end
258
-
259
- # merges the properties given into a not_configured hash
260
- # @param [Symbol, Hash, Array] properties the properties to merge
261
- # @param [Proc] block the block to point the properties to when
262
- # not configured
263
- #
264
- def nested_merge_not_configured_hash(*properties, &block)
265
- nested = properties.last.is_a?(Hash) ? properties.pop : {}
266
- nested = ingest_configuration_block!(nested, &block)
267
- props = zip_to_hash(block, *properties)
268
-
269
- @not_configured.merge! nested, &method(:configuration_deep_merge)
270
- @not_configured.merge! props, &method(:configuration_deep_merge)
271
- end
272
-
273
- # Solves merge conflicts when merging
274
- # @param [Symbol] key the key that conflicts
275
- # @param [Anything] oldval the value of the left side of the merge
276
- # @param [Anything] newval the value of the right side of the merge
277
- # @return a mergable value with conflicts solved
278
- #
279
- def configuration_deep_merge(_key, oldval, newval)
280
- if oldval.is_a?(Hash) && newval.is_a?(Hash)
281
- oldval.merge(newval, &method(:configuration_deep_merge))
282
- else
283
- Array(oldval) + Array(newval)
284
- end
285
- end
286
-
287
- # Zip a value with keys to a hash so all keys point to the value
288
- # @param [Anything] value the value to point to
289
- # @param [Array] keys the keys to install
290
- #
291
- def zip_to_hash(value, *keys)
292
- Hash[keys.zip([value] * keys.size)]
228
+ defaults:
229
+ defined?(@configuration_defaults) && @configuration_defaults,
230
+ properties:
231
+ defined?(@configurable_properties) && @configurable_properties,
232
+ types:
233
+ defined?(@configurable_types) && @configurable_types,
234
+ blocks:
235
+ defined?(@configurable_blocks) && @configurable_blocks,
236
+ method_blocks:
237
+ defined?(@configuration_method_blocks) && @configuration_method_blocks,
238
+ not_configured_blocks:
239
+ defined?(@not_configured_blocks) && @not_configured_blocks,
240
+ }.keep_if { |_, value| value }
293
241
  end
294
242
  end
295
243
  end
@@ -3,18 +3,6 @@ module Configurations
3
3
  # of various properties including keywords
4
4
  #
5
5
  class Configuration < BlankObject
6
- # Reserved methods are not assignable. They define behaviour needed for
7
- # the configuration object to work properly.
8
- #
9
- RESERVED_METHODS = [
10
- :initialize,
11
- :inspect,
12
- :method_missing,
13
- :object_id,
14
- :singleton_class, # needed by rbx
15
- :to_h,
16
- :to_s # needed by rbx / 1.9.3 for inspect
17
- ]
18
6
 
19
7
  class << self
20
8
  # Make new a private method, but allow __new__ alias. Instantiating
@@ -31,10 +19,16 @@ module Configurations
31
19
  # not_configured properties
32
20
 
33
21
  def initialize(options = {}, &block)
34
- @__methods__ = options.fetch(:methods) { ::Hash.new }
35
- @__not_configured__ = options.fetch(:not_configured) { ::Hash.new }
22
+ @data = Data.new(__configuration_hash__)
23
+ @path = options.fetch(:path) { Path.new }
24
+ @data_map = options.fetch(:data) { Maps::Data.new }
25
+
26
+ @methods = options.fetch(:methods) { ::Hash.new }
27
+ @method_blocks = options.fetch(:method_blocks) { Maps::Blocks.new }
28
+ @not_configured_blocks = options.fetch(:not_configured_blocks) { Maps::Blocks.new }
36
29
 
37
- @data = __configuration_hash__
30
+ @reserved_method_validator = Validators::ReservedMethods.new
31
+ @key_ambiguity_validator = Validators::Ambiguity.new
38
32
 
39
33
  __instance_eval__(&options[:defaults]) if options[:defaults]
40
34
  __instance_eval__(&block) if block
@@ -78,7 +72,8 @@ module Configurations
78
72
  # different values
79
73
  #
80
74
  def from_h(h)
81
- __test_ambiguity!(h)
75
+ @key_ambiguity_validator.validate!(h)
76
+
82
77
  h.each do |property, value|
83
78
  p = property.to_sym
84
79
  if value.is_a?(::Hash) && __nested?(p)
@@ -108,8 +103,12 @@ module Configurations
108
103
  # @param [Symbol] property The property to test for configurability
109
104
  # @return [Boolean] whether the given property is configurable
110
105
  #
111
- def __configurable?(_property)
112
- fail NotImplementedError, 'must be implemented in subclass'
106
+ def __configurable?(property)
107
+ if defined?(@configurable_properties) && @configurable_properties
108
+ @configurable_properties.configurable?(@path.add(property))
109
+ else
110
+ true
111
+ end
113
112
  end
114
113
 
115
114
  # @param [Symbol] property The property to test for
@@ -130,9 +129,10 @@ module Configurations
130
129
  # as singleton methods
131
130
  #
132
131
  def __install_configuration_methods__
133
- @__methods__.each do |meth, block|
134
- __test_reserved!(meth)
135
- __define_singleton_method__(meth, &block) if block.is_a?(::Proc)
132
+ entries = @method_blocks.entries_at(@path)
133
+ entries.each do |meth, entry|
134
+ @reserved_method_validator.validate!(meth)
135
+ __define_singleton_method__(meth, &entry.block)
136
136
  end
137
137
  end
138
138
 
@@ -141,39 +141,17 @@ module Configurations
141
141
  # @return [Hash] a hash to be used for configuration initialization
142
142
  #
143
143
  def __options_hash_for__(property)
144
- hash = {}
145
- hash[:not_configured] =
146
- __not_configured_hash_for__(property) if @__not_configured__[property]
147
- hash[:methods] = @__methods__[property] if @__methods__.key?(property)
148
-
149
- hash
150
- end
151
-
152
- # @param [Symbol] property the property to return the callback for
153
- # @return [Proc] a block to use when property is called before
154
- # configuration, defaults to a block yielding nil
155
- #
156
- def __not_configured_callback_for__(property)
157
- not_configured = @__not_configured__[property] || ::Proc.new { nil }
144
+ nested_path = @path.add(property)
158
145
 
159
- unless not_configured.is_a?(::Proc)
160
- blocks = __collect_blocks__(not_configured)
161
- not_configured = ->(p) { blocks.each { |b| b.call(p) } }
162
- end
146
+ hash = {}
147
+ hash[:path] = nested_path
148
+ hash[:data] = @data_map
149
+ hash[:properties] = defined?(@properties) && @properties
163
150
 
164
- not_configured
165
- end
151
+ hash[:not_configured_blocks] = @not_configured_blocks
166
152
 
167
- # @param [Symbol] property the property to return the not
168
- # configured hash option for
169
- # @return [Hash] a hash which can be used as a not configured
170
- # hash in options
171
- #
172
- def __not_configured_hash_for__(property)
173
- hash = ::Hash.new(&@__not_configured__.default_proc)
174
- hash.merge!(
175
- @__not_configured__[property]
176
- ) if @__not_configured__[property].is_a?(::Hash)
153
+ hash[:method_blocks] = @method_blocks
154
+ hash[:methods] = @methods[property] if @methods.key?(property)
177
155
 
178
156
  hash
179
157
  end
@@ -192,7 +170,7 @@ module Configurations
192
170
  # @param [Any] value the given value
193
171
  #
194
172
  def __assign!(property, value)
195
- __test_reserved!(property)
173
+ @data_map.add_entry(@path.add(property), value)
196
174
  @data[property] = value
197
175
  end
198
176
 
@@ -222,55 +200,5 @@ module Configurations
222
200
  method.to_s[0..-2].to_sym
223
201
  end
224
202
 
225
- # @param [Symbol] method the method to test for reservedness
226
- # @raise [Configurations::ReservedMethodError] raises this error if
227
- # a property is a reserved method.
228
- #
229
- def __test_reserved!(method)
230
- ::Kernel.fail(
231
- ::Configurations::ReservedMethodError,
232
- "#{method} is a reserved method and can not be assigned"
233
- ) if __is_reserved?(method)
234
- end
235
-
236
- # @param [Hash] the hash to test for ambiguity
237
- # @raise [Configurations::ConfigurationError] raises this error if
238
- # a property is defined ambiguously
239
- #
240
- def __test_ambiguity!(h)
241
- symbols, others = h.keys.partition { |k| k.is_a?(::Symbol) }
242
- ambiguous = symbols.map(&:to_s) & others
243
-
244
- unless ambiguous.empty?
245
- ::Kernel.fail(
246
- ::Configurations::ConfigurationError,
247
- "Can not resolve configuration values for #{ambiguous.join(', ')} " \
248
- "defined as both Symbol and #{others.first.class.name} keys. " \
249
- 'Please resolve the ambiguity.'
250
- )
251
- end
252
- end
253
-
254
- # @param [Symbol] method the method to test for
255
- # @return [TrueClass, FalseClass] whether the method is reserved
256
- #
257
- def __is_reserved?(method)
258
- RESERVED_METHODS.include?(method)
259
- end
260
-
261
- # @param [Hash] a hash to collect blocks from
262
- # @return [Proc] a proc to call all the procs
263
- #
264
- def __collect_blocks__(hash)
265
- hash.reduce([]) do |array, (k, v)|
266
- array << if v.is_a?(::Hash)
267
- __collect_blocks__(v)
268
- else
269
- v || k
270
- end
271
-
272
- array
273
- end.flatten
274
- end
275
203
  end
276
204
  end
@@ -0,0 +1,44 @@
1
+ module Configurations
2
+ # Configuration is a blank object in order to allow configuration
3
+ # of various properties including keywords
4
+ #
5
+ class Data
6
+ def initialize(
7
+ data,
8
+ reserved_method_validator = Validators::ReservedMethods.new
9
+ )
10
+ @data = data
11
+ @reserved_method_validator = reserved_method_validator
12
+ end
13
+
14
+ def [](key)
15
+ @data[key]
16
+ end
17
+
18
+ def []=(key, value)
19
+ @reserved_method_validator.validate!(key)
20
+
21
+ @data[key] = value
22
+ end
23
+
24
+ def key?(key)
25
+ @data.key?(key)
26
+ end
27
+
28
+ def fetch(key, &block)
29
+ @data.fetch(key, &block)
30
+ end
31
+
32
+ def each(&block)
33
+ @data.each(&block)
34
+ end
35
+
36
+ def reduce(acc, &block)
37
+ @data.reduce(acc, &block)
38
+ end
39
+
40
+ def inspect
41
+ @data.inspect
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'maps/blocks'
2
+ require_relative 'maps/data'
3
+ require_relative 'maps/types'
4
+ require_relative 'maps/properties'
5
+ require_relative 'maps/readers'
6
+ require_relative 'maps/writers'
@@ -0,0 +1,62 @@
1
+ module Configurations
2
+ module Maps
3
+ class Blocks
4
+ class Entry
5
+ attr_reader :block
6
+
7
+ def initialize(block)
8
+ @block = block
9
+ end
10
+
11
+ def evaluate!(value)
12
+ return value unless @block
13
+ block.call(value)
14
+ end
15
+ end
16
+
17
+ def initialize(reader = Readers::Tolerant.new)
18
+ @map = {}
19
+ @reader = reader
20
+ @default = nil
21
+ end
22
+
23
+ def add_default(block)
24
+ @default = Entry.new(block)
25
+ end
26
+
27
+ def add(block, properties)
28
+ properties.each do |property|
29
+ add_entry(property, block, @map)
30
+ end
31
+ end
32
+
33
+ def entries_at(path)
34
+ entries = @reader.read(@map, path) || {}
35
+ entries.dup.keep_if { |_, v| v.is_a?(Entry) }
36
+ end
37
+
38
+ def evaluate!(path, value)
39
+ entry = @reader.read(@map, path) || @default
40
+ return unless entry
41
+
42
+ entry.evaluate!(value)
43
+ end
44
+
45
+ def add_entry(property, block, subtree)
46
+ if property.is_a?(Hash)
47
+ property.each do |key, val|
48
+ subtree[key] = add_entry(val, block, subtree.fetch(key, {}))
49
+ end
50
+ elsif property.is_a?(Array)
51
+ property.each do |val|
52
+ add_entry(val, block, subtree)
53
+ end
54
+ else
55
+ subtree[property] = Entry.new(block)
56
+ end
57
+
58
+ subtree
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,30 @@
1
+ module Configurations
2
+ module Maps
3
+ class Data
4
+ class Entry
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+ end
9
+
10
+ def initialize(
11
+ reader = Readers::Tolerant.new,
12
+ writer = Writers::Default.new { |value|
13
+ Entry.new(value)
14
+ }
15
+ )
16
+ @map = {}
17
+ @reader = reader
18
+ @writer = writer
19
+ end
20
+
21
+ def nested?(path)
22
+ @reader.read(@map, path)
23
+ end
24
+
25
+ def add_entry(path, value)
26
+ @writer.write(@map, path, value)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ module Configurations
2
+ module Maps
3
+ class Properties
4
+ attr_reader :map
5
+ class Entry
6
+ end
7
+
8
+ def initialize(reader = Readers::Tolerant.new)
9
+ @map = {}
10
+ @reader = reader
11
+ end
12
+
13
+ def empty?
14
+ @map.empty?
15
+ end
16
+
17
+ def add(properties)
18
+ properties.each do |property|
19
+ add_entry(property, @map)
20
+ end
21
+ end
22
+
23
+ def entries_at(path)
24
+ @reader.read(@map, path) || {}
25
+ end
26
+
27
+ def configurable?(path)
28
+ !!@reader.read(@map, path)
29
+ end
30
+
31
+ def add_entry(property, subtree)
32
+ if property.is_a?(Hash)
33
+ property.each do |key, val|
34
+ subtree[key] = add_entry(val, subtree.fetch(key, {}))
35
+ end
36
+ elsif property.is_a?(Array)
37
+ property.each do |val|
38
+ add_entry(val, subtree)
39
+ end
40
+ else
41
+ subtree[property] = Entry.new
42
+ end
43
+
44
+ subtree
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1 @@
1
+ require_relative 'readers/tolerant'
@@ -0,0 +1,13 @@
1
+ module Configurations
2
+ module Maps
3
+ module Readers
4
+ class Tolerant
5
+ def read(map, path)
6
+ path.reduce(map) do |map, value|
7
+ map[value] if map
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ module Configurations
2
+ module Maps
3
+ class Types
4
+ attr_reader :map
5
+ class Entry
6
+ attr_reader :type
7
+
8
+ def initialize(type)
9
+ @type = type
10
+ end
11
+
12
+ def valid?(value)
13
+ !@type || value.is_a?(@type)
14
+ end
15
+ end
16
+
17
+ def initialize(reader = Readers::Tolerant.new)
18
+ @map = {}
19
+ @reader = reader
20
+ end
21
+
22
+ def add(type, properties)
23
+ properties.each do |property|
24
+ add_entry(property, type, @map)
25
+ end
26
+ end
27
+
28
+ def test!(path, value)
29
+ entry = @reader.read(@map, path)
30
+ return unless entry
31
+
32
+ fail(
33
+ ConfigurationError,
34
+ "#{path.print} must be configured with #{entry.type} (got #{value})",
35
+ caller
36
+ ) unless entry.valid?(value)
37
+ end
38
+
39
+ def add_entry(property, type, subtree)
40
+ if property.is_a?(Hash)
41
+ property.each do |key, val|
42
+ subtree[key] = add_entry(val, type, subtree.fetch(key, {}))
43
+ end
44
+ elsif property.is_a?(Array)
45
+ property.each do |val|
46
+ add_entry(val, type, subtree)
47
+ end
48
+ else
49
+ subtree[property] = Entry.new(type)
50
+ end
51
+
52
+ subtree
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1 @@
1
+ require_relative 'writers/default'
@@ -0,0 +1,17 @@
1
+ module Configurations
2
+ module Maps
3
+ module Writers
4
+ class Default
5
+
6
+ def initialize(&block)
7
+ @entry_block = block
8
+ end
9
+
10
+ def write(map, path, value)
11
+ map[path.to_s] = @entry_block.call(value)
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ module Configurations
2
+ class Path
3
+ def initialize(path = [])
4
+ @path = path
5
+ end
6
+
7
+ def add(*path)
8
+ Path.new(@path + path)
9
+ end
10
+
11
+ def reduce(initial, &block)
12
+ @path.reduce(initial, &block)
13
+ end
14
+
15
+ def to_s
16
+ @path.join(".")
17
+ end
18
+ alias :inspect :to_s
19
+ alias :print :to_s
20
+
21
+ end
22
+ end
@@ -16,65 +16,43 @@ module Configurations
16
16
  # @return [HostModule::Configuration] a configuration
17
17
  #
18
18
  def initialize(options = {}, &block)
19
- @__configurable__ = options.fetch(:configurable)
19
+ @reserved_method_validator = Validators::ReservedMethods.new
20
+
21
+ @path = options.fetch(:path) { Path.new }
22
+ @properties = options.fetch(:properties) { Maps::Properties.new }
23
+ @types = options.fetch(:types)
24
+ @blocks = options.fetch(:blocks)
25
+
20
26
  __evaluate_configurable!
21
27
 
22
28
  super
23
29
  end
24
30
 
25
- # @param [Symbol] property The property to test for configurability
26
- # @return [Boolean] whether the given property is configurable
27
- #
28
- def __configurable?(property)
29
- @__configurable__.key?(property) ||
30
- @__nested_configurables__.key?(property)
31
- end
32
-
33
31
  private
34
32
 
35
33
  # Evaluates configurable properties and passes eventual hashes
36
34
  # down to subconfigurations
37
35
  #
38
36
  def __evaluate_configurable!
39
- @__configurable__.each do |k, assertion|
40
- if k.is_a?(::Hash)
41
- k.each do |property, nested|
42
- __add_to_nested_configurables!(property, nested, assertion)
43
- __install_nested_getter__(property)
44
- end
37
+ entries = @properties.entries_at(@path)
38
+ entries.each do |property, value|
39
+ if value.is_a?(Maps::Properties::Entry)
40
+ __install_property__(property)
45
41
  else
46
- __install_property__(k)
42
+ __install_nested_getter__(property)
47
43
  end
48
44
  end
49
45
  end
50
46
 
51
- # Add a property to a nested configurable
52
- #
53
- def __add_to_nested_configurables!(property, nested, assertion)
54
- @__nested_configurables__ ||= ::Hash.new { |h, k| h[k] = {} }
55
- @__nested_configurables__[property].merge!(
56
- __configurable_hash__(property, nested, assertion)
57
- )
58
- end
59
-
60
47
  # Get an options hash for a property
61
48
  #
62
49
  def __options_hash_for__(property)
63
- super(property).merge(configurable: @__nested_configurables__[property])
64
- end
65
-
66
- # @param [Symbol, Hash, Array] property configurable properties,
67
- # either single or nested
68
- # @param [Symbol, Hash, Array] value configurable properties,
69
- # either single or nested
70
- # @param [Hash] assertion assertion if any
71
- # @return a hash with configurable values pointing to their types
72
- #
73
- def __configurable_hash__(_property, value, assertion)
74
- value = [value] unless value.is_a?(::Array)
75
- hash = ::Hash[value.zip([assertion].flatten * value.size)]
76
-
77
- hash
50
+ _nested_path = @path.add(property)
51
+ super(property).merge(
52
+ properties: @properties,
53
+ types: @types,
54
+ blocks: @blocks
55
+ )
78
56
  end
79
57
 
80
58
  # @param [Symbol] property the property to test for
@@ -89,7 +67,7 @@ module Configurations
89
67
  # @param [Symbol] property the property to install
90
68
  #
91
69
  def __install_property__(property)
92
- __test_reserved!(property)
70
+ @reserved_method_validator.validate!(property)
93
71
  __install_setter__(property)
94
72
  __install_getter__(property)
95
73
  end
@@ -108,7 +86,9 @@ module Configurations
108
86
  #
109
87
  def __install_getter__(property)
110
88
  __define_singleton_method__ property do
111
- @data.fetch(property, &__not_configured_callback_for__(property))
89
+ @data.fetch(property) do
90
+ @not_configured_blocks.evaluate!(@path.add(property), property)
91
+ end
112
92
  end
113
93
  end
114
94
 
@@ -127,49 +107,11 @@ module Configurations
127
107
  # @param [Any] value the given value
128
108
  #
129
109
  def __assign!(property, value)
130
- __assert_type!(property, value)
131
- v = __evaluate_block!(property, value)
110
+ @types.test!(@path.add(property), value)
111
+ v = @blocks.evaluate!(@path.add(property), value)
112
+
132
113
  value = v unless v.nil?
133
114
  super(property, value)
134
115
  end
135
-
136
- # Type assertion for configurable properties
137
- # @param [Symbol] property the property to type test
138
- # @param [Any] value the given value
139
- # @raise [ConfigurationError] if the given value has the wrong type
140
- #
141
- def __assert_type!(property, value)
142
- return unless __evaluable?(property, :type)
143
-
144
- assertion = @__configurable__[property][:type]
145
- return if value.is_a?(assertion)
146
-
147
- ::Kernel.fail(
148
- ConfigurationError,
149
- "#{property} must be configured with #{assertion} (got #{value.class})",
150
- caller
151
- )
152
- end
153
-
154
- # Block assertion for configurable properties
155
- # @param [Symbol] property the property to type test
156
- # @param [Any] value the given value
157
- #
158
- def __evaluate_block!(property, value)
159
- return value unless __evaluable?(property, :block)
160
-
161
- evaluation = @__configurable__[property][:block]
162
- evaluation.call(value)
163
- end
164
-
165
- # @param [Symbol] property The property to test for
166
- # @param [Symbol] assertion_type The evaluation type type to test for
167
- # @return [Boolean] whether the given property is assertable
168
- #
169
- def __evaluable?(property, evaluation)
170
- __configurable?(property) &&
171
- @__configurable__[property].is_a?(::Hash) &&
172
- @__configurable__[property].key?(evaluation)
173
- end
174
116
  end
175
117
  end
@@ -0,0 +1,2 @@
1
+ require_relative 'validators/ambiguity'
2
+ require_relative 'validators/reserved_methods'
@@ -0,0 +1,26 @@
1
+ module Configurations
2
+ module Validators
3
+ class Ambiguity
4
+ # @param [Hash] the hash to test for ambiguity
5
+ # @raise [Configurations::ConfigurationError] raises this error if
6
+ # a property is defined ambiguously
7
+ #
8
+ def validate!(h)
9
+ symbols, others = h.keys.partition { |k|
10
+ k.is_a?(::Symbol)
11
+ }
12
+
13
+ ambiguous = symbols.map(&:to_s) & others
14
+
15
+ unless ambiguous.empty?
16
+ ::Kernel.fail(
17
+ ::Configurations::ConfigurationError,
18
+ "Can not resolve configuration values for #{ambiguous.join(', ')} " \
19
+ "defined as both Symbol and #{others.first.class.name} keys. " \
20
+ 'Please resolve the ambiguity.'
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ module Configurations
2
+ module Validators
3
+ class ReservedMethods
4
+ # @param [Symbol] method the method to test for reservedness
5
+ # @raise [Configurations::ReservedMethodError] raises this error if
6
+ # a property is a reserved method.
7
+ #
8
+ def validate!(method)
9
+ ::Kernel.fail(
10
+ ::Configurations::ReservedMethodError,
11
+ "#{method} is a reserved method and can not be assigned"
12
+ ) if reserved?(method)
13
+ end
14
+
15
+ private
16
+
17
+ # Reserved methods are not assignable. They define behaviour needed for
18
+ # the configuration object to work properly.
19
+ #
20
+ RESERVED_METHODS = [
21
+ :initialize,
22
+ :inspect,
23
+ :method_missing,
24
+ :object_id,
25
+ :singleton_class, # needed by rbx
26
+ :to_h,
27
+ :to_s # needed by rbx / 1.9.3 for inspect
28
+ ]
29
+
30
+ # @param [Symbol] method the method to test for
31
+ # @return [TrueClass, FalseClass] whether the method is reserved
32
+ #
33
+ def reserved?(method)
34
+ RESERVED_METHODS.include?(method)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -1,50 +1,36 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class TestConfigurationSynchronized < MiniTest::Test
4
- module TestModule
4
+ module TestModuleA
5
5
  include Configurations
6
6
 
7
7
  configuration_defaults do |c|
8
- c.a = 'b'
8
+ c.a = -1
9
9
  end
10
10
  end
11
11
 
12
- def test_configuration_synchronized
13
- with_gc_disabled do
14
- ids = []
15
- threads = 100.times.map do |i|
16
- Thread.new do
17
- sleep rand(1000) / 1000.0
18
- ids << TestModule.configure do |c|
19
- c.a = i
20
- end.a
21
- end
22
- end
23
- threads.each(&:join)
12
+ module TestModuleB
13
+ include Configurations
24
14
 
25
- assert_equal 100, ids.uniq.size
15
+ configuration_defaults do |c|
16
+ c.a = -1
26
17
  end
27
18
  end
28
19
 
29
- def test_one_instance_mutation
30
- there = TestModule.configuration.a
31
- t = Thread.new do
32
- TestModule.configure do |c|
33
- c.a = 'c'
20
+ def test_configuration_synchronized
21
+ collector = []
22
+ semaphore = Mutex.new
23
+ threads = 100.times.map do |i|
24
+ Thread.new do
25
+ sleep i%50 / 1000.0
26
+ collector << TestModuleA.configure do |c|
27
+ c.a = i
28
+ end.a
34
29
  end
35
-
36
- there = TestModule.configuration.a
37
30
  end
31
+ threads.each(&:join)
38
32
 
39
- t.join
40
- here = TestModule.configuration.a
41
-
42
- assert_equal here, there
33
+ assert_equal 100, collector.uniq.size
43
34
  end
44
35
 
45
- def with_gc_disabled(&_block)
46
- GC.disable
47
- yield
48
- GC.enable
49
- end
50
36
  end
@@ -49,9 +49,9 @@ module Tests
49
49
  end
50
50
 
51
51
  def test_from_h_with_unambiguous_strings_and_symbols
52
- c = @module.configure { |c| c.from_h('p1' => 'bla', :p2 => 2) }
53
- assert_equal 2, c.p2
54
- assert_equal 'bla', c.p1
52
+ config = @module.configure { |c| c.from_h('p1' => 'bla', :p2 => 2) }
53
+ assert_equal 2, config.p2
54
+ assert_equal 'bla', config.p1
55
55
  end
56
56
  end
57
57
  end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
3
+
4
+ rake test
@@ -19,4 +19,5 @@ end
19
19
 
20
20
  require 'minitest/autorun'
21
21
  require 'minitest/pride'
22
+ require 'minitest/focus'
22
23
  require 'test/unit/assertions'
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
3
+
4
+ while sleep 1; do
5
+ {
6
+ find $DIR -name "*.rb";
7
+ find $DIR/../lib -name "*.rb";
8
+ } | entr -cdr $DIR/run
9
+ done
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configurations
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Beat Richartz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-11 00:00:00.000000000 Z
11
+ date: 2016-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -17,13 +17,41 @@ dependencies:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5.4'
20
- type: :development
21
20
  prerelease: false
21
+ type: :development
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest-focus
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ prerelease: false
35
+ type: :development
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ prerelease: false
49
+ type: :development
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: yard
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -31,8 +59,8 @@ dependencies:
31
59
  - - "~>"
32
60
  - !ruby/object:Gem::Version
33
61
  version: '0.8'
34
- type: :development
35
62
  prerelease: false
63
+ type: :development
36
64
  version_requirements: !ruby/object:Gem::Requirement
37
65
  requirements:
38
66
  - - "~>"
@@ -45,8 +73,8 @@ dependencies:
45
73
  - - "~>"
46
74
  - !ruby/object:Gem::Version
47
75
  version: '10'
48
- type: :development
49
76
  prerelease: false
77
+ type: :development
50
78
  version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
80
  - - "~>"
@@ -72,8 +100,22 @@ files:
72
100
  - lib/configurations/blank_object.rb
73
101
  - lib/configurations/configurable.rb
74
102
  - lib/configurations/configuration.rb
103
+ - lib/configurations/data.rb
75
104
  - lib/configurations/error.rb
105
+ - lib/configurations/maps.rb
106
+ - lib/configurations/maps/blocks.rb
107
+ - lib/configurations/maps/data.rb
108
+ - lib/configurations/maps/properties.rb
109
+ - lib/configurations/maps/readers.rb
110
+ - lib/configurations/maps/readers/tolerant.rb
111
+ - lib/configurations/maps/types.rb
112
+ - lib/configurations/maps/writers.rb
113
+ - lib/configurations/maps/writers/default.rb
114
+ - lib/configurations/path.rb
76
115
  - lib/configurations/strict.rb
116
+ - lib/configurations/validators.rb
117
+ - lib/configurations/validators/ambiguity.rb
118
+ - lib/configurations/validators/reserved_methods.rb
77
119
  - test/configurations/arbitrary/test.rb
78
120
  - test/configurations/arbitrary/test_defaults.rb
79
121
  - test/configurations/arbitrary/test_hash_methods.rb
@@ -121,9 +163,11 @@ files:
121
163
  - test/configurations/strict_with_blocks/test_methods.rb
122
164
  - test/configurations/strict_with_blocks/test_not_configured.rb
123
165
  - test/configurations/strict_with_blocks/test_not_configured_default.rb
166
+ - test/run
124
167
  - test/support/setup.rb
125
168
  - test/support/shared.rb
126
169
  - test/test_helper.rb
170
+ - test/watch
127
171
  homepage: http://github.com/beatrichartz/configurations
128
172
  licenses:
129
173
  - MIT
@@ -144,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
188
  version: '0'
145
189
  requirements: []
146
190
  rubyforge_project:
147
- rubygems_version: 2.4.5
191
+ rubygems_version: 2.5.1
148
192
  signing_key:
149
193
  specification_version: 4
150
194
  summary: Configurations with a configure block from arbitrary to type-restricted for
@@ -197,7 +241,9 @@ test_files:
197
241
  - test/configurations/strict_with_blocks/test_methods.rb
198
242
  - test/configurations/strict_with_blocks/test_not_configured.rb
199
243
  - test/configurations/strict_with_blocks/test_not_configured_default.rb
244
+ - test/run
200
245
  - test/support/setup.rb
201
246
  - test/support/shared.rb
202
247
  - test/test_helper.rb
248
+ - test/watch
203
249
  has_rdoc: