dry-configurable 0.8.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 +82 -16
- data/LICENSE +1 -1
- data/README.md +17 -28
- data/dry-configurable.gemspec +29 -15
- data/lib/dry-configurable.rb +2 -0
- data/lib/dry/configurable.rb +23 -131
- data/lib/dry/configurable/class_methods.rb +103 -0
- data/lib/dry/configurable/compiler.rb +45 -0
- data/lib/dry/configurable/config.rb +81 -115
- 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 -13
- 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 +33 -17
- data/.codeclimate.yml +0 -23
- data/.gitignore +0 -8
- data/.rspec +0 -2
- data/.travis.yml +0 -27
- 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,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,103 +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
|
-
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
|
24
|
+
# @api private
|
25
|
+
def initialize(settings)
|
26
|
+
@settings = settings.dup
|
27
|
+
@resolved = Concurrent::Map.new
|
74
28
|
end
|
75
29
|
|
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
|
90
|
-
end
|
91
|
-
alias to_hash to_h
|
92
|
-
|
93
30
|
# Get config value by a key
|
94
31
|
#
|
95
32
|
# @param [String,Symbol] name
|
96
33
|
#
|
97
34
|
# @return Config value
|
98
35
|
def [](name)
|
99
|
-
|
100
|
-
|
36
|
+
raise ArgumentError, "+#{name}+ is not a setting name" unless settings.key?(name)
|
37
|
+
|
38
|
+
settings[name].value
|
101
39
|
end
|
102
40
|
|
103
41
|
# Set config value.
|
@@ -106,55 +44,83 @@ module Dry
|
|
106
44
|
# @param [String,Symbol] name
|
107
45
|
# @param [Object] value
|
108
46
|
def []=(name, value)
|
109
|
-
|
110
|
-
public_send("#{name}=", value)
|
47
|
+
public_send(:"#{name}=", value)
|
111
48
|
end
|
112
49
|
|
113
|
-
#
|
50
|
+
# Update config with new values
|
114
51
|
#
|
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
|
52
|
+
# @param [Hash] A hash with new values
|
122
53
|
#
|
123
|
-
# @param [Hash] values to set
|
124
54
|
# @return [Config]
|
55
|
+
#
|
56
|
+
# @api public
|
125
57
|
def update(values)
|
126
58
|
values.each do |key, value|
|
127
|
-
|
59
|
+
case value
|
60
|
+
when Hash
|
128
61
|
self[key].update(value)
|
129
62
|
else
|
130
63
|
self[key] = value
|
131
64
|
end
|
132
65
|
end
|
133
|
-
|
66
|
+
end
|
67
|
+
|
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))
|
134
96
|
end
|
135
97
|
|
136
98
|
private
|
137
99
|
|
138
|
-
# @private
|
139
|
-
def
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
self[setting.name] = setting.value
|
151
|
-
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
|
152
112
|
end
|
153
113
|
end
|
154
114
|
|
155
|
-
# @private
|
156
|
-
def
|
157
|
-
|
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
|
158
124
|
end
|
159
125
|
end
|
160
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
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/configurable/config'
|
4
|
+
require 'dry/configurable/methods'
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Configurable
|
8
|
+
# Instance-level API when `Dry::Configurable` is included in a class
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module InstanceMethods
|
12
|
+
include Methods
|
13
|
+
|
14
|
+
# Return object's configuration
|
15
|
+
#
|
16
|
+
# @return [Config]
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
attr_reader :config
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def initialize(*)
|
23
|
+
@config = Config.new(self.class._settings.dup)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
# Finalize the config and freeze the object
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def finalize!
|
31
|
+
return self if frozen?
|
32
|
+
|
33
|
+
super
|
34
|
+
freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @api public
|
40
|
+
def initialize_copy(source)
|
41
|
+
super
|
42
|
+
@config = source.config.dup
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|