configurations 2.0.0.pre → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -7
- data/.travis.yml +4 -2
- data/License.txt +1 -1
- data/README.md +23 -7
- data/Rakefile +1 -1
- data/configurations.gemspec +7 -3
- data/lib/configurations.rb +8 -4
- data/lib/configurations/arbitrary.rb +124 -0
- data/lib/configurations/blank_object.rb +60 -0
- data/lib/configurations/configurable.rb +153 -42
- data/lib/configurations/configuration.rb +96 -213
- data/lib/configurations/strict.rb +170 -0
- data/test/configurations/arbitrary/test.rb +23 -0
- data/test/configurations/arbitrary/test_defaults.rb +5 -0
- data/test/configurations/arbitrary/test_hash_methods.rb +11 -0
- data/test/configurations/arbitrary/test_methods.rb +5 -0
- data/test/configurations/arbitrary/test_not_configured.rb +5 -0
- data/test/configurations/arbitrary/test_not_configured_default.rb +5 -0
- data/test/configurations/shared/defaults.rb +43 -0
- data/test/configurations/shared/hash_methods.rb +40 -0
- data/test/configurations/shared/kernel_methods.rb +9 -0
- data/test/configurations/shared/methods.rb +31 -0
- data/test/configurations/shared/not_configured_callbacks.rb +42 -0
- data/test/configurations/shared/not_configured_default_callback.rb +42 -0
- data/test/configurations/shared/properties.rb +46 -0
- data/test/configurations/shared/properties_outside_block.rb +40 -0
- data/test/configurations/shared/strict_hash_methods.rb +43 -0
- data/test/configurations/strict/test.rb +20 -0
- data/test/configurations/strict/test_defaults.rb +6 -0
- data/test/configurations/strict/test_hash_methods.rb +11 -0
- data/test/configurations/strict/test_methods.rb +6 -0
- data/test/configurations/strict/test_not_configured.rb +6 -0
- data/test/configurations/strict/test_not_configured_default.rb +6 -0
- data/test/configurations/strict_types/test.rb +18 -0
- data/test/configurations/strict_types/test_defaults.rb +6 -0
- data/test/configurations/strict_types/test_hash_methods.rb +11 -0
- data/test/configurations/strict_types/test_methods.rb +6 -0
- data/test/configurations/strict_types/test_not_configured.rb +6 -0
- data/test/configurations/strict_types/test_not_configured_default.rb +6 -0
- data/test/configurations/strict_types_with_blocks/test.rb +22 -0
- data/test/configurations/strict_types_with_blocks/test_defaults.rb +6 -0
- data/test/configurations/strict_types_with_blocks/test_hash_methods.rb +14 -0
- data/test/configurations/strict_types_with_blocks/test_methods.rb +6 -0
- data/test/configurations/strict_types_with_blocks/test_not_configured.rb +6 -0
- data/test/configurations/strict_types_with_blocks/test_not_configured_default.rb +6 -0
- data/test/configurations/strict_with_blocks/test.rb +16 -0
- data/test/configurations/strict_with_blocks/test_defaults.rb +6 -0
- data/test/configurations/strict_with_blocks/test_hash_methods.rb +14 -0
- data/test/configurations/strict_with_blocks/test_methods.rb +6 -0
- data/test/configurations/strict_with_blocks/test_not_configured.rb +6 -0
- data/test/configurations/strict_with_blocks/test_not_configured_default.rb +6 -0
- data/test/support/setup.rb +173 -0
- data/test/support/shared.rb +11 -0
- data/test/test_helper.rb +6 -5
- metadata +148 -65
- data/test/configurations/test_configurable_with_blocks_configuration.rb +0 -54
- data/test/configurations/test_configuration.rb +0 -182
- data/test/configurations/test_configuration_methods.rb +0 -85
- data/test/configurations/test_stricter_configuration.rb +0 -173
- data/test/support/testmodules.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f9907568167e40b9014fea6fd1999980874e83a8
|
4
|
+
data.tar.gz: 848cd43a6bb4e9c41cf9aa6ff21d1b3966fb7238
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a60a00a58aa9af53a443fa2781138c734248ac77b5c2f55724cb330eca8a173636445657e8b7f23a4e907fd319cd9eb651a26ad2216c9d80d9d98501f4e7e646
|
7
|
+
data.tar.gz: 8b0a6f40548e80e3ba7f21eb6674980b2877ff9f270ed02e0c8aadedea532c350270895ccf12108302c74a076967b14f84d5796669e87083e451b1e45ffaa9a8
|
data/.travis.yml
CHANGED
data/License.txt
CHANGED
data/README.md
CHANGED
@@ -10,13 +10,13 @@ Configurations provides a unified approach to do configurations using the `MyGem
|
|
10
10
|
|
11
11
|
or with Bundler
|
12
12
|
|
13
|
-
`gem 'configurations', '~> 2.0.0
|
13
|
+
`gem 'configurations', '~> 2.0.0'`
|
14
14
|
|
15
15
|
Configurations uses [Semver 2.0](http://semver.org/)
|
16
16
|
|
17
17
|
## Compatibility
|
18
18
|
|
19
|
-
Compatible with MRI 1.9.2 - 2.
|
19
|
+
Compatible with MRI 1.9.2 - 2.2, Rubinius 2.2, jRuby 1.7 and 9K
|
20
20
|
|
21
21
|
## Why?
|
22
22
|
|
@@ -61,7 +61,7 @@ Undefined properties on an arbitrary configuration will return `nil`
|
|
61
61
|
MyGem.configuration.not_set #=> nil
|
62
62
|
```
|
63
63
|
|
64
|
-
If you want to define the behaviour for not set properties yourself, use `not_configured`.
|
64
|
+
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
65
|
|
66
66
|
```
|
67
67
|
module MyGem
|
@@ -71,6 +71,15 @@ module MyGem
|
|
71
71
|
end
|
72
72
|
```
|
73
73
|
|
74
|
+
Or you can define finer-grained callbacks:
|
75
|
+
|
76
|
+
```
|
77
|
+
module MyGem
|
78
|
+
not_configured my: { nested: :prop } do |prop|
|
79
|
+
raise NoMethodError, "#{prop} must be configured"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
74
83
|
|
75
84
|
### Second way: Restricted Configuration
|
76
85
|
|
@@ -114,7 +123,7 @@ If you want to define the behaviour for not set properties yourself, use `not_co
|
|
114
123
|
|
115
124
|
```
|
116
125
|
module MyGem
|
117
|
-
not_configured do |prop|
|
126
|
+
not_configured :awesome, :nice do |prop| # omit the arguments to get a catch-all not_configured
|
118
127
|
warn :not_configured, "Please configure #{prop} or live in danger"
|
119
128
|
end
|
120
129
|
end
|
@@ -219,6 +228,13 @@ You get:
|
|
219
228
|
MyGem.configuration.foobar('ARG') #=> 'FOOBARARG'
|
220
229
|
```
|
221
230
|
|
231
|
+
configuration methods can also be installed on nested properties using hashes:
|
232
|
+
|
233
|
+
```
|
234
|
+
configuration_method foo: :bar do |arg|
|
235
|
+
foo + bar + arg
|
236
|
+
end
|
237
|
+
```
|
222
238
|
|
223
239
|
### Defaults:
|
224
240
|
|
@@ -239,7 +255,7 @@ MyGem.configuration.to_h #=> a Hash
|
|
239
255
|
|
240
256
|
### Configure with a hash where needed
|
241
257
|
|
242
|
-
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.
|
258
|
+
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.
|
243
259
|
|
244
260
|
```
|
245
261
|
yaml_hash = YAML.load_file('configuration.yml')
|
@@ -252,7 +268,7 @@ end
|
|
252
268
|
|
253
269
|
### Some caveats
|
254
270
|
|
255
|
-
The `to_h` from above is along with `method_missing`, `object_id` and `initialize` the only purposely defined API method which you can not overwrite with a configuration value.
|
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.
|
256
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.
|
257
273
|
|
258
274
|
## Contributing
|
@@ -263,4 +279,4 @@ Let's make this awesome. Write tests for your added stuff, bonus points for feat
|
|
263
279
|
|
264
280
|
### Copyright
|
265
281
|
|
266
|
-
Copyright ©
|
282
|
+
Copyright © 2015 Beat Richartz. See LICENSE.txt for further details.
|
data/Rakefile
CHANGED
data/configurations.gemspec
CHANGED
@@ -5,13 +5,17 @@ Gem::Specification.new do |s|
|
|
5
5
|
s.name = 'configurations'
|
6
6
|
s.version = Configurations::VERSION
|
7
7
|
s.authors = ['Beat Richartz']
|
8
|
-
s.description =
|
8
|
+
s.description = <<-DESCRIPTION
|
9
|
+
Configurations provides a unified approach to do configurations with the flexibility to do everything
|
10
|
+
from arbitrary configurations to type asserted configurations for your gem or any other ruby code.
|
11
|
+
DESCRIPTION
|
9
12
|
s.email = 'attr_accessor@gmail.com'
|
10
13
|
s.homepage = 'http://github.com/beatrichartz/configurations'
|
11
14
|
s.licenses = %w(MIT)
|
12
15
|
s.require_paths = %w(lib)
|
13
|
-
s.summary =
|
14
|
-
|
16
|
+
s.summary = <<-SUMMARY
|
17
|
+
Configurations with a configure block from arbitrary to type-restricted for your gem or other ruby code.
|
18
|
+
SUMMARY
|
15
19
|
s.files = `git ls-files`.split("\n")
|
16
20
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
17
21
|
|
data/lib/configurations.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require_relative 'configurations/error'
|
2
|
+
require_relative 'configurations/blank_object'
|
2
3
|
require_relative 'configurations/configuration'
|
4
|
+
require_relative 'configurations/arbitrary'
|
5
|
+
require_relative 'configurations/strict'
|
3
6
|
require_relative 'configurations/configurable'
|
4
7
|
|
5
|
-
# Configurations provides a unified approach to do configurations
|
6
|
-
#
|
7
|
-
#
|
8
|
+
# Configurations provides a unified approach to do configurations
|
9
|
+
# with the flexibility to do everything from arbitrary configurations
|
10
|
+
# to type asserted configurations for your gem or any other ruby code.
|
11
|
+
# @version 2.0.0
|
8
12
|
# @author Beat Richartz
|
9
13
|
#
|
10
14
|
module Configurations
|
@@ -12,5 +16,5 @@ module Configurations
|
|
12
16
|
|
13
17
|
# Version number of Configurations
|
14
18
|
#
|
15
|
-
VERSION = '2.0.0
|
19
|
+
VERSION = '2.0.0'
|
16
20
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Configurations
|
3
|
+
# Configuration is a blank object in order to allow configuration of
|
4
|
+
# various properties including keywords
|
5
|
+
#
|
6
|
+
class ArbitraryConfiguration < Configuration
|
7
|
+
# Initialize a new configuration
|
8
|
+
# @param [Hash] options The options to initialize a configuration with
|
9
|
+
# @option options [Hash] methods a hash of method names pointing to procs
|
10
|
+
# @option options [Proc] not_configured a proc to evaluate for
|
11
|
+
# not_configured properties
|
12
|
+
# @param [Proc] block a block to configure this configuration with
|
13
|
+
# @yield [HostModule::Configuration] a configuration
|
14
|
+
# @return [HostModule::Configuration] a configuration
|
15
|
+
# @note An arbitrary configuration has to control its writeable state,
|
16
|
+
# therefore configuration is only possible in the initialization block
|
17
|
+
#
|
18
|
+
def initialize(options = {}, &block)
|
19
|
+
self.__writeable__ = true
|
20
|
+
super
|
21
|
+
self.__writeable__ = false if block
|
22
|
+
end
|
23
|
+
|
24
|
+
# Method missing gives access for reading and writing to the underlying
|
25
|
+
# configuration hash via dot notation
|
26
|
+
#
|
27
|
+
def method_missing(method, *args, &block)
|
28
|
+
if __respond_to_writer?(method)
|
29
|
+
__assign!(method.to_s[0..-2].to_sym, args.first)
|
30
|
+
elsif __respond_to_method_for_write?(method)
|
31
|
+
@data[method]
|
32
|
+
elsif __respond_to_method_for_read?(method, *args, &block)
|
33
|
+
@data.fetch(method, &__not_configured_callback_for__(method))
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Respond to missing according to the method_missing implementation
|
40
|
+
#
|
41
|
+
def respond_to_missing?(method, include_private = false)
|
42
|
+
__respond_to_writer?(method) ||
|
43
|
+
__respond_to_method_for_read?(method, *args, &block) ||
|
44
|
+
__respond_to_method_for_write?(method) ||
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
# A convenience accessor to instantiate a configuration from a hash
|
49
|
+
# @param [Hash] h the hash to read into the configuration
|
50
|
+
# @return [Configuration] the configuration with values assigned
|
51
|
+
# @note can only be accessed during writeable state (in configure block).
|
52
|
+
# Unassignable values are ignored
|
53
|
+
# @raise [ArgumentError] unless used in writeable state (in configure block)
|
54
|
+
#
|
55
|
+
def from_h(h)
|
56
|
+
unless @__writeable__
|
57
|
+
fail ::ArgumentError, 'can not dynamically assign values from a hash'
|
58
|
+
end
|
59
|
+
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Symbol] property The property to test for configurability
|
64
|
+
# @return [Boolean] whether the given property is configurable
|
65
|
+
#
|
66
|
+
def __configurable?(_property)
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set the configuration to writeable or read only. Access to writer methods
|
71
|
+
# is only allowed within the configure block, this method is used to invoke
|
72
|
+
# writeability for subconfigurations.
|
73
|
+
# @param [Boolean] data true if the configuration should be writeable, false
|
74
|
+
# otherwise
|
75
|
+
#
|
76
|
+
def __writeable__=(data)
|
77
|
+
@__writeable__ = data
|
78
|
+
return if @data.nil?
|
79
|
+
|
80
|
+
@data.each do |_k, v|
|
81
|
+
v.__writeable__ = data if v.is_a?(__class__)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# @param [Symbol] property The property to test for
|
88
|
+
# @return [Boolean] whether the given property has been configured
|
89
|
+
#
|
90
|
+
def __configured?(_property)
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
# @param [Symbol] method the method to test for
|
95
|
+
# @return [Boolean] whether the given method is a writer
|
96
|
+
#
|
97
|
+
def __is_writer?(method)
|
98
|
+
method.to_s.end_with?('=')
|
99
|
+
end
|
100
|
+
|
101
|
+
# @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
|
104
|
+
#
|
105
|
+
def __respond_to_method_for_write?(method)
|
106
|
+
!__is_writer?(method) && @__writeable__ && @data[method].is_a?(__class__)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param [Symbol] method the method to test for
|
110
|
+
# @return [Boolean] whether the configuration responds to the given property
|
111
|
+
#
|
112
|
+
def __respond_to_method_for_read?(method, *args, &block)
|
113
|
+
!__is_writer?(method) && args.empty? && block.nil?
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param [Symbol] method the method to test for
|
117
|
+
# @return [Boolean] whether the method is a writer and is used in writeable
|
118
|
+
# state
|
119
|
+
#
|
120
|
+
def __respond_to_writer?(method)
|
121
|
+
@__writeable__ && __is_writer?(method)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Configurations
|
2
|
+
# Create a blank object with some kernel methods
|
3
|
+
#
|
4
|
+
class BlankObject < ::BasicObject
|
5
|
+
# The instance methods to keep on the blank object.
|
6
|
+
#
|
7
|
+
KEEP_METHODS = [
|
8
|
+
:equal?,
|
9
|
+
:object_id,
|
10
|
+
:__id__,
|
11
|
+
:__send__,
|
12
|
+
:method_missing
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
# The kernel methods to alias to an internal name
|
16
|
+
#
|
17
|
+
ALIAS_KERNEL_METHODS = {
|
18
|
+
__class__: :class,
|
19
|
+
__instance_eval__: :instance_eval,
|
20
|
+
__define_singleton_method__: :define_singleton_method
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
# The kernel methods to keep on the blank object
|
24
|
+
#
|
25
|
+
KEEP_KERNEL_METHODS = [
|
26
|
+
:respond_to?,
|
27
|
+
:is_a?,
|
28
|
+
:inspect,
|
29
|
+
:object_id,
|
30
|
+
# rbx needs the singleton class to access singleton methods
|
31
|
+
:singleton_class,
|
32
|
+
*ALIAS_KERNEL_METHODS.keys
|
33
|
+
].freeze
|
34
|
+
|
35
|
+
# Undefines every instance method except the kept methods
|
36
|
+
#
|
37
|
+
(instance_methods - KEEP_METHODS).each do |method|
|
38
|
+
undef_method method
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Module] A Kernel module with only the methods
|
42
|
+
# defined in KEEP_KERNEL_METHODS
|
43
|
+
#
|
44
|
+
def self.blank_kernel
|
45
|
+
kernel = ::Kernel.dup
|
46
|
+
|
47
|
+
ALIAS_KERNEL_METHODS.each do |new_name, old_name|
|
48
|
+
kernel.module_eval { alias_method new_name, old_name }
|
49
|
+
end
|
50
|
+
|
51
|
+
(kernel.instance_methods - KEEP_KERNEL_METHODS).each do |method|
|
52
|
+
kernel.module_eval { undef_method method }
|
53
|
+
end
|
54
|
+
|
55
|
+
kernel
|
56
|
+
end
|
57
|
+
|
58
|
+
include blank_kernel
|
59
|
+
end
|
60
|
+
end
|
@@ -4,7 +4,8 @@ module Configurations
|
|
4
4
|
module Configurable
|
5
5
|
extend self
|
6
6
|
|
7
|
-
# Once included, Configurations installs three methods in the host module:
|
7
|
+
# Once included, Configurations installs three methods in the host module:
|
8
|
+
# configure, configuration_defaults and configurable
|
8
9
|
#
|
9
10
|
def included(base)
|
10
11
|
install_configure_in(base)
|
@@ -13,29 +14,25 @@ module Configurations
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
# Installs #configure in base, and makes sure that it will instantiate
|
17
|
+
# Installs #configure in base, and makes sure that it will instantiate
|
18
|
+
# configuration as a subclass of the host module
|
17
19
|
#
|
18
20
|
def install_configure_in(base)
|
19
21
|
base.class_eval <<-EOF
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
configurable: @configurable,
|
35
|
-
not_configured: @not_configured_callback,
|
36
|
-
&block
|
37
|
-
)
|
38
|
-
end
|
22
|
+
# The central configure method
|
23
|
+
# @params [Proc] block the block to configure host module with
|
24
|
+
# @raise [ArgumentError] error when not given a block
|
25
|
+
# @example Configure a configuration
|
26
|
+
# MyGem.configure do |c|
|
27
|
+
# c.foo = :bar
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
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
|
+
)
|
39
36
|
end
|
40
37
|
EOF
|
41
38
|
end
|
@@ -46,70 +43,121 @@ module Configurations
|
|
46
43
|
# A reader for Configuration
|
47
44
|
#
|
48
45
|
def configuration
|
49
|
-
@configuration ||= @configuration_defaults && configure {
|
46
|
+
@configuration ||= @configuration_defaults && configure {}
|
50
47
|
end
|
51
48
|
|
52
|
-
# Configuration defaults can be used to set the defaults of
|
49
|
+
# Configuration defaults can be used to set the defaults of
|
50
|
+
# any Configuration
|
53
51
|
# @param [Proc] block setting the default values of the configuration
|
54
52
|
#
|
55
53
|
def configuration_defaults(&block)
|
56
54
|
@configuration_defaults = block
|
57
55
|
end
|
58
56
|
|
59
|
-
# configurable can be used to set the properties which should be
|
60
|
-
# the given property should
|
61
|
-
#
|
62
|
-
# @param [
|
57
|
+
# configurable can be used to set the properties which should be
|
58
|
+
# configurable, as well as a type which the given property should
|
59
|
+
# be asserted to
|
60
|
+
# @param [Class, Symbol, Hash] properties a type as a first argument to
|
61
|
+
# type assert (if any) or nested properties to allow for setting
|
62
|
+
# @param [Proc] block a block with arity 2 to evaluate when a property
|
63
|
+
# is set. It will be given: property name and value
|
63
64
|
# @example Define a configurable property
|
64
65
|
# configurable :foo
|
65
66
|
# @example Define a type asserted, nested property for type String
|
66
67
|
# configurable String, bar: :baz
|
67
68
|
# @example Define a custom assertion for a property
|
68
69
|
# configurable biz: %i(bi bu) do |value|
|
69
|
-
#
|
70
|
+
# unless %w(a b c).include?(value)
|
71
|
+
# fail ArgumentError, 'must be one of a, b, c'
|
72
|
+
# end
|
70
73
|
# end
|
71
74
|
#
|
72
75
|
def configurable(*properties, &block)
|
73
76
|
type = properties.shift if properties.first.is_a?(Class)
|
77
|
+
|
74
78
|
@configurable ||= {}
|
75
79
|
@configurable.merge! to_configurable_hash(properties, type, &block)
|
76
80
|
end
|
77
81
|
|
78
82
|
# returns whether a property is set to be configurable
|
79
83
|
# @param [Symbol] property the property to ask status for
|
84
|
+
# @return [Boolean] whether the property is configurable
|
80
85
|
#
|
81
86
|
def configurable?(property)
|
82
|
-
@configurable.is_a?(Hash) && @configurable.
|
87
|
+
@configurable.is_a?(Hash) && @configurable.key?(property)
|
83
88
|
end
|
84
89
|
|
85
|
-
# configuration method can be used to retrieve properties
|
90
|
+
# configuration method can be used to retrieve properties
|
91
|
+
# from the configuration
|
92
|
+
# which use your gem's context
|
86
93
|
# @param [Class, Symbol, Hash] method the method to define
|
87
94
|
# @param [Proc] block the block to evaluate
|
88
|
-
# @example Define a configuration method 'foobararg'
|
95
|
+
# @example Define a configuration method 'foobararg'
|
89
96
|
# configuration_method :foobararg do |arg|
|
90
97
|
# foo + bar + arg
|
91
98
|
# end
|
99
|
+
# @example Define a configuration method on a nested property
|
100
|
+
# configuration_method foo: { bar: :arg } do
|
101
|
+
# baz + biz
|
102
|
+
# end
|
92
103
|
#
|
93
104
|
def configuration_method(method, &block)
|
94
|
-
|
105
|
+
fail ArgumentError, "can't be configuration property and a method" if configurable?(method)
|
106
|
+
|
95
107
|
@configuration_methods ||= {}
|
96
|
-
|
108
|
+
method_hash = if method.is_a?(Hash)
|
109
|
+
ingest_configuration_block!(method, &block)
|
110
|
+
else
|
111
|
+
{ method => block }
|
112
|
+
end
|
113
|
+
|
114
|
+
@configuration_methods.merge! method_hash
|
97
115
|
end
|
98
116
|
|
99
|
-
# not_configured defines the behaviour when a property has not been
|
100
|
-
# This can be useful for presence validations of certain
|
101
|
-
# or behaviour for undefined properties deviating from the
|
102
|
-
#
|
117
|
+
# not_configured defines the behaviour when a property has not been
|
118
|
+
# configured. This can be useful for presence validations of certain
|
119
|
+
# properties or behaviour for undefined properties deviating from the
|
120
|
+
# original behaviour.
|
121
|
+
# @param [Array, Symbol, Hash] properties the properties to install
|
122
|
+
# the callback on. If omitted, the callback will be installed on
|
123
|
+
# all properties that have no specific callbacks
|
124
|
+
# @param [Proc] block the block to evaluate when a property
|
125
|
+
# has not been configured
|
103
126
|
# @yield [Symbol] the property that has not been configured
|
127
|
+
# @example Define a specific not_configured callback
|
128
|
+
# not_configured :property1, property2: :property3 do |property|
|
129
|
+
# raise ArgumentError, "#{property} should be configured"
|
130
|
+
# end
|
131
|
+
# @example Define a catch-all not_configured callback
|
132
|
+
# not_configured do |property|
|
133
|
+
# raise StandardError, "You did not configure #{property}"
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
def not_configured(*properties, &block)
|
137
|
+
@not_configured ||= {}
|
138
|
+
|
139
|
+
if properties.empty?
|
140
|
+
@not_configured.default_proc = ->(h, k) { h[k] = block }
|
141
|
+
else
|
142
|
+
nested_merge_not_configured_hash(*properties, &block)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return the class name of the configuration class to use
|
104
147
|
#
|
105
|
-
def
|
106
|
-
@
|
148
|
+
def configuration_type
|
149
|
+
if @configurable.nil? || @configurable.empty?
|
150
|
+
:ArbitraryConfiguration
|
151
|
+
else
|
152
|
+
:StrictConfiguration
|
153
|
+
end
|
107
154
|
end
|
108
155
|
|
109
156
|
private
|
110
157
|
|
111
158
|
# Instantiates a configurable hash from a property and a type
|
112
|
-
# @param [Symbol, Hash, Array] properties configurable properties,
|
159
|
+
# @param [Symbol, Hash, Array] properties configurable properties,
|
160
|
+
# either single or nested
|
113
161
|
# @param [Class] type the type to assert, if any
|
114
162
|
# @return a hash with configurable values pointing to their types
|
115
163
|
#
|
@@ -118,8 +166,71 @@ module Configurations
|
|
118
166
|
assertion_hash.merge! block: block if block_given?
|
119
167
|
assertion_hash.merge! type: type if type
|
120
168
|
|
121
|
-
|
122
|
-
|
169
|
+
zip_to_hash(assertion_hash, *properties)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Makes all values of hash point to block
|
173
|
+
# @param [Hash] hash the hash to modify
|
174
|
+
# @param [Proc] block the block to point to
|
175
|
+
# @return a hash with all previous values being keys pointing to block
|
176
|
+
#
|
177
|
+
def ingest_configuration_block!(hash, &block)
|
178
|
+
hash.each do |k, v|
|
179
|
+
value = if v.is_a?(Hash)
|
180
|
+
ingest_configuration_block!(v, &block)
|
181
|
+
else
|
182
|
+
zip_to_hash(block, *Array(v))
|
183
|
+
end
|
184
|
+
|
185
|
+
hash.merge! k => value
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return a hash of configuration options with no nil values
|
190
|
+
#
|
191
|
+
def configuration_options
|
192
|
+
{
|
193
|
+
defaults: @configuration_defaults,
|
194
|
+
methods: @configuration_methods,
|
195
|
+
configurable: @configurable,
|
196
|
+
not_configured: @not_configured
|
197
|
+
}.delete_if { |_, value| value.nil? }
|
198
|
+
end
|
199
|
+
|
200
|
+
# merges the properties given into a not_configured hash
|
201
|
+
# @param [Symbol, Hash, Array] properties the properties to merge
|
202
|
+
# @param [Proc] block the block to point the properties to when
|
203
|
+
# not configured
|
204
|
+
#
|
205
|
+
def nested_merge_not_configured_hash(*properties, &block)
|
206
|
+
nested = properties.last.is_a?(Hash) ? properties.pop : {}
|
207
|
+
nested = ingest_configuration_block!(nested, &block)
|
208
|
+
props = zip_to_hash(block, *properties)
|
209
|
+
|
210
|
+
@not_configured.merge! nested, &method(:configuration_deep_merge)
|
211
|
+
@not_configured.merge! props, &method(:configuration_deep_merge)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Solves merge conflicts when merging
|
215
|
+
# @param [Symbol] key the key that conflicts
|
216
|
+
# @param [Anything] oldval the value of the left side of the merge
|
217
|
+
# @param [Anything] newval the value of the right side of the merge
|
218
|
+
# @return a mergable value with conflicts solved
|
219
|
+
#
|
220
|
+
def configuration_deep_merge(_key, oldval, newval)
|
221
|
+
if oldval.is_a?(Hash) && newval.is_a?(Hash)
|
222
|
+
oldval.merge(newval, &method(:configuration_deep_merge))
|
223
|
+
else
|
224
|
+
Array(oldval) + Array(newval)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Zip a value with keys to a hash so all keys point to the value
|
229
|
+
# @param [Anything] value the value to point to
|
230
|
+
# @param [Array] keys the keys to install
|
231
|
+
#
|
232
|
+
def zip_to_hash(value, *keys)
|
233
|
+
Hash[keys.zip([value] * keys.size)]
|
123
234
|
end
|
124
235
|
end
|
125
236
|
end
|