dry-configurable 0.9.0 → 0.11.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 +50 -21
- data/LICENSE +1 -1
- data/README.md +15 -27
- data/dry-configurable.gemspec +27 -18
- data/lib/dry-configurable.rb +2 -0
- data/lib/dry/configurable.rb +21 -146
- data/lib/dry/configurable/class_methods.rb +103 -0
- data/lib/dry/configurable/compiler.rb +45 -0
- data/lib/dry/configurable/config.rb +78 -136
- data/lib/dry/configurable/constants.rb +12 -0
- data/lib/dry/configurable/dsl.rb +62 -0
- data/lib/dry/configurable/dsl/args.rb +58 -0
- data/lib/dry/configurable/{error.rb → errors.rb} +5 -1
- data/lib/dry/configurable/instance_methods.rb +46 -0
- data/lib/dry/configurable/methods.rb +32 -0
- data/lib/dry/configurable/setting.rb +91 -18
- data/lib/dry/configurable/settings.rb +42 -87
- data/lib/dry/configurable/test_interface.rb +3 -5
- data/lib/dry/configurable/version.rb +3 -1
- metadata +30 -25
- data/.codeclimate.yml +0 -12
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
- data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
- data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
- data/.github/workflows/ci.yml +0 -70
- data/.github/workflows/docsite.yml +0 -34
- data/.github/workflows/sync_configs.yml +0 -30
- data/.gitignore +0 -9
- data/.rspec +0 -4
- data/.rubocop.yml +0 -89
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -20
- data/Rakefile +0 -12
- data/docsite/source/index.html.md +0 -55
- data/docsite/source/testing.html.md +0 -27
- data/lib/dry/configurable/settings/argument_parser.rb +0 -50
- data/rakelib/rubocop.rake +0 -18
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/configurable/setting'
|
4
|
+
require 'dry/configurable/settings'
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Configurable
|
8
|
+
# Setting compiler used internally by the DSL
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Compiler
|
12
|
+
def call(ast)
|
13
|
+
Settings.new.tap do |settings|
|
14
|
+
ast.each do |node|
|
15
|
+
settings << visit(node)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def visit(node)
|
22
|
+
type, rest = node
|
23
|
+
public_send(:"visit_#{type}", rest)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def visit_constructor(node)
|
28
|
+
setting, constructor = node
|
29
|
+
visit(setting).with(constructor: constructor)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def visit_setting(node)
|
34
|
+
name, default, opts = node
|
35
|
+
Setting.new(name, **opts, default: default)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
def visit_nested(node)
|
40
|
+
parent, children = node
|
41
|
+
visit(parent).nested(call(children))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,112 +1,41 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
module Configurable
|
5
|
-
# @private
|
6
|
-
class Config
|
7
|
-
class << self
|
8
|
-
# @private
|
9
|
-
def [](settings)
|
10
|
-
::Class.new(Config) do
|
11
|
-
@settings = settings
|
12
|
-
singleton_class.send(:attr_reader, :settings)
|
13
|
-
|
14
|
-
@lock = ::Mutex.new
|
15
|
-
@config_defined = false
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# @private
|
20
|
-
def define_accessors!
|
21
|
-
@lock.synchronize do
|
22
|
-
break if config_defined?
|
23
|
-
|
24
|
-
settings.each do |setting|
|
25
|
-
next if setting.reserved?
|
26
|
-
|
27
|
-
define_method(setting.name) do
|
28
|
-
@config[setting.name]
|
29
|
-
end
|
30
|
-
|
31
|
-
define_method("#{setting.name}=") do |value|
|
32
|
-
raise FrozenConfig, 'Cannot modify frozen config' if frozen?
|
33
|
-
@config[setting.name] = setting.processor.(value)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
@config_defined = true
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# @private
|
42
|
-
def config_defined?
|
43
|
-
@config_defined
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def initialize
|
48
|
-
@config = ::Concurrent::Hash.new
|
49
|
-
@lock = ::Mutex.new
|
50
|
-
@defined = false
|
51
|
-
end
|
3
|
+
require 'concurrent/map'
|
52
4
|
|
53
|
-
|
54
|
-
@defined
|
55
|
-
end
|
5
|
+
require 'dry/equalizer'
|
56
6
|
|
57
|
-
|
58
|
-
|
59
|
-
@lock.synchronize do
|
60
|
-
break if self.defined?
|
7
|
+
require 'dry/configurable/constants'
|
8
|
+
require 'dry/configurable/errors'
|
61
9
|
|
62
|
-
|
63
|
-
|
10
|
+
module Dry
|
11
|
+
module Configurable
|
12
|
+
# Config exposes setting values through a convenient API
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
class Config
|
16
|
+
include Dry::Equalizer(:values)
|
64
17
|
|
65
|
-
|
66
|
-
|
18
|
+
# @api private
|
19
|
+
attr_reader :settings
|
67
20
|
|
68
|
-
|
69
|
-
|
21
|
+
# @api private
|
22
|
+
attr_reader :resolved
|
70
23
|
|
71
|
-
# @private
|
72
|
-
def
|
73
|
-
|
74
|
-
@
|
75
|
-
freeze
|
24
|
+
# @api private
|
25
|
+
def initialize(settings)
|
26
|
+
@settings = settings.dup
|
27
|
+
@resolved = Concurrent::Map.new
|
76
28
|
end
|
77
29
|
|
78
|
-
# Serialize config to a Hash
|
79
|
-
#
|
80
|
-
# @return [Hash]
|
81
|
-
#
|
82
|
-
# @api public
|
83
|
-
def to_h
|
84
|
-
@config.each_with_object({}) do |(key, value), hash|
|
85
|
-
case value
|
86
|
-
when Config
|
87
|
-
hash[key] = value.to_h
|
88
|
-
else
|
89
|
-
hash[key] = value
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
alias to_hash to_h
|
94
|
-
|
95
30
|
# Get config value by a key
|
96
31
|
#
|
97
32
|
# @param [String,Symbol] name
|
98
33
|
#
|
99
34
|
# @return Config value
|
100
35
|
def [](name)
|
101
|
-
setting
|
36
|
+
raise ArgumentError, "+#{name}+ is not a setting name" unless settings.key?(name)
|
102
37
|
|
103
|
-
|
104
|
-
raise_unknown_setting_error(name)
|
105
|
-
elsif setting.reserved?
|
106
|
-
@config[setting.name]
|
107
|
-
else
|
108
|
-
public_send(name)
|
109
|
-
end
|
38
|
+
settings[name].value
|
110
39
|
end
|
111
40
|
|
112
41
|
# Set config value.
|
@@ -115,70 +44,83 @@ module Dry
|
|
115
44
|
# @param [String,Symbol] name
|
116
45
|
# @param [Object] value
|
117
46
|
def []=(name, value)
|
118
|
-
|
119
|
-
|
120
|
-
if setting.nil?
|
121
|
-
raise_unknown_setting_error(name)
|
122
|
-
elsif setting.reserved?
|
123
|
-
@config[setting.name] = setting.processor.(value)
|
124
|
-
else
|
125
|
-
public_send("#{name}=", value)
|
126
|
-
end
|
47
|
+
public_send(:"#{name}=", value)
|
127
48
|
end
|
128
49
|
|
129
|
-
#
|
50
|
+
# Update config with new values
|
130
51
|
#
|
131
|
-
# @param [
|
132
|
-
# @return [Bool]
|
133
|
-
def key?(name)
|
134
|
-
self.class.settings.name?(name)
|
135
|
-
end
|
136
|
-
|
137
|
-
# Recursively update values from a hash
|
52
|
+
# @param [Hash] A hash with new values
|
138
53
|
#
|
139
|
-
# @param [Hash] values to set
|
140
54
|
# @return [Config]
|
55
|
+
#
|
56
|
+
# @api public
|
141
57
|
def update(values)
|
142
58
|
values.each do |key, value|
|
143
|
-
|
59
|
+
case value
|
60
|
+
when Hash
|
144
61
|
self[key].update(value)
|
145
62
|
else
|
146
63
|
self[key] = value
|
147
64
|
end
|
148
65
|
end
|
149
|
-
self
|
150
66
|
end
|
151
67
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
68
|
+
# Dump config into a hash
|
69
|
+
#
|
70
|
+
# @return [Hash]
|
71
|
+
#
|
72
|
+
# @api public
|
73
|
+
def values
|
74
|
+
settings
|
75
|
+
.map { |setting| [setting.name, setting.value] }
|
76
|
+
.map { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
|
77
|
+
.to_h
|
78
|
+
end
|
79
|
+
alias_method :to_h, :values
|
80
|
+
alias_method :to_hash, :values
|
81
|
+
|
82
|
+
# @api private
|
83
|
+
def finalize!
|
84
|
+
settings.freeze
|
85
|
+
freeze
|
86
|
+
end
|
87
|
+
|
88
|
+
# @api private
|
89
|
+
def pristine
|
90
|
+
self.class.new(settings.pristine)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
def respond_to_missing?(meth, include_private = false)
|
95
|
+
super || settings.key?(resolve(meth))
|
158
96
|
end
|
159
97
|
|
160
98
|
private
|
161
99
|
|
162
|
-
# @private
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
self[setting.name] = setting.value
|
175
|
-
end
|
100
|
+
# @api private
|
101
|
+
def method_missing(meth, *args)
|
102
|
+
setting = settings[resolve(meth)]
|
103
|
+
|
104
|
+
super unless setting
|
105
|
+
|
106
|
+
if setting.writer?(meth)
|
107
|
+
raise FrozenConfig, 'Cannot modify frozen config' if frozen?
|
108
|
+
|
109
|
+
settings << setting.with(input: args[0])
|
110
|
+
else
|
111
|
+
setting.value
|
176
112
|
end
|
177
113
|
end
|
178
114
|
|
179
|
-
# @private
|
180
|
-
def
|
181
|
-
|
115
|
+
# @api private
|
116
|
+
def resolve(meth)
|
117
|
+
resolved.fetch(meth) { resolved[meth] = meth.to_s.tr('=', '').to_sym }
|
118
|
+
end
|
119
|
+
|
120
|
+
# @api private
|
121
|
+
def initialize_copy(source)
|
122
|
+
super
|
123
|
+
@settings = source.settings.dup
|
182
124
|
end
|
183
125
|
end
|
184
126
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/configurable/constants'
|
4
|
+
require 'dry/configurable/setting'
|
5
|
+
require 'dry/configurable/settings'
|
6
|
+
require 'dry/configurable/compiler'
|
7
|
+
require 'dry/configurable/dsl/args'
|
8
|
+
|
9
|
+
module Dry
|
10
|
+
module Configurable
|
11
|
+
# Setting DSL used by the class API
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class DSL
|
15
|
+
VALID_NAME = /\A[a-z_]\w*\z/i.freeze
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
attr_reader :compiler
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
attr_reader :ast
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def initialize(&block)
|
25
|
+
@compiler = Compiler.new
|
26
|
+
@ast = []
|
27
|
+
instance_exec(&block) if block
|
28
|
+
end
|
29
|
+
|
30
|
+
# Register a new setting node and compile it into a setting object
|
31
|
+
#
|
32
|
+
# @see ClassMethods.setting
|
33
|
+
# @api public
|
34
|
+
# @return Setting
|
35
|
+
def setting(name, *args, &block)
|
36
|
+
unless VALID_NAME.match?(name.to_s)
|
37
|
+
raise ArgumentError, "#{name} is not a valid setting name"
|
38
|
+
end
|
39
|
+
|
40
|
+
args = Args.new(args)
|
41
|
+
|
42
|
+
args.ensure_valid_options
|
43
|
+
|
44
|
+
default, opts = args
|
45
|
+
|
46
|
+
node = [:setting, [name, default, opts == default ? EMPTY_HASH : opts]]
|
47
|
+
|
48
|
+
if block
|
49
|
+
if block.arity.zero?
|
50
|
+
ast << [:nested, [node, DSL.new(&block).ast]]
|
51
|
+
else
|
52
|
+
ast << [:constructor, [node, block]]
|
53
|
+
end
|
54
|
+
else
|
55
|
+
ast << node
|
56
|
+
end
|
57
|
+
|
58
|
+
compiler.visit(ast.last)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/configurable/constants'
|
4
|
+
require 'dry/configurable/setting'
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Configurable
|
8
|
+
class DSL
|
9
|
+
# @api private
|
10
|
+
class Args
|
11
|
+
# @api private
|
12
|
+
attr_reader :args
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
attr_reader :size
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
attr_reader :opts
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def initialize(args)
|
22
|
+
@args = args
|
23
|
+
@size = args.size
|
24
|
+
@opts = Setting::OPTIONS
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def ensure_valid_options
|
29
|
+
return unless options
|
30
|
+
|
31
|
+
keys = options.keys - opts
|
32
|
+
raise ArgumentError, "Invalid options: #{keys.inspect}" unless keys.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def to_ary
|
37
|
+
[default, options || EMPTY_HASH]
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def default
|
42
|
+
if size.equal?(1) && options.nil?
|
43
|
+
args[0]
|
44
|
+
elsif size > 1 && options
|
45
|
+
args[0]
|
46
|
+
else
|
47
|
+
Undefined
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
def options
|
53
|
+
args.detect { |arg| arg.is_a?(Hash) && (opts & arg.keys).any? }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|