dry-configurable 0.8.3 → 0.11.3
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 +98 -25
- data/LICENSE +1 -1
- data/README.md +15 -27
- data/dry-configurable.gemspec +28 -19
- data/lib/dry-configurable.rb +2 -0
- data/lib/dry/configurable.rb +21 -147
- data/lib/dry/configurable/class_methods.rb +103 -0
- data/lib/dry/configurable/compiler.rb +45 -0
- data/lib/dry/configurable/config.rb +80 -121
- 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 +95 -12
- data/lib/dry/configurable/settings.rb +43 -63
- data/lib/dry/configurable/test_interface.rb +3 -5
- data/lib/dry/configurable/version.rb +3 -1
- metadata +30 -16
- data/.codeclimate.yml +0 -23
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.travis.yml +0 -28
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -17
- data/Rakefile +0 -12
- data/lib/dry/configurable/settings/argument_parser.rb +0 -50
- data/rakelib/rubocop.rake +0 -18
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'dry/configurable/constants'
|
6
|
+
require 'dry/configurable/dsl'
|
7
|
+
require 'dry/configurable/methods'
|
8
|
+
require 'dry/configurable/settings'
|
9
|
+
|
10
|
+
module Dry
|
11
|
+
module Configurable
|
12
|
+
module ClassMethods
|
13
|
+
include Methods
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
def inherited(klass)
|
17
|
+
super
|
18
|
+
|
19
|
+
parent_settings = (respond_to?(:config) ? config._settings : _settings)
|
20
|
+
|
21
|
+
klass.instance_variable_set('@_settings', parent_settings)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a setting to the configuration
|
25
|
+
#
|
26
|
+
# @param [Mixed] key
|
27
|
+
# The accessor key for the configuration value
|
28
|
+
# @param [Mixed] default
|
29
|
+
# The default config value
|
30
|
+
#
|
31
|
+
# @yield
|
32
|
+
# If a block is given, it will be evaluated in the context of
|
33
|
+
# a new configuration class, and bound as the default value
|
34
|
+
#
|
35
|
+
# @return [Dry::Configurable::Config]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def setting(*args, &block)
|
39
|
+
setting = __config_dsl__.setting(*args, &block)
|
40
|
+
|
41
|
+
_settings << setting
|
42
|
+
|
43
|
+
__config_reader__.define(setting.name) if setting.reader?
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return declared settings
|
49
|
+
#
|
50
|
+
# @return [Set<Symbol>]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def settings
|
54
|
+
@settings ||= Set[*_settings.map(&:name)]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return declared settings
|
58
|
+
#
|
59
|
+
# @return [Settings]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def _settings
|
63
|
+
@_settings ||= Settings.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return configuration
|
67
|
+
#
|
68
|
+
# @return [Config]
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
def config
|
72
|
+
@config ||= Config.new(_settings)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def __config_dsl__
|
77
|
+
@dsl ||= DSL.new
|
78
|
+
end
|
79
|
+
|
80
|
+
# @api private
|
81
|
+
def __config_reader__
|
82
|
+
@__config_reader__ ||=
|
83
|
+
begin
|
84
|
+
reader = Module.new do
|
85
|
+
def self.define(name)
|
86
|
+
define_method(name) do
|
87
|
+
config[name]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if included_modules.include?(InstanceMethods)
|
93
|
+
include(reader)
|
94
|
+
end
|
95
|
+
|
96
|
+
extend(reader)
|
97
|
+
|
98
|
+
reader
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -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,94 +1,31 @@
|
|
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
|
-
define_method(setting.name) do
|
26
|
-
@config[setting.name]
|
27
|
-
end
|
28
|
-
|
29
|
-
define_method("#{setting.name}=") do |value|
|
30
|
-
raise FrozenConfig, 'Cannot modify frozen config' if frozen?
|
31
|
-
@config[setting.name] = setting.processor.(value)
|
32
|
-
end
|
33
|
-
end
|
3
|
+
require 'concurrent/map'
|
34
4
|
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# @private
|
40
|
-
def config_defined?
|
41
|
-
@config_defined
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def initialize
|
46
|
-
@config = ::Concurrent::Hash.new
|
47
|
-
@lock = ::Mutex.new
|
48
|
-
@defined = false
|
49
|
-
end
|
5
|
+
require 'dry/equalizer'
|
50
6
|
|
51
|
-
|
52
|
-
|
53
|
-
end
|
7
|
+
require 'dry/configurable/constants'
|
8
|
+
require 'dry/configurable/errors'
|
54
9
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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)
|
62
17
|
|
63
|
-
|
64
|
-
|
18
|
+
# @api private
|
19
|
+
attr_reader :_settings
|
65
20
|
|
66
|
-
|
67
|
-
|
21
|
+
# @api private
|
22
|
+
attr_reader :_resolved
|
68
23
|
|
69
|
-
# @private
|
70
|
-
def
|
71
|
-
|
72
|
-
@
|
73
|
-
freeze
|
74
|
-
end
|
75
|
-
|
76
|
-
# Serialize config to a Hash
|
77
|
-
#
|
78
|
-
# @return [Hash]
|
79
|
-
#
|
80
|
-
# @api public
|
81
|
-
def to_h
|
82
|
-
@config.each_with_object({}) do |(key, value), hash|
|
83
|
-
case value
|
84
|
-
when Config
|
85
|
-
hash[key] = value.to_h
|
86
|
-
else
|
87
|
-
hash[key] = value
|
88
|
-
end
|
89
|
-
end
|
24
|
+
# @api private
|
25
|
+
def initialize(settings)
|
26
|
+
@_settings = settings.dup
|
27
|
+
@_resolved = Concurrent::Map.new
|
90
28
|
end
|
91
|
-
alias to_hash to_h
|
92
29
|
|
93
30
|
# Get config value by a key
|
94
31
|
#
|
@@ -96,8 +33,10 @@ module Dry
|
|
96
33
|
#
|
97
34
|
# @return Config value
|
98
35
|
def [](name)
|
99
|
-
|
100
|
-
|
36
|
+
name = name.to_sym
|
37
|
+
raise ArgumentError, "+#{name}+ is not a setting name" unless _settings.key?(name)
|
38
|
+
|
39
|
+
_settings[name].value
|
101
40
|
end
|
102
41
|
|
103
42
|
# Set config value.
|
@@ -106,63 +45,83 @@ module Dry
|
|
106
45
|
# @param [String,Symbol] name
|
107
46
|
# @param [Object] value
|
108
47
|
def []=(name, value)
|
109
|
-
|
110
|
-
public_send("#{name}=", value)
|
48
|
+
public_send(:"#{name}=", value)
|
111
49
|
end
|
112
50
|
|
113
|
-
#
|
51
|
+
# Update config with new values
|
114
52
|
#
|
115
|
-
# @param [
|
116
|
-
# @return [Bool]
|
117
|
-
def key?(name)
|
118
|
-
self.class.settings.name?(name)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Recursively update values from a hash
|
53
|
+
# @param [Hash] A hash with new values
|
122
54
|
#
|
123
|
-
# @param [Hash] values to set
|
124
55
|
# @return [Config]
|
56
|
+
#
|
57
|
+
# @api public
|
125
58
|
def update(values)
|
126
59
|
values.each do |key, value|
|
127
|
-
|
60
|
+
case value
|
61
|
+
when Hash
|
128
62
|
self[key].update(value)
|
129
63
|
else
|
130
64
|
self[key] = value
|
131
65
|
end
|
132
66
|
end
|
133
|
-
self
|
134
67
|
end
|
135
68
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
69
|
+
# Dump config into a hash
|
70
|
+
#
|
71
|
+
# @return [Hash]
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
def values
|
75
|
+
_settings
|
76
|
+
.map { |setting| [setting.name, setting.value] }
|
77
|
+
.map { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
|
78
|
+
.to_h
|
79
|
+
end
|
80
|
+
alias_method :to_h, :values
|
81
|
+
alias_method :to_hash, :values
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def finalize!
|
85
|
+
_settings.freeze
|
86
|
+
freeze
|
87
|
+
end
|
88
|
+
|
89
|
+
# @api private
|
90
|
+
def pristine
|
91
|
+
self.class.new(_settings.pristine)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
def respond_to_missing?(meth, include_private = false)
|
96
|
+
super || _settings.key?(resolve(meth))
|
142
97
|
end
|
143
98
|
|
144
99
|
private
|
145
100
|
|
146
|
-
# @private
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
self[setting.name] = setting.value
|
159
|
-
end
|
101
|
+
# @api private
|
102
|
+
def method_missing(meth, *args)
|
103
|
+
setting = _settings[resolve(meth)]
|
104
|
+
|
105
|
+
super unless setting
|
106
|
+
|
107
|
+
if setting.writer?(meth)
|
108
|
+
raise FrozenConfig, 'Cannot modify frozen config' if frozen?
|
109
|
+
|
110
|
+
_settings << setting.with(input: args[0])
|
111
|
+
else
|
112
|
+
setting.value
|
160
113
|
end
|
161
114
|
end
|
162
115
|
|
163
|
-
# @private
|
164
|
-
def
|
165
|
-
|
116
|
+
# @api private
|
117
|
+
def resolve(meth)
|
118
|
+
_resolved.fetch(meth) { _resolved[meth] = meth.to_s.tr('=', '').to_sym }
|
119
|
+
end
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
def initialize_copy(source)
|
123
|
+
super
|
124
|
+
@_settings = source._settings.dup
|
166
125
|
end
|
167
126
|
end
|
168
127
|
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.to_sym, 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
|