dry-configurable 0.15.0 → 0.16.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 +4 -4
- data/CHANGELOG.md +33 -0
- data/LICENSE +1 -1
- data/dry-configurable.gemspec +1 -1
- data/lib/dry/configurable/class_methods.rb +27 -15
- data/lib/dry/configurable/compiler.rb +3 -4
- data/lib/dry/configurable/config.rb +90 -36
- data/lib/dry/configurable/dsl.rb +15 -103
- data/lib/dry/configurable/extension.rb +48 -0
- data/lib/dry/configurable/instance_methods.rb +1 -4
- data/lib/dry/configurable/methods.rb +0 -2
- data/lib/dry/configurable/setting.rb +23 -112
- data/lib/dry/configurable/settings.rb +9 -31
- data/lib/dry/configurable/version.rb +1 -1
- data/lib/dry/configurable.rb +25 -18
- metadata +13 -13
- data/lib/dry/configurable/flags.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 323f300c1cf0cf407272352b81dd0775ac7f855713cd6145fccc9d11dbdae8cf
|
4
|
+
data.tar.gz: b8258cfa9a30108831384e575a00e5afd93b9d94942d52799d23013e3753ceaf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d37acc7d39adb1ed1f0da218c5f16ac0e6d09832e70e73220770fa28ad8eff4f21f440a6285b22785de82f76a8df6f444d37024d5059fdc2ba3f3b3006fd19c9
|
7
|
+
data.tar.gz: c7888430539dfa30f4518ca159cae0675d2766ec8c27870a5e74c64f04521aa517fea1772bb766f5c9f364d83e24610c7bf7f72c65739adb05b2e9a0ca1230f0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
<!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
|
2
2
|
|
3
|
+
## 0.16.0 2022-10-08
|
4
|
+
|
5
|
+
|
6
|
+
### Added
|
7
|
+
|
8
|
+
- Support for custom config classes via `config_class:` option (#136 by @solnic)
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
extend Dry::Configurable(config_class: MyConfig)
|
12
|
+
```
|
13
|
+
|
14
|
+
Your config class should inherit from `Dry::Configurable::Config`.
|
15
|
+
- Return `Dry::Core::Constants::Undefined` (instead of nil) as the value for non-configured settings via a `default_undefined: true` option (#141 by @timriley)
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
extend Dry::Configurable(default_undefined: true)
|
19
|
+
```
|
20
|
+
|
21
|
+
You must opt into this feature via the `default_undefined: true` option. Non-configured setting values are still `nil` by default.
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
- Remove exec bit from version.rb (#139 by @Fryguy)
|
26
|
+
|
27
|
+
### Changed
|
28
|
+
|
29
|
+
- Improve memory usage by separating setting definitions from config values (#138 by @timriley)
|
30
|
+
|
31
|
+
Your usage of dry-configurable may be impacted if you have been accessing objects from `_settings` or the internals of `Dry::Configurable::Config`. `_settings` now returns `Dry::Configurable::Setting` instances, which contain only the details from the setting's definition. Setting _values_ remain in `Dry::Configurable::Config`.
|
32
|
+
- Use Zeitwerk to speed up load time (#135 by @solnic)
|
33
|
+
|
34
|
+
[Compare v0.15.0...v0.16.0](https://github.com/dry-rb/dry-configurable/compare/v0.15.0...v0.16.0)
|
35
|
+
|
3
36
|
## 0.15.0 2022-04-21
|
4
37
|
|
5
38
|
|
data/LICENSE
CHANGED
data/dry-configurable.gemspec
CHANGED
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.required_ruby_version = ">= 2.7.0"
|
30
30
|
|
31
31
|
# to update dependencies edit project.yml
|
32
|
-
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
33
32
|
spec.add_runtime_dependency "dry-core", "~> 0.6"
|
33
|
+
spec.add_runtime_dependency "zeitwerk", "~> 2.6"
|
34
34
|
|
35
35
|
spec.add_development_dependency "bundler"
|
36
36
|
spec.add_development_dependency "rake"
|
@@ -2,11 +2,6 @@
|
|
2
2
|
|
3
3
|
require "set"
|
4
4
|
|
5
|
-
require "dry/configurable/constants"
|
6
|
-
require "dry/configurable/dsl"
|
7
|
-
require "dry/configurable/methods"
|
8
|
-
require "dry/configurable/settings"
|
9
|
-
|
10
5
|
module Dry
|
11
6
|
module Configurable
|
12
7
|
module ClassMethods
|
@@ -16,8 +11,17 @@ module Dry
|
|
16
11
|
def inherited(subclass)
|
17
12
|
super
|
18
13
|
|
19
|
-
subclass.instance_variable_set(
|
20
|
-
|
14
|
+
subclass.instance_variable_set(:@__config_extension__, __config_extension__)
|
15
|
+
|
16
|
+
new_settings = _settings.dup
|
17
|
+
subclass.instance_variable_set(:@_settings, new_settings)
|
18
|
+
|
19
|
+
# Only classes **extending** Dry::Configurable have class-level config. When
|
20
|
+
# Dry::Configurable is **included**, the class-level config method is undefined because it
|
21
|
+
# resides at the instance-level instead (see `Configurable.included`).
|
22
|
+
if respond_to?(:config)
|
23
|
+
subclass.instance_variable_set(:@config, config.dup_for_settings(new_settings))
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
27
|
# Add a setting to the configuration
|
@@ -52,7 +56,7 @@ module Dry
|
|
52
56
|
#
|
53
57
|
# @api public
|
54
58
|
def settings
|
55
|
-
|
59
|
+
Set[*_settings.map(&:name)]
|
56
60
|
end
|
57
61
|
|
58
62
|
# Return declared settings
|
@@ -70,17 +74,25 @@ module Dry
|
|
70
74
|
#
|
71
75
|
# @api public
|
72
76
|
def config
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
@config ||= __config_build__
|
78
|
+
end
|
79
|
+
|
80
|
+
# @api private
|
81
|
+
def __config_build__(settings = _settings)
|
82
|
+
__config_extension__.config_class.new(settings)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def __config_extension__
|
87
|
+
@__config_extension__
|
79
88
|
end
|
80
89
|
|
81
90
|
# @api private
|
82
91
|
def __config_dsl__
|
83
|
-
@__config_dsl__ ||= DSL.new
|
92
|
+
@__config_dsl__ ||= DSL.new(
|
93
|
+
config_class: __config_extension__.config_class,
|
94
|
+
default_undefined: __config_extension__.default_undefined
|
95
|
+
)
|
84
96
|
end
|
85
97
|
|
86
98
|
# @api private
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/configurable/setting"
|
4
|
-
require "dry/configurable/settings"
|
5
|
-
|
6
3
|
module Dry
|
7
4
|
module Configurable
|
8
5
|
# Setting compiler used internally by the DSL
|
@@ -32,7 +29,9 @@ module Dry
|
|
32
29
|
# @api private
|
33
30
|
def visit_nested(node)
|
34
31
|
parent, children = node
|
35
|
-
|
32
|
+
name, opts = parent[1]
|
33
|
+
|
34
|
+
Setting.new(name, **opts, children: Settings.new(call(children)))
|
36
35
|
end
|
37
36
|
end
|
38
37
|
end
|
@@ -1,12 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "dry/core/constants"
|
4
4
|
|
5
5
|
require "dry/core/equalizer"
|
6
6
|
|
7
|
-
require "dry/configurable/constants"
|
8
|
-
require "dry/configurable/errors"
|
9
|
-
|
10
7
|
module Dry
|
11
8
|
module Configurable
|
12
9
|
# Config exposes setting values through a convenient API
|
@@ -19,12 +16,17 @@ module Dry
|
|
19
16
|
attr_reader :_settings
|
20
17
|
|
21
18
|
# @api private
|
22
|
-
attr_reader :
|
19
|
+
attr_reader :_values
|
23
20
|
|
24
21
|
# @api private
|
25
|
-
def initialize(settings)
|
22
|
+
def initialize(settings, values: {})
|
26
23
|
@_settings = settings
|
27
|
-
@
|
24
|
+
@_values = values
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def dup_for_settings(settings)
|
29
|
+
self.class.new(settings, values: dup_values)
|
28
30
|
end
|
29
31
|
|
30
32
|
# Get config value by a key
|
@@ -34,9 +36,18 @@ module Dry
|
|
34
36
|
# @return Config value
|
35
37
|
def [](name)
|
36
38
|
name = name.to_sym
|
37
|
-
raise ArgumentError, "+#{name}+ is not a setting name" unless _settings.key?(name)
|
38
39
|
|
39
|
-
_settings[name]
|
40
|
+
unless (setting = _settings[name])
|
41
|
+
raise ArgumentError, "+#{name}+ is not a setting name"
|
42
|
+
end
|
43
|
+
|
44
|
+
_values.fetch(name) {
|
45
|
+
# When reading values, only capture cloneable (i.e. mutable) values in local state, making
|
46
|
+
# it easier to determine which values have actually been changed vs just read
|
47
|
+
setting.to_value.tap { |value|
|
48
|
+
_values[name] = value if setting.cloneable?
|
49
|
+
}
|
50
|
+
}
|
40
51
|
end
|
41
52
|
|
42
53
|
# Set config value.
|
@@ -45,7 +56,15 @@ module Dry
|
|
45
56
|
# @param [String,Symbol] name
|
46
57
|
# @param [Object] value
|
47
58
|
def []=(name, value)
|
48
|
-
|
59
|
+
raise FrozenConfig, "Cannot modify frozen config" if frozen?
|
60
|
+
|
61
|
+
name = name.to_sym
|
62
|
+
|
63
|
+
unless (setting = _settings[name])
|
64
|
+
raise ArgumentError, "+#{name}+ is not a setting name"
|
65
|
+
end
|
66
|
+
|
67
|
+
_values[name] = setting.constructor.(value)
|
49
68
|
end
|
50
69
|
|
51
70
|
# Update config with new values
|
@@ -70,61 +89,96 @@ module Dry
|
|
70
89
|
self
|
71
90
|
end
|
72
91
|
|
73
|
-
#
|
92
|
+
# Returns true if the value for the given key has been set on this config.
|
93
|
+
#
|
94
|
+
# For simple values, this returns true if the value has been explicitly assigned.
|
95
|
+
#
|
96
|
+
# For cloneable (mutable) values, since these are captured on read, returns true if the value
|
97
|
+
# does not compare equally to its corresdponing default value. This relies on these objects
|
98
|
+
# having functioning `#==` checks.
|
99
|
+
#
|
100
|
+
# @return [Bool]
|
101
|
+
#
|
102
|
+
# @api public
|
103
|
+
def configured?(key)
|
104
|
+
if _settings[key].cloneable? && _values.key?(key)
|
105
|
+
return _values[key] != _settings[key].to_value
|
106
|
+
end
|
107
|
+
|
108
|
+
_values.key?(key)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the current config values.
|
112
|
+
#
|
113
|
+
# Nested configs remain in their {Config} instances.
|
74
114
|
#
|
75
115
|
# @return [Hash]
|
76
116
|
#
|
77
117
|
# @api public
|
78
118
|
def values
|
79
|
-
_settings
|
80
|
-
|
81
|
-
|
82
|
-
|
119
|
+
_settings.to_h { |setting| [setting.name, self[setting.name]] }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns config values as a hash, with nested values also converted from {Config} instances
|
123
|
+
# into hashes.
|
124
|
+
#
|
125
|
+
# @return [Hash]
|
126
|
+
#
|
127
|
+
# @api public
|
128
|
+
def to_h
|
129
|
+
values.to_h { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
|
83
130
|
end
|
84
|
-
alias_method :to_h, :values
|
85
131
|
|
86
132
|
# @api private
|
87
133
|
def finalize!(freeze_values: false)
|
88
|
-
|
134
|
+
values.each_value do |value|
|
135
|
+
if value.is_a?(self.class)
|
136
|
+
value.finalize!(freeze_values: freeze_values)
|
137
|
+
elsif freeze_values
|
138
|
+
value.freeze
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
89
142
|
freeze
|
90
143
|
end
|
91
144
|
|
92
145
|
# @api private
|
93
146
|
def pristine
|
94
|
-
self.class.new(_settings
|
95
|
-
end
|
96
|
-
|
97
|
-
# @api private
|
98
|
-
def respond_to_missing?(meth, include_private = false)
|
99
|
-
super || _settings.key?(resolve(meth))
|
147
|
+
self.class.new(_settings)
|
100
148
|
end
|
101
149
|
|
102
150
|
private
|
103
151
|
|
104
|
-
|
105
|
-
|
106
|
-
setting = _settings[
|
152
|
+
def method_missing(name, *args)
|
153
|
+
setting_name = setting_name_from_method(name)
|
154
|
+
setting = _settings[setting_name]
|
107
155
|
|
108
156
|
super unless setting
|
109
157
|
|
110
|
-
if
|
111
|
-
|
112
|
-
|
113
|
-
_settings << setting.with(input: args[0])
|
158
|
+
if name.end_with?("=")
|
159
|
+
self[setting_name] = args[0]
|
114
160
|
else
|
115
|
-
|
161
|
+
self[setting_name]
|
116
162
|
end
|
117
163
|
end
|
118
164
|
|
119
|
-
|
120
|
-
|
121
|
-
|
165
|
+
def respond_to_missing?(meth, include_private = false)
|
166
|
+
_settings.key?(setting_name_from_method(meth)) || super
|
167
|
+
end
|
168
|
+
|
169
|
+
def setting_name_from_method(method_name)
|
170
|
+
method_name.to_s.tr("=", "").to_sym
|
171
|
+
end
|
172
|
+
|
173
|
+
def dup_values
|
174
|
+
_values.each_with_object({}) { |(key, val), dup_hsh|
|
175
|
+
dup_hsh[key] = _settings[key].cloneable? ? val.dup : val
|
176
|
+
}
|
122
177
|
end
|
123
178
|
|
124
|
-
# @api private
|
125
179
|
def initialize_copy(source)
|
126
180
|
super
|
127
|
-
@
|
181
|
+
@_values = source.__send__(:dup_values)
|
128
182
|
end
|
129
183
|
end
|
130
184
|
end
|
data/lib/dry/configurable/dsl.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/configurable/constants"
|
4
|
-
require "dry/configurable/flags"
|
5
|
-
require "dry/configurable/setting"
|
6
|
-
require "dry/configurable/settings"
|
7
|
-
require "dry/configurable/compiler"
|
8
3
|
require "dry/core/deprecations"
|
9
4
|
|
10
5
|
module Dry
|
@@ -15,16 +10,16 @@ module Dry
|
|
15
10
|
class DSL
|
16
11
|
VALID_NAME = /\A[a-z_]\w*\z/i.freeze
|
17
12
|
|
18
|
-
# @api private
|
19
13
|
attr_reader :compiler
|
20
14
|
|
21
|
-
# @api private
|
22
15
|
attr_reader :ast
|
23
16
|
|
24
|
-
|
25
|
-
|
17
|
+
attr_reader :options
|
18
|
+
|
19
|
+
def initialize(**options, &block)
|
26
20
|
@compiler = Compiler.new
|
27
21
|
@ast = []
|
22
|
+
@options = options
|
28
23
|
instance_exec(&block) if block
|
29
24
|
end
|
30
25
|
|
@@ -33,106 +28,15 @@ module Dry
|
|
33
28
|
# @see ClassMethods.setting
|
34
29
|
# @api private
|
35
30
|
# @return Setting
|
36
|
-
def setting(name,
|
31
|
+
def setting(name, **options, &block) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
37
32
|
unless VALID_NAME.match?(name.to_s)
|
38
33
|
raise ArgumentError, "#{name} is not a valid setting name"
|
39
34
|
end
|
40
35
|
|
41
|
-
if default != Undefined
|
42
|
-
if Dry::Configurable.warn_on_setting_positional_default
|
43
|
-
Dry::Core::Deprecations.announce(
|
44
|
-
"default value as positional argument to settings",
|
45
|
-
"Provide a `default:` keyword argument instead",
|
46
|
-
tag: "dry-configurable",
|
47
|
-
uplevel: 2
|
48
|
-
)
|
49
|
-
end
|
50
|
-
|
51
|
-
options = options.merge(default: default)
|
52
|
-
end
|
53
|
-
|
54
|
-
if RUBY_VERSION < "3.0" &&
|
55
|
-
default == Undefined &&
|
56
|
-
(valid_opts, invalid_opts = valid_and_invalid_options(options)) &&
|
57
|
-
invalid_opts.any? &&
|
58
|
-
valid_opts.none?
|
59
|
-
# In Ruby 2.6 and 2.7, when a hash is given as the second positional argument
|
60
|
-
# (i.e. the hash is intended to be the setting's default value), and there are
|
61
|
-
# no other keyword arguments given, the hash is assigned to the `options`
|
62
|
-
# variable instead of `default`.
|
63
|
-
#
|
64
|
-
# For example, for this setting:
|
65
|
-
#
|
66
|
-
# setting :hash_setting, {my_hash: true}
|
67
|
-
#
|
68
|
-
# We'll have a `default` of `Undefined` and an `options` of `{my_hash: true}`
|
69
|
-
#
|
70
|
-
# If any additional keyword arguments are provided, e.g.:
|
71
|
-
#
|
72
|
-
# setting :hash_setting, {my_hash: true}, reader: true
|
73
|
-
#
|
74
|
-
# Then we'll have a `default` of `{my_hash: true}` and an `options` of `{reader:
|
75
|
-
# true}`, which is what we want.
|
76
|
-
#
|
77
|
-
# To work around that first case and ensure our (deprecated) backwards
|
78
|
-
# compatibility holds for Ruby 2.6 and 2.7, we extract all invalid options from
|
79
|
-
# `options`, and if there are no remaining valid options (i.e. if there were no
|
80
|
-
# keyword arguments given), then we can infer the invalid options to be a
|
81
|
-
# default hash value for the setting.
|
82
|
-
#
|
83
|
-
# This approach also preserves the behavior of raising an ArgumentError when a
|
84
|
-
# distinct hash is _not_ intentionally provided as the second positional
|
85
|
-
# argument (i.e. it's not enclosed in braces), and instead invalid keyword
|
86
|
-
# arguments are given alongside valid ones. So this setting:
|
87
|
-
#
|
88
|
-
# setting :some_setting, invalid_option: true, reader: true
|
89
|
-
#
|
90
|
-
# Would raise an ArgumentError as expected.
|
91
|
-
#
|
92
|
-
# However, the one case we can't catch here is when invalid options are supplied
|
93
|
-
# without hash literal braces, but there are no other keyword arguments
|
94
|
-
# supplied. In this case, a setting like:
|
95
|
-
#
|
96
|
-
# setting :hash_setting, my_hash: true
|
97
|
-
#
|
98
|
-
# Is parsed identically to the first case described above:
|
99
|
-
#
|
100
|
-
# setting :hash_setting, {my_hash: true}
|
101
|
-
#
|
102
|
-
# So in both of these cases, the default value will become `{my_hash: true}`. We
|
103
|
-
# consider this unlikely to be a problem in practice, since users are not likely
|
104
|
-
# to be providing invalid options to `setting` and expecting them to be ignored.
|
105
|
-
# Additionally, the deprecation messages will make the new behavior obvious, and
|
106
|
-
# encourage the users to upgrade their setting definitions.
|
107
|
-
|
108
|
-
if Dry::Configurable.warn_on_setting_positional_default
|
109
|
-
Dry::Core::Deprecations.announce(
|
110
|
-
"default value as positional argument to settings",
|
111
|
-
"Provide a `default:` keyword argument instead",
|
112
|
-
tag: "dry-configurable",
|
113
|
-
uplevel: 2
|
114
|
-
)
|
115
|
-
end
|
116
|
-
|
117
|
-
options = {default: invalid_opts}
|
118
|
-
end
|
119
|
-
|
120
|
-
if block && !block.arity.zero?
|
121
|
-
if Dry::Configurable.warn_on_setting_constructor_block
|
122
|
-
Dry::Core::Deprecations.announce(
|
123
|
-
"passing a constructor as a block",
|
124
|
-
"Provide a `constructor:` keyword argument instead",
|
125
|
-
tag: "dry-configurable",
|
126
|
-
uplevel: 2
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
options = options.merge(constructor: block)
|
131
|
-
block = nil
|
132
|
-
end
|
133
|
-
|
134
36
|
ensure_valid_options(options)
|
135
37
|
|
38
|
+
options = {default: default, config_class: config_class, **options}
|
39
|
+
|
136
40
|
node = [:setting, [name.to_sym, options]]
|
137
41
|
|
138
42
|
if block
|
@@ -144,6 +48,14 @@ module Dry
|
|
144
48
|
compiler.visit(ast.last)
|
145
49
|
end
|
146
50
|
|
51
|
+
def config_class
|
52
|
+
options[:config_class]
|
53
|
+
end
|
54
|
+
|
55
|
+
def default
|
56
|
+
options[:default_undefined] ? Undefined : nil
|
57
|
+
end
|
58
|
+
|
147
59
|
private
|
148
60
|
|
149
61
|
def ensure_valid_options(options)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Configurable
|
5
|
+
class Extension < Module
|
6
|
+
# @api private
|
7
|
+
attr_reader :config_class
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
attr_reader :default_undefined
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def initialize(config_class: Configurable::Config, default_undefined: false)
|
14
|
+
super()
|
15
|
+
@config_class = config_class
|
16
|
+
@default_undefined = default_undefined
|
17
|
+
freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def extended(klass)
|
22
|
+
super
|
23
|
+
klass.extend(ClassMethods)
|
24
|
+
klass.instance_variable_set(:@__config_extension__, self)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def included(klass)
|
29
|
+
raise AlreadyIncluded if klass.include?(InstanceMethods)
|
30
|
+
|
31
|
+
super
|
32
|
+
|
33
|
+
klass.class_eval do
|
34
|
+
extend(ClassMethods)
|
35
|
+
include(InstanceMethods)
|
36
|
+
prepend(Initializer)
|
37
|
+
|
38
|
+
class << self
|
39
|
+
undef :config
|
40
|
+
undef :configure
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
klass.instance_variable_set(:@__config_extension__, self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -12,10 +12,7 @@ module Dry
|
|
12
12
|
module Initializer
|
13
13
|
# @api private
|
14
14
|
def initialize(*)
|
15
|
-
|
16
|
-
# this instance. This does mean that any settings defined on the class _after_
|
17
|
-
# initialization will not be available on the instance.
|
18
|
-
@config = Config.new(self.class._settings.dup)
|
15
|
+
@config = self.class.__config_build__(self.class._settings)
|
19
16
|
|
20
17
|
super
|
21
18
|
end
|
@@ -4,18 +4,15 @@ require "set"
|
|
4
4
|
|
5
5
|
require "dry/core/equalizer"
|
6
6
|
|
7
|
-
require "dry/configurable/constants"
|
8
|
-
require "dry/configurable/config"
|
9
|
-
|
10
7
|
module Dry
|
11
8
|
module Configurable
|
12
9
|
# This class represents a setting and is used internally.
|
13
10
|
#
|
14
11
|
# @api private
|
15
12
|
class Setting
|
16
|
-
include Dry::Equalizer(:name, :
|
13
|
+
include Dry::Equalizer(:name, :default, :constructor, :children, :options, inspect: false)
|
17
14
|
|
18
|
-
OPTIONS = %i[
|
15
|
+
OPTIONS = %i[default reader constructor cloneable settings config_class].freeze
|
19
16
|
|
20
17
|
DEFAULT_CONSTRUCTOR = -> v { v }.freeze
|
21
18
|
|
@@ -25,105 +22,35 @@ module Dry
|
|
25
22
|
attr_reader :name
|
26
23
|
|
27
24
|
# @api private
|
28
|
-
attr_reader :
|
25
|
+
attr_reader :default
|
29
26
|
|
30
27
|
# @api private
|
31
|
-
attr_reader :
|
28
|
+
attr_reader :constructor
|
32
29
|
|
33
30
|
# @api private
|
34
|
-
attr_reader :
|
31
|
+
attr_reader :children
|
35
32
|
|
36
33
|
# @api private
|
37
34
|
attr_reader :options
|
38
35
|
|
39
|
-
# Specialized Setting which includes nested settings
|
40
|
-
#
|
41
|
-
# @api private
|
42
|
-
class Nested < Setting
|
43
|
-
CONSTRUCTOR = Config.method(:new)
|
44
|
-
|
45
|
-
# @api private
|
46
|
-
def pristine
|
47
|
-
with(input: input.pristine)
|
48
|
-
end
|
49
|
-
|
50
|
-
# @api private
|
51
|
-
def constructor
|
52
|
-
CONSTRUCTOR
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
36
|
# @api private
|
57
37
|
def self.cloneable_value?(value)
|
58
38
|
CLONEABLE_VALUE_TYPES.any? { |type| value.is_a?(type) }
|
59
39
|
end
|
60
40
|
|
61
41
|
# @api private
|
62
|
-
def initialize(
|
42
|
+
def initialize(
|
43
|
+
name,
|
44
|
+
default:,
|
45
|
+
constructor: DEFAULT_CONSTRUCTOR,
|
46
|
+
children: EMPTY_ARRAY,
|
47
|
+
**options
|
48
|
+
)
|
63
49
|
@name = name
|
64
|
-
@writer_name = :"#{name}="
|
65
|
-
@options = options
|
66
|
-
|
67
|
-
# Setting collections (see `Settings`) are shared between the configurable class
|
68
|
-
# and its `config` object, so for cloneable individual settings, we duplicate
|
69
|
-
# their _values_ as early as possible to ensure no impact from unintended mutation
|
70
|
-
@input = input
|
71
50
|
@default = default
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
evaluate if input_defined?
|
78
|
-
end
|
79
|
-
|
80
|
-
# @api private
|
81
|
-
def input_defined?
|
82
|
-
!input.equal?(Undefined)
|
83
|
-
end
|
84
|
-
|
85
|
-
# @api private
|
86
|
-
def value
|
87
|
-
return @value if evaluated?
|
88
|
-
|
89
|
-
@value = constructor[Undefined.coalesce(input, default, nil)]
|
90
|
-
end
|
91
|
-
alias_method :evaluate, :value
|
92
|
-
private :evaluate
|
93
|
-
|
94
|
-
# @api private
|
95
|
-
def evaluated?
|
96
|
-
instance_variable_defined?(:@value)
|
97
|
-
end
|
98
|
-
|
99
|
-
# @api private
|
100
|
-
def nested(settings)
|
101
|
-
Nested.new(name, input: settings, **options)
|
102
|
-
end
|
103
|
-
|
104
|
-
# @api private
|
105
|
-
def pristine
|
106
|
-
with(input: Undefined)
|
107
|
-
end
|
108
|
-
|
109
|
-
# @api private
|
110
|
-
def finalize!(freeze_values: false)
|
111
|
-
if value.is_a?(Config)
|
112
|
-
value.finalize!(freeze_values: freeze_values)
|
113
|
-
elsif freeze_values
|
114
|
-
value.freeze
|
115
|
-
end
|
116
|
-
freeze
|
117
|
-
end
|
118
|
-
|
119
|
-
# @api private
|
120
|
-
def with(new_opts)
|
121
|
-
self.class.new(name, input: input, default: default, **options, **new_opts)
|
122
|
-
end
|
123
|
-
|
124
|
-
# @api private
|
125
|
-
def constructor
|
126
|
-
options[:constructor] || DEFAULT_CONSTRUCTOR
|
51
|
+
@constructor = constructor
|
52
|
+
@children = children
|
53
|
+
@options = options
|
127
54
|
end
|
128
55
|
|
129
56
|
# @api private
|
@@ -131,36 +58,20 @@ module Dry
|
|
131
58
|
options[:reader].equal?(true)
|
132
59
|
end
|
133
60
|
|
134
|
-
# @api private
|
135
|
-
def writer?(meth)
|
136
|
-
writer_name.equal?(meth)
|
137
|
-
end
|
138
|
-
|
139
61
|
# @api private
|
140
62
|
def cloneable?
|
141
|
-
|
142
|
-
# Return cloneable option if explicitly set
|
143
|
-
options[:cloneable]
|
144
|
-
else
|
145
|
-
# Otherwise, infer cloneable from any of the input, default, or value
|
146
|
-
Setting.cloneable_value?(input) || Setting.cloneable_value?(default) || (
|
147
|
-
evaluated? && Setting.cloneable_value?(value)
|
148
|
-
)
|
149
|
-
end
|
63
|
+
children.any? || options.fetch(:cloneable) { Setting.cloneable_value?(default) }
|
150
64
|
end
|
151
65
|
|
152
|
-
private
|
153
|
-
|
154
66
|
# @api private
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
67
|
+
def to_value
|
68
|
+
if children.any?
|
69
|
+
(options[:config_class] || Config).new(children)
|
70
|
+
else
|
71
|
+
value = default
|
72
|
+
value = constructor.(value) unless value.eql?(Undefined)
|
159
73
|
|
160
|
-
|
161
|
-
@input = source.input.dup
|
162
|
-
@default = source.default.dup
|
163
|
-
@value = source.value.dup if source.evaluated?
|
74
|
+
cloneable? ? value.dup : value
|
164
75
|
end
|
165
76
|
end
|
166
77
|
end
|
@@ -1,9 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent/map"
|
4
|
-
|
5
3
|
require "dry/core/equalizer"
|
6
|
-
require "dry/configurable/constants"
|
7
4
|
|
8
5
|
module Dry
|
9
6
|
module Configurable
|
@@ -11,27 +8,27 @@ module Dry
|
|
11
8
|
#
|
12
9
|
# @api private
|
13
10
|
class Settings
|
14
|
-
include Dry::Equalizer(:
|
11
|
+
include Dry::Equalizer(:settings)
|
15
12
|
|
16
13
|
include Enumerable
|
17
14
|
|
18
15
|
# @api private
|
19
|
-
attr_reader :
|
16
|
+
attr_reader :settings
|
20
17
|
|
21
18
|
# @api private
|
22
|
-
def initialize(
|
23
|
-
|
19
|
+
def initialize(settings = EMPTY_ARRAY)
|
20
|
+
@settings = settings.each_with_object({}) { |s, m| m[s.name] = s }
|
24
21
|
end
|
25
22
|
|
26
23
|
# @api private
|
27
24
|
def <<(setting)
|
28
|
-
|
25
|
+
settings[setting.name] = setting
|
29
26
|
self
|
30
27
|
end
|
31
28
|
|
32
29
|
# @api private
|
33
30
|
def [](name)
|
34
|
-
|
31
|
+
settings[name]
|
35
32
|
end
|
36
33
|
|
37
34
|
# @api private
|
@@ -41,37 +38,18 @@ module Dry
|
|
41
38
|
|
42
39
|
# @api private
|
43
40
|
def keys
|
44
|
-
|
41
|
+
settings.keys
|
45
42
|
end
|
46
43
|
|
47
44
|
# @api private
|
48
45
|
def each(&block)
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
# @api private
|
53
|
-
def pristine
|
54
|
-
self.class.new(map(&:pristine))
|
55
|
-
end
|
56
|
-
|
57
|
-
# @api private
|
58
|
-
def finalize!(freeze_values: false)
|
59
|
-
each { |element| element.finalize!(freeze_values: freeze_values) }
|
60
|
-
freeze
|
46
|
+
settings.each_value(&block)
|
61
47
|
end
|
62
48
|
|
63
49
|
private
|
64
50
|
|
65
|
-
# @api private
|
66
51
|
def initialize_copy(source)
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
# @api private
|
71
|
-
def initialize_elements(elements)
|
72
|
-
@elements = elements.each_with_object(Concurrent::Map.new) { |s, m|
|
73
|
-
m[s.name] = s
|
74
|
-
}
|
52
|
+
@settings = source.settings.dup
|
75
53
|
end
|
76
54
|
end
|
77
55
|
end
|
data/lib/dry/configurable.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "zeitwerk"
|
4
4
|
|
5
|
+
require "dry/core/constants"
|
5
6
|
require "dry/configurable/constants"
|
6
|
-
require "dry/configurable/class_methods"
|
7
|
-
require "dry/configurable/instance_methods"
|
8
|
-
require "dry/configurable/config"
|
9
|
-
require "dry/configurable/setting"
|
10
7
|
require "dry/configurable/errors"
|
11
8
|
|
12
9
|
module Dry
|
10
|
+
# @api public
|
11
|
+
def self.Configurable(**options)
|
12
|
+
Configurable::Extension.new(**options)
|
13
|
+
end
|
14
|
+
|
13
15
|
# A simple configuration mixin
|
14
16
|
#
|
15
17
|
# @example class-level configuration
|
@@ -44,27 +46,32 @@ module Dry
|
|
44
46
|
#
|
45
47
|
# @api public
|
46
48
|
module Configurable
|
49
|
+
def self.loader
|
50
|
+
@loader ||= Zeitwerk::Loader.new.tap do |loader|
|
51
|
+
root = File.expand_path("..", __dir__)
|
52
|
+
loader.tag = "dry-configurable"
|
53
|
+
loader.inflector = Zeitwerk::GemInflector.new("#{root}/dry-configurable.rb")
|
54
|
+
loader.push_dir(root)
|
55
|
+
loader.ignore(
|
56
|
+
"#{root}/dry-configurable.rb",
|
57
|
+
"#{root}/dry/configurable/{constants,errors,version}.rb"
|
58
|
+
)
|
59
|
+
loader.inflector.inflect("dsl" => "DSL")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
47
63
|
# @api private
|
48
64
|
def self.extended(klass)
|
49
65
|
super
|
50
|
-
klass.extend(
|
66
|
+
klass.extend(Extension.new)
|
51
67
|
end
|
52
68
|
|
53
69
|
# @api private
|
54
70
|
def self.included(klass)
|
55
|
-
raise AlreadyIncluded if klass.include?(InstanceMethods)
|
56
|
-
|
57
71
|
super
|
58
|
-
klass.
|
59
|
-
extend(ClassMethods)
|
60
|
-
include(InstanceMethods)
|
61
|
-
prepend(Initializer)
|
62
|
-
|
63
|
-
class << self
|
64
|
-
undef :config
|
65
|
-
undef :configure
|
66
|
-
end
|
67
|
-
end
|
72
|
+
klass.include(Extension.new)
|
68
73
|
end
|
74
|
+
|
75
|
+
loader.setup
|
69
76
|
end
|
70
77
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-configurable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Holland
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: dry-core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0.6'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: zeitwerk
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.6'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.6'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,7 +99,7 @@ files:
|
|
99
99
|
- lib/dry/configurable/constants.rb
|
100
100
|
- lib/dry/configurable/dsl.rb
|
101
101
|
- lib/dry/configurable/errors.rb
|
102
|
-
- lib/dry/configurable/
|
102
|
+
- lib/dry/configurable/extension.rb
|
103
103
|
- lib/dry/configurable/instance_methods.rb
|
104
104
|
- lib/dry/configurable/methods.rb
|
105
105
|
- lib/dry/configurable/setting.rb
|
@@ -114,7 +114,7 @@ metadata:
|
|
114
114
|
changelog_uri: https://github.com/dry-rb/dry-configurable/blob/main/CHANGELOG.md
|
115
115
|
source_code_uri: https://github.com/dry-rb/dry-configurable
|
116
116
|
bug_tracker_uri: https://github.com/dry-rb/dry-configurable/issues
|
117
|
-
post_install_message:
|
117
|
+
post_install_message:
|
118
118
|
rdoc_options: []
|
119
119
|
require_paths:
|
120
120
|
- lib
|
@@ -129,8 +129,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: '0'
|
131
131
|
requirements: []
|
132
|
-
rubygems_version: 3.
|
133
|
-
signing_key:
|
132
|
+
rubygems_version: 3.3.7
|
133
|
+
signing_key:
|
134
134
|
specification_version: 4
|
135
135
|
summary: A mixin to add configuration functionality to your classes
|
136
136
|
test_files: []
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "dry/core/class_attributes"
|
4
|
-
|
5
|
-
module Dry
|
6
|
-
module Configurable
|
7
|
-
extend Core::ClassAttributes
|
8
|
-
|
9
|
-
# Set to false to suppress deprecation warning when a setting default is provided as a
|
10
|
-
# positional argument
|
11
|
-
defines :warn_on_setting_positional_default
|
12
|
-
warn_on_setting_positional_default true
|
13
|
-
|
14
|
-
# Set to false to suppress deprecation warning when a setting constructor is provided
|
15
|
-
# as a block
|
16
|
-
defines :warn_on_setting_constructor_block
|
17
|
-
warn_on_setting_constructor_block true
|
18
|
-
end
|
19
|
-
end
|