configurations 2.2.0 → 2.2.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
  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: