configurations 2.0.0 → 2.2.0

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: f9907568167e40b9014fea6fd1999980874e83a8
4
- data.tar.gz: 848cd43a6bb4e9c41cf9aa6ff21d1b3966fb7238
3
+ metadata.gz: a0d503a4163d4238d71c61d2349a78e5f74b59be
4
+ data.tar.gz: 21343c5d0ff8b12c948caced4f73bb74a04d2083
5
5
  SHA512:
6
- metadata.gz: a60a00a58aa9af53a443fa2781138c734248ac77b5c2f55724cb330eca8a173636445657e8b7f23a4e907fd319cd9eb651a26ad2216c9d80d9d98501f4e7e646
7
- data.tar.gz: 8b0a6f40548e80e3ba7f21eb6674980b2877ff9f270ed02e0c8aadedea532c350270895ccf12108302c74a076967b14f84d5796669e87083e451b1e45ffaa9a8
6
+ metadata.gz: 71026df8af3278a819d01c1ce0729a27d86ef8a83845596a4301730eee4fb585afeb1ca0f6681c23ac68a1fc74237880fb332bb720d7b6cffb9daecac54f445f
7
+ data.tar.gz: 354aea5c774097ef557c2ce645f55c124fe2f5f0684fb0c01e205741c8afc8a2134aa00a0994528303c2e9f44d20c4d17387148f1004871ba19d8877d6d2a9be
@@ -6,10 +6,11 @@ rvm:
6
6
  - 1.9.2
7
7
  - 1.9.3
8
8
  - 2.0.0
9
- - 2.1.5
10
- - 2.2.0
9
+ - 2.1.6
10
+ - 2.2.2
11
11
  - rbx
12
12
  - jruby-19mode
13
13
  - jruby-20mode
14
+ - jruby-head
14
15
 
15
16
  script: CODECLIMATE_REPO_TOKEN=36cf84c73264d3c361003f66903eec8aa5fb2b3494496f3a9676630518ecc9f9 rake
data/README.md CHANGED
@@ -6,17 +6,21 @@ Configurations provides a unified approach to do configurations using the `MyGem
6
6
 
7
7
  ## Install
8
8
 
9
- `gem install configurations`
9
+ ```ruby
10
+ gem install configurations
11
+ ```
10
12
 
11
13
  or with Bundler
12
14
 
13
- `gem 'configurations', '~> 2.0.0'`
15
+ ```ruby
16
+ gem 'configurations', '~> 2.2.0'
17
+ ```
14
18
 
15
19
  Configurations uses [Semver 2.0](http://semver.org/)
16
20
 
17
21
  ## Compatibility
18
22
 
19
- Compatible with MRI 1.9.2 - 2.2, Rubinius 2.2, jRuby 1.7 and 9K
23
+ Compatible with MRI 1.9.2 - 2.2, Rubinius 2.x, jRuby 1.7 and 9K
20
24
 
21
25
  ## Why?
22
26
 
@@ -32,7 +36,7 @@ Less time copy pasting configuration code, more time writing exciting code for y
32
36
 
33
37
  Go boom! with ease. This allows your gem / code users to set any value they like.
34
38
 
35
- ```
39
+ ```ruby
36
40
  module MyGem
37
41
  include Configurations
38
42
  end
@@ -40,7 +44,7 @@ end
40
44
 
41
45
  Gives your users:
42
46
 
43
- ```
47
+ ```ruby
44
48
  MyGem.configure do |c|
45
49
  c.foo.bar.baz = 'fizz'
46
50
  c.hi = 'Hello-o'
@@ -50,20 +54,20 @@ end
50
54
 
51
55
  Gives you:
52
56
 
53
- ```
57
+ ```ruby
54
58
  MyGem.configuration.class #=> 'oooh wow'
55
59
  MyGem.configuration.foo.bar.baz #=> 'fizz'
56
60
  ```
57
61
 
58
62
  Undefined properties on an arbitrary configuration will return `nil`
59
63
 
60
- ```
64
+ ```ruby
61
65
  MyGem.configuration.not_set #=> nil
62
66
  ```
63
67
 
64
68
  If you want to define the behaviour for not set properties yourself, use `not_configured`. You can either define a catch-all `not_configured` which will be executed whenever you call a value that has not been configured and has no default:
65
69
 
66
- ```
70
+ ```ruby
67
71
  module MyGem
68
72
  not_configured do |prop|
69
73
  raise NoMethodError, "#{prop} must be configured"
@@ -73,7 +77,7 @@ end
73
77
 
74
78
  Or you can define finer-grained callbacks:
75
79
 
76
- ```
80
+ ```ruby
77
81
  module MyGem
78
82
  not_configured my: { nested: :prop } do |prop|
79
83
  raise NoMethodError, "#{prop} must be configured"
@@ -85,7 +89,7 @@ end
85
89
 
86
90
  If you just want some properties to be configurable, consider this option
87
91
 
88
- ```
92
+ ```ruby
89
93
  module MyGem
90
94
  include Configurations
91
95
  configurable :foo, bar: :baz, biz: %i(bi ba bu)
@@ -94,7 +98,7 @@ end
94
98
 
95
99
  Gives your users:
96
100
 
97
- ```
101
+ ```ruby
98
102
  MyGem.configure do |c|
99
103
  c.foo = 'FOO'
100
104
  c.bar.baz = 'FIZZ'
@@ -108,23 +112,23 @@ end
108
112
 
109
113
  Gives you:
110
114
 
111
- ```
115
+ ```ruby
112
116
  MyGem.configuration.foo #=> 'FOO'
113
117
  MyGem.configuration.bar.baz #=> 'FIZZ'
114
118
  ```
115
119
 
116
120
  Not configured properties on a restricted configuration will raise `NoMethodError`
117
121
 
118
- ```
122
+ ```ruby
119
123
  MyGem.configuration.not_set #=> <#NoMethodError>
120
124
  ```
121
125
 
122
126
  If you want to define the behaviour for not set properties yourself, use `not_configured`. This will only affect properties set to configurable. All not configurable properties will raise `NoMethodError`.
123
127
 
124
- ```
128
+ ```ruby
125
129
  module MyGem
126
130
  not_configured :awesome, :nice do |prop| # omit the arguments to get a catch-all not_configured
127
- warn :not_configured, "Please configure #{prop} or live in danger"
131
+ warn :not_configured, "Please configure #{prop} or live in danger: youtube.com/watch?v=yZ15vCGuvH0"
128
132
  end
129
133
  end
130
134
  ```
@@ -133,7 +137,7 @@ end
133
137
 
134
138
  If you want to make sure your configurations only accept one type, consider this option
135
139
 
136
- ```
140
+ ```ruby
137
141
  module MyGem
138
142
  include Configurations
139
143
  configurable String, :foo
@@ -143,7 +147,7 @@ end
143
147
 
144
148
  Gives your users:
145
149
 
146
- ```
150
+ ```ruby
147
151
  MyGem.configure do |c|
148
152
  c.foo = 'FOO'
149
153
  c.bar.baz = %w(hello)
@@ -158,7 +162,7 @@ end
158
162
 
159
163
  If you need further assertions or you need to change a value before it gets stored in the configuration, consider passing a block
160
164
 
161
- ```
165
+ ```ruby
162
166
  module MyGem
163
167
  include Configurations
164
168
  configurable :foo do |value|
@@ -181,7 +185,7 @@ end
181
185
 
182
186
  Gives your users:
183
187
 
184
- ```
188
+ ```ruby
185
189
  MyGem.configure do |c|
186
190
  c.foo = 'FOO'
187
191
  c.bar.baz = %w(bi)
@@ -193,7 +197,7 @@ end
193
197
 
194
198
  Gives you:
195
199
 
196
- ```
200
+ ```ruby
197
201
  MyGem.configuration.foo #=> 'FOO ooooh my'
198
202
  MyGem.configuration.bar.baz #=> one of %w(bi ba bu)
199
203
  ```
@@ -203,7 +207,7 @@ MyGem.configuration.bar.baz #=> one of %w(bi ba bu)
203
207
  You might want to define methods on your configuration which use configuration values to bring out another value.
204
208
  This is what `configuration_method` is here to help you with:
205
209
 
206
- ```
210
+ ```ruby
207
211
  module MyGem
208
212
  include Configurations
209
213
  configurable :foo, :bar
@@ -215,7 +219,7 @@ end
215
219
 
216
220
  Your users do:
217
221
 
218
- ```
222
+ ```ruby
219
223
  MyGem.configure do |c|
220
224
  c.foo = 'FOO'
221
225
  c.bar = 'BAR'
@@ -224,13 +228,13 @@ end
224
228
 
225
229
  You get:
226
230
 
227
- ```
231
+ ```ruby
228
232
  MyGem.configuration.foobar('ARG') #=> 'FOOBARARG'
229
233
  ```
230
234
 
231
235
  configuration methods can also be installed on nested properties using hashes:
232
236
 
233
- ```
237
+ ```ruby
234
238
  configuration_method foo: :bar do |arg|
235
239
  foo + bar + arg
236
240
  end
@@ -238,7 +242,7 @@ end
238
242
 
239
243
  ### Defaults:
240
244
 
241
- ```
245
+ ```ruby
242
246
  module MyGem
243
247
  include Configurations
244
248
  configuration_defaults do |c|
@@ -249,7 +253,7 @@ end
249
253
 
250
254
  ### Get a hash if you need it
251
255
 
252
- ```
256
+ ```ruby
253
257
  MyGem.configuration.to_h #=> a Hash
254
258
  ```
255
259
 
@@ -257,7 +261,7 @@ MyGem.configuration.to_h #=> a Hash
257
261
 
258
262
  Sometimes your users will have a hash of configuration values which are not handy to press into the block form. In that case, they can use `from_h` inside the `configure` block to either read in the full or a nested configuration. With a everything besides arbitrary configurations, `from_h` can also be used outside the block.
259
263
 
260
- ```
264
+ ```ruby
261
265
  yaml_hash = YAML.load_file('configuration.yml')
262
266
 
263
267
  MyGem.configure do |c|
@@ -268,8 +272,23 @@ end
268
272
 
269
273
  ### Some caveats
270
274
 
271
- The `to_h` from above is along with `method_missing`, `object_id` and `initialize` and `singleton_class` the only purposely defined API method which you can not overwrite with a configuration value.
272
- Apart from these methods, you should be able to set pretty much any property name you like. `Configuration` inherits from `BasicObject`, so even `Kernel` and `Object` method names are available.
275
+ #### Reserved Methods
276
+ These are reserved methods on the configuration instance and should not be defined:
277
+ - `initialize`
278
+ - `inspect`
279
+ - `method_missing`
280
+ - `object_id`
281
+ - `singleton_class`
282
+ - `to_h`
283
+ - `to_s`
284
+
285
+ `Configuration` inherits from `BasicObject`, so method names defined through `Kernel` and `Object` are available.
286
+
287
+ ## Thread safety
288
+ Configuration is synchronized. Re-configuration via the `configure` block switches out the configuration in place rather than mutating its properties, so don't hold on to configuration objects in another context.
289
+ That said, please bear in mind that keeping mutable state in configurations is as bad an idea as every other kind of global mutable state, if you expect values to change at runtime, configurations are not the right place to keep them:
290
+
291
+ Encourage your users to configure once when initializing the environment, reconfigure on reload, but never ever at runtime.
273
292
 
274
293
  ## Contributing
275
294
 
@@ -16,5 +16,5 @@ module Configurations
16
16
 
17
17
  # Version number of Configurations
18
18
  #
19
- VERSION = '2.0.0'
19
+ VERSION = '2.2.0'
20
20
  end
@@ -3,7 +3,7 @@ module Configurations
3
3
  # Configuration is a blank object in order to allow configuration of
4
4
  # various properties including keywords
5
5
  #
6
- class ArbitraryConfiguration < Configuration
6
+ module Arbitrary
7
7
  # Initialize a new configuration
8
8
  # @param [Hash] options The options to initialize a configuration with
9
9
  # @option options [Hash] methods a hash of method names pointing to procs
@@ -50,7 +50,8 @@ module Configurations
50
50
  # @return [Configuration] the configuration with values assigned
51
51
  # @note can only be accessed during writeable state (in configure block).
52
52
  # Unassignable values are ignored
53
- # @raise [ArgumentError] unless used in writeable state (in configure block)
53
+ # @raise [ArgumentError] unless used in writeable state
54
+ # (in configure block)
54
55
  #
55
56
  def from_h(h)
56
57
  unless @__writeable__
@@ -70,8 +71,8 @@ module Configurations
70
71
  # Set the configuration to writeable or read only. Access to writer methods
71
72
  # is only allowed within the configure block, this method is used to invoke
72
73
  # writeability for subconfigurations.
73
- # @param [Boolean] data true if the configuration should be writeable, false
74
- # otherwise
74
+ # @param [Boolean] data true if the configuration should be writeable,
75
+ # false otherwise
75
76
  #
76
77
  def __writeable__=(data)
77
78
  @__writeable__ = data
@@ -99,15 +100,16 @@ module Configurations
99
100
  end
100
101
 
101
102
  # @param [Symbol] method the method to test for
102
- # @return [Boolean] whether the configuration responds to the given property
103
- # as a method during writeable state
103
+ # @return [Boolean] whether the configuration responds to the given
104
+ # property as a method during writeable state
104
105
  #
105
106
  def __respond_to_method_for_write?(method)
106
107
  !__is_writer?(method) && @__writeable__ && @data[method].is_a?(__class__)
107
108
  end
108
109
 
109
110
  # @param [Symbol] method the method to test for
110
- # @return [Boolean] whether the configuration responds to the given property
111
+ # @return [Boolean] whether the configuration responds to the
112
+ # given property
111
113
  #
112
114
  def __respond_to_method_for_read?(method, *args, &block)
113
115
  !__is_writer?(method) && args.empty? && block.nil?
@@ -8,6 +8,7 @@ module Configurations
8
8
  :equal?,
9
9
  :object_id,
10
10
  :__id__,
11
+ :__instance_variables__,
11
12
  :__send__,
12
13
  :method_missing
13
14
  ].freeze
@@ -26,11 +27,12 @@ module Configurations
26
27
  :respond_to?,
27
28
  :is_a?,
28
29
  :inspect,
30
+ :to_s,
29
31
  :object_id,
30
32
  # rbx needs the singleton class to access singleton methods
31
33
  :singleton_class,
32
34
  *ALIAS_KERNEL_METHODS.keys
33
- ].freeze
35
+ ].compact.freeze
34
36
 
35
37
  # Undefines every instance method except the kept methods
36
38
  #
@@ -1,3 +1,5 @@
1
+ require 'thread'
2
+
1
3
  module Configurations
2
4
  # Module configurable provides the API of configurations
3
5
  #
@@ -9,16 +11,32 @@ module Configurations
9
11
  #
10
12
  def included(base)
11
13
  install_configure_in(base)
12
- base.class_eval do
14
+ base.instance_eval do
13
15
  extend ClassMethods
16
+
17
+ # call configuration_mutex once to initialize the value
18
+ #
19
+ initialize_configuration!
14
20
  end
15
21
  end
16
22
 
23
+ def underscore_camelized(string)
24
+ string.gsub(/::/, '/')
25
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
26
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
27
+ .tr('-', '_')
28
+ .downcase
29
+ end
30
+
17
31
  # Installs #configure in base, and makes sure that it will instantiate
18
32
  # configuration as a subclass of the host module
19
33
  #
20
34
  def install_configure_in(base)
21
- base.class_eval <<-EOF
35
+ base.instance_eval <<-EOF
36
+ # Configuration class for host module
37
+ #
38
+ #{base.name}::Configuration = Class.new(Configurations::Configuration)
39
+
22
40
  # The central configure method
23
41
  # @params [Proc] block the block to configure host module with
24
42
  # @raise [ArgumentError] error when not given a block
@@ -28,24 +46,50 @@ module Configurations
28
46
  # end
29
47
  #
30
48
  def self.configure(&block)
31
- fail ArgumentError, "configure needs a block" unless block_given?
32
- @configuration = #{base.name}.const_get(configuration_type).new(
33
- configuration_options,
34
- &block
35
- )
49
+ semaphore.synchronize do
50
+ fail ArgumentError, "configure needs a block" unless block_given?
51
+ include_configuration_type!(#{base.name}::Configuration)
52
+
53
+ set_configuration!(&block)
54
+ end
55
+ end
56
+
57
+ # A reader for Configuration
58
+ #
59
+ def configuration
60
+ semaphore.synchronize do
61
+ return @configuration if @configuration
62
+
63
+ if @configuration_defaults
64
+ include_configuration_type!(#{base.name}::Configuration)
65
+ set_configuration! { }
66
+ end
67
+ end
68
+ end
69
+
70
+
71
+ private
72
+
73
+ # Sets the configuration instance variable
74
+ #
75
+ def self.set_configuration!(&block)
76
+ @configuration = #{base.name}::Configuration.__new__(
77
+ configuration_options,
78
+ &block
79
+ )
36
80
  end
81
+
82
+ @semaphore = Mutex.new
83
+ def self.semaphore
84
+ @semaphore
85
+ end
86
+
37
87
  EOF
38
88
  end
39
89
 
40
90
  # Class methods that will get installed in the host module
41
91
  #
42
92
  module ClassMethods
43
- # A reader for Configuration
44
- #
45
- def configuration
46
- @configuration ||= @configuration_defaults && configure {}
47
- end
48
-
49
93
  # Configuration defaults can be used to set the defaults of
50
94
  # any Configuration
51
95
  # @param [Proc] block setting the default values of the configuration
@@ -73,7 +117,7 @@ module Configurations
73
117
  # end
74
118
  #
75
119
  def configurable(*properties, &block)
76
- type = properties.shift if properties.first.is_a?(Class)
120
+ type = properties.shift if properties.first.is_a?(Module)
77
121
 
78
122
  @configurable ||= {}
79
123
  @configurable.merge! to_configurable_hash(properties, type, &block)
@@ -102,7 +146,10 @@ module Configurations
102
146
  # end
103
147
  #
104
148
  def configuration_method(method, &block)
105
- fail ArgumentError, "can't be configuration property and a method" if configurable?(method)
149
+ fail(
150
+ ArgumentError,
151
+ "can't be configuration property and a method"
152
+ ) if configurable?(method)
106
153
 
107
154
  @configuration_methods ||= {}
108
155
  method_hash = if method.is_a?(Hash)
@@ -143,18 +190,30 @@ module Configurations
143
190
  end
144
191
  end
145
192
 
193
+ private
194
+
195
+ def initialize_configuration!
196
+ @configuration = nil
197
+ end
198
+
199
+ # Include the configuration type module into the host configuration class
200
+ #
201
+ def include_configuration_type!(base)
202
+ return if base.ancestors.include?(configuration_type)
203
+
204
+ base.send :include, configuration_type
205
+ end
206
+
146
207
  # @return the class name of the configuration class to use
147
208
  #
148
209
  def configuration_type
149
210
  if @configurable.nil? || @configurable.empty?
150
- :ArbitraryConfiguration
211
+ Configurations::Arbitrary
151
212
  else
152
- :StrictConfiguration
213
+ Configurations::Strict
153
214
  end
154
215
  end
155
216
 
156
- private
157
-
158
217
  # Instantiates a configurable hash from a property and a type
159
218
  # @param [Symbol, Hash, Array] properties configurable properties,
160
219
  # either single or nested
@@ -3,6 +3,27 @@ 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
+
19
+ class << self
20
+ # Make new a private method, but allow __new__ alias. Instantiating
21
+ # configurations is not part of the public API.
22
+ #
23
+ alias_method :__new__, :new
24
+ private :new
25
+ end
26
+
6
27
  # Initialize a new configuration
7
28
  # @param [Hash] options The options to initialize a configuration with
8
29
  # @option options [Hash] methods a hash of method names pointing to procs
@@ -52,19 +73,38 @@ module Configurations
52
73
  # A convenience accessor to instantiate a configuration from a hash
53
74
  # @param [Hash] h the hash to read into the configuration
54
75
  # @return [Configuration] the configuration with values assigned
76
+ # @raise [ConfigurationError] if the given hash ambiguous values
77
+ # - string and symbol keys with the same string value pointing to
78
+ # different values
55
79
  #
56
80
  def from_h(h)
81
+ __test_ambiguity!(h)
57
82
  h.each do |property, value|
58
- if value.is_a?(::Hash) && __nested?(property)
59
- @data[property].from_h(value)
60
- elsif __configurable?(property)
61
- __assign!(property, value)
83
+ p = property.to_sym
84
+ if value.is_a?(::Hash) && __nested?(p)
85
+ @data[p].from_h(value)
86
+ elsif __configurable?(p)
87
+ __assign!(p, value)
62
88
  end
63
89
  end
64
90
 
65
91
  self
66
92
  end
67
93
 
94
+ # Inspect a configuration. Implements inspect without exposing internally
95
+ # used instance variables.
96
+ # @param [TrueClass, FalseClass] debug whether to show internals, defaults
97
+ # to false
98
+ # @return [String] The inspect output for this instance
99
+ #
100
+ def inspect(debug = false)
101
+ unless debug
102
+ '#<%s:0x00%x @data=%s>' % [__class__, object_id << 1, @data.inspect]
103
+ else
104
+ super()
105
+ end
106
+ end
107
+
68
108
  # @param [Symbol] property The property to test for configurability
69
109
  # @return [Boolean] whether the given property is configurable
70
110
  #
@@ -91,6 +131,7 @@ module Configurations
91
131
  #
92
132
  def __install_configuration_methods__
93
133
  @__methods__.each do |meth, block|
134
+ __test_reserved!(meth)
94
135
  __define_singleton_method__(meth, &block) if block.is_a?(::Proc)
95
136
  end
96
137
  end
@@ -101,7 +142,8 @@ module Configurations
101
142
  #
102
143
  def __options_hash_for__(property)
103
144
  hash = {}
104
- hash[:not_configured] = __not_configured_hash_for__(property) if @__not_configured__[property]
145
+ hash[:not_configured] =
146
+ __not_configured_hash_for__(property) if @__not_configured__[property]
105
147
  hash[:methods] = @__methods__[property] if @__methods__.key?(property)
106
148
 
107
149
  hash
@@ -129,7 +171,9 @@ module Configurations
129
171
  #
130
172
  def __not_configured_hash_for__(property)
131
173
  hash = ::Hash.new(&@__not_configured__.default_proc)
132
- hash.merge! @__not_configured__[property] if @__not_configured__[property].is_a?(::Hash)
174
+ hash.merge!(
175
+ @__not_configured__[property]
176
+ ) if @__not_configured__[property].is_a?(::Hash)
133
177
 
134
178
  hash
135
179
  end
@@ -139,7 +183,7 @@ module Configurations
139
183
  #
140
184
  def __configuration_hash__
141
185
  ::Hash.new do |h, k|
142
- h[k] = __class__.new(__options_hash_for__(k)) if __configurable?(k)
186
+ h[k] = __class__.__new__(__options_hash_for__(k)) if __configurable?(k)
143
187
  end
144
188
  end
145
189
 
@@ -148,6 +192,7 @@ module Configurations
148
192
  # @param [Any] value the given value
149
193
  #
150
194
  def __assign!(property, value)
195
+ __test_reserved!(property)
151
196
  @data[property] = value
152
197
  end
153
198
 
@@ -177,6 +222,42 @@ module Configurations
177
222
  method.to_s[0..-2].to_sym
178
223
  end
179
224
 
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
+
180
261
  # @param [Hash] a hash to collect blocks from
181
262
  # @return [Proc] a proc to call all the procs
182
263
  #
@@ -2,4 +2,9 @@ module Configurations
2
2
  # A configuration Error, raised when configuration gets misconfigured
3
3
  #
4
4
  ConfigurationError = Class.new(ArgumentError)
5
+
6
+ # A reserved method error, raised when configurable is used with
7
+ # reserved methods
8
+ #
9
+ ReservedMethodError = Class.new(NameError)
5
10
  end
@@ -3,7 +3,7 @@ module Configurations
3
3
  # StrictConfiguration is a blank object with setters and getters defined
4
4
  # according to the configurable settings given
5
5
  #
6
- class StrictConfiguration < Configuration
6
+ module Strict
7
7
  # Initialize a new configuration
8
8
  # @param [Hash] options The options to initialize a configuration with
9
9
  # @option options [Hash] configurable a hash of configurable properties
@@ -48,6 +48,8 @@ module Configurations
48
48
  end
49
49
  end
50
50
 
51
+ # Add a property to a nested configurable
52
+ #
51
53
  def __add_to_nested_configurables!(property, nested, assertion)
52
54
  @__nested_configurables__ ||= ::Hash.new { |h, k| h[k] = {} }
53
55
  @__nested_configurables__[property].merge!(
@@ -55,6 +57,8 @@ module Configurations
55
57
  )
56
58
  end
57
59
 
60
+ # Get an options hash for a property
61
+ #
58
62
  def __options_hash_for__(property)
59
63
  super(property).merge(configurable: @__nested_configurables__[property])
60
64
  end
@@ -85,6 +89,7 @@ module Configurations
85
89
  # @param [Symbol] property the property to install
86
90
  #
87
91
  def __install_property__(property)
92
+ __test_reserved!(property)
88
93
  __install_setter__(property)
89
94
  __install_getter__(property)
90
95
  end
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class TestReservedMethods < ConfigurationsTest
4
+ def test_raises_when_setup_with_reserved_methods
5
+ assert_raises Configurations::ReservedMethodError do
6
+ self.class.setup_with do |c|
7
+ c.to_s = 'bla'
8
+ end
9
+
10
+ setup
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+
3
+ class TestArbitraryReservedMethodsAsMethods < ConfigurationsTest
4
+ def test_reserved_methods_not_allowed_as_methods
5
+ assert_raises Configurations::ReservedMethodError do
6
+ @module.class_eval do
7
+ configuration_method :to_h do
8
+ 'h'
9
+ end
10
+ end
11
+
12
+ setup
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ require 'test_helper'
2
+
3
+ class TestConfigurationSynchronized < MiniTest::Test
4
+ module TestModule
5
+ include Configurations
6
+
7
+ configuration_defaults do |c|
8
+ c.a = 'b'
9
+ end
10
+ end
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)
24
+
25
+ assert_equal 100, ids.uniq.size
26
+ end
27
+ end
28
+
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'
34
+ end
35
+
36
+ there = TestModule.configuration.a
37
+ end
38
+
39
+ t.join
40
+ here = TestModule.configuration.a
41
+
42
+ assert_equal here, there
43
+ end
44
+
45
+ def with_gc_disabled(&_block)
46
+ GC.disable
47
+ yield
48
+ GC.enable
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ class TestInspect < ConfigurationsTest
2
+ setup_with {}
3
+
4
+ def inspect_output(_configuration)
5
+ "#<#{self.class.name}::TestModule::Configuration:0x00%x @data={}>" % [@configuration.object_id << 1]
6
+ end
7
+
8
+ def test_non_debug_inspect
9
+ expected = inspect_output(@configuration)
10
+
11
+ assert @configuration.inspect == expected, "Expected inspect to produce output #{expected.inspect}, but got #{@configuration.inspect.inspect}"
12
+ end
13
+
14
+ def test_debug_inspect
15
+ non_debug = inspect_output(@configuration)
16
+
17
+ refute @configuration.inspect(true) == non_debug, "Expected debug inspect to delegate to kernel and produce more output, but got #{@configuration.inspect(true).inspect}"
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ class TestInstantiationPrevention < ConfigurationsTest
2
+ def test_instantiation_via_new_is_prevented
3
+ configuration_class = @module.const_get(:Configuration)
4
+ assert_raises NoMethodError do
5
+ configuration_class.new({})
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ class TestIsAConfiguration < ConfigurationsTest
2
+ def test_is_a_configuration
3
+ configuration_class = @module.const_get(:Configuration)
4
+ assert @configuration.is_a?(configuration_class), 'Expected configuration to be defined in host module'
5
+ end
6
+ end
@@ -35,6 +35,24 @@ module Tests
35
35
  old_to_h = @configuration.to_h.dup
36
36
  assert_equal(old_to_h, @module.configure { |c| c.from_h(old_to_h) }.to_h)
37
37
  end
38
+
39
+ def test_from_h_with_strings
40
+ old_to_h = @configuration.to_h.dup
41
+ string_to_h = Hash[old_to_h.map { |k, v| [k.to_s, v] }]
42
+ assert_equal(old_to_h, @module.configure { |c| c.from_h(string_to_h) }.to_h)
43
+ end
44
+
45
+ def test_from_h_with_ambiguous_strings_and_symbols
46
+ assert_raises Configurations::ConfigurationError do
47
+ @module.configure { |c| c.from_h('p1' => 'bla', :p1 => 'blu') }
48
+ end
49
+ end
50
+
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
55
+ end
38
56
  end
39
57
  end
40
58
  end
@@ -6,6 +6,15 @@ module Tests
6
6
  assert_equal(expected, @module.configure { |c| c.from_h(input) }.to_h)
7
7
  end
8
8
 
9
+ def test_from_h_with_strings
10
+ expected, input = expection_and_input
11
+ string_input = Hash[input.map { |k, v| [k.to_s, v] }]
12
+ assert_equal(
13
+ expected,
14
+ @module.configure { |c| c.from_h(string_input) }.to_h
15
+ )
16
+ end
17
+
9
18
  def test_from_h_outside_block
10
19
  expected, input = expection_and_input
11
20
  assert_equal(expected, @configuration.from_h(input).to_h)
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class TestStrictReservedMethods < ConfigurationsTest
4
+ def test_reserved_methods_not_configurable
5
+ assert_raises Configurations::ReservedMethodError do
6
+ @module.class_eval do
7
+ configurable :inspect
8
+ end
9
+
10
+ setup
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+
3
+ class TestStrictReservedMethodsAsMethods < ConfigurationsTest
4
+ def test_reserved_methods_not_allowed_as_methods
5
+ assert_raises Configurations::ReservedMethodError do
6
+ @module.class_eval do
7
+ configuration_method :to_h do
8
+ 'h'
9
+ end
10
+ end
11
+
12
+ setup
13
+ end
14
+ end
15
+ end
@@ -45,7 +45,7 @@ module Test
45
45
 
46
46
  features.each do |feature|
47
47
  method = method(feature)
48
- mod.module_eval{ |m| method.call(m) }
48
+ mod.module_eval { |m| method.call(m) }
49
49
  end
50
50
 
51
51
  @configuration_block = block if block_given?
@@ -117,7 +117,7 @@ module Test
117
117
  configurable Hash, p3: { p5: :p7 }
118
118
  configurable Symbol, :class
119
119
  configurable Proc, :module
120
- configurable Class, :puts
120
+ configurable Module, :puts
121
121
  end
122
122
  end
123
123
 
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.0.0
4
+ version: 2.2.0
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-01-07 00:00:00.000000000 Z
11
+ date: 2015-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -80,6 +80,12 @@ files:
80
80
  - test/configurations/arbitrary/test_methods.rb
81
81
  - test/configurations/arbitrary/test_not_configured.rb
82
82
  - test/configurations/arbitrary/test_not_configured_default.rb
83
+ - test/configurations/arbitrary/test_reserved_methods.rb
84
+ - test/configurations/arbitrary/test_reserved_methods_as_methods.rb
85
+ - test/configurations/configuration/test_configure_synchronized.rb
86
+ - test/configurations/configuration/test_inspect.rb
87
+ - test/configurations/configuration/test_instantiation_prevention.rb
88
+ - test/configurations/configuration/test_is_a_configuration.rb
83
89
  - test/configurations/shared/defaults.rb
84
90
  - test/configurations/shared/hash_methods.rb
85
91
  - test/configurations/shared/kernel_methods.rb
@@ -95,6 +101,8 @@ files:
95
101
  - test/configurations/strict/test_methods.rb
96
102
  - test/configurations/strict/test_not_configured.rb
97
103
  - test/configurations/strict/test_not_configured_default.rb
104
+ - test/configurations/strict/test_reserved_methods.rb
105
+ - test/configurations/strict/test_reserved_methods_as_methods.rb
98
106
  - test/configurations/strict_types/test.rb
99
107
  - test/configurations/strict_types/test_defaults.rb
100
108
  - test/configurations/strict_types/test_hash_methods.rb
@@ -148,6 +156,12 @@ test_files:
148
156
  - test/configurations/arbitrary/test_methods.rb
149
157
  - test/configurations/arbitrary/test_not_configured.rb
150
158
  - test/configurations/arbitrary/test_not_configured_default.rb
159
+ - test/configurations/arbitrary/test_reserved_methods.rb
160
+ - test/configurations/arbitrary/test_reserved_methods_as_methods.rb
161
+ - test/configurations/configuration/test_configure_synchronized.rb
162
+ - test/configurations/configuration/test_inspect.rb
163
+ - test/configurations/configuration/test_instantiation_prevention.rb
164
+ - test/configurations/configuration/test_is_a_configuration.rb
151
165
  - test/configurations/shared/defaults.rb
152
166
  - test/configurations/shared/hash_methods.rb
153
167
  - test/configurations/shared/kernel_methods.rb
@@ -163,6 +177,8 @@ test_files:
163
177
  - test/configurations/strict/test_methods.rb
164
178
  - test/configurations/strict/test_not_configured.rb
165
179
  - test/configurations/strict/test_not_configured_default.rb
180
+ - test/configurations/strict/test_reserved_methods.rb
181
+ - test/configurations/strict/test_reserved_methods_as_methods.rb
166
182
  - test/configurations/strict_types/test.rb
167
183
  - test/configurations/strict_types/test_defaults.rb
168
184
  - test/configurations/strict_types/test_hash_methods.rb