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 +4 -4
- data/.travis.yml +3 -2
- data/README.md +48 -29
- data/lib/configurations.rb +1 -1
- data/lib/configurations/arbitrary.rb +9 -7
- data/lib/configurations/blank_object.rb +3 -1
- data/lib/configurations/configurable.rb +78 -19
- data/lib/configurations/configuration.rb +88 -7
- data/lib/configurations/error.rb +5 -0
- data/lib/configurations/strict.rb +6 -1
- data/test/configurations/arbitrary/test_reserved_methods.rb +13 -0
- data/test/configurations/arbitrary/test_reserved_methods_as_methods.rb +15 -0
- data/test/configurations/configuration/test_configure_synchronized.rb +50 -0
- data/test/configurations/configuration/test_inspect.rb +19 -0
- data/test/configurations/configuration/test_instantiation_prevention.rb +8 -0
- data/test/configurations/configuration/test_is_a_configuration.rb +6 -0
- data/test/configurations/shared/hash_methods.rb +18 -0
- data/test/configurations/shared/strict_hash_methods.rb +9 -0
- data/test/configurations/strict/test_reserved_methods.rb +13 -0
- data/test/configurations/strict/test_reserved_methods_as_methods.rb +15 -0
- data/test/support/setup.rb +2 -2
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0d503a4163d4238d71c61d2349a78e5f74b59be
|
4
|
+
data.tar.gz: 21343c5d0ff8b12c948caced4f73bb74a04d2083
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71026df8af3278a819d01c1ce0729a27d86ef8a83845596a4301730eee4fb585afeb1ca0f6681c23ac68a1fc74237880fb332bb720d7b6cffb9daecac54f445f
|
7
|
+
data.tar.gz: 354aea5c774097ef557c2ce645f55c124fe2f5f0684fb0c01e205741c8afc8a2134aa00a0994528303c2e9f44d20c4d17387148f1004871ba19d8877d6d2a9be
|
data/.travis.yml
CHANGED
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
|
-
|
9
|
+
```ruby
|
10
|
+
gem install configurations
|
11
|
+
```
|
10
12
|
|
11
13
|
or with Bundler
|
12
14
|
|
13
|
-
|
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.
|
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
|
-
|
272
|
-
|
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
|
|
data/lib/configurations.rb
CHANGED
@@ -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
|
-
|
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
|
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,
|
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
|
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
|
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.
|
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.
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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?(
|
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
|
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
|
-
|
211
|
+
Configurations::Arbitrary
|
151
212
|
else
|
152
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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] =
|
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!
|
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__.
|
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
|
#
|
data/lib/configurations/error.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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
|
data/test/support/setup.rb
CHANGED
@@ -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
|
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.
|
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-
|
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
|