dry-configurable 0.8.2 → 0.11.2

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.
@@ -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,103 +1,41 @@
1
- require 'concurrent/hash'
1
+ # frozen_string_literal: true
2
2
 
3
- module Dry
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
- @config_defined = true
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
- def defined?
52
- @defined
53
- end
7
+ require 'dry/configurable/constants'
8
+ require 'dry/configurable/errors'
54
9
 
55
- # @private
56
- def define!(parent_config = EMPTY_HASH)
57
- @lock.synchronize do
58
- break if self.defined?
59
-
60
- self.class.define_accessors!
61
- set_values!(parent_config)
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
- @defined = true
64
- end
18
+ # @api private
19
+ attr_reader :_settings
65
20
 
66
- self
67
- end
21
+ # @api private
22
+ attr_reader :_resolved
68
23
 
69
- # @private
70
- def finalize!
71
- define!
72
- @config.freeze
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
- raise_unknown_setting_error(name) unless key?(name.to_sym)
100
- public_send(name)
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
- raise_unknown_setting_error(name) unless key?(name.to_sym)
110
- public_send("#{name}=", value)
47
+ public_send(:"#{name}=", value)
111
48
  end
112
49
 
113
- # Whether config has a key
50
+ # Update config with new values
114
51
  #
115
- # @param [Symbol] key
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
- if self[key].is_a?(Config)
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
- self
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 set_values!(parent)
140
- self.class.settings.each do |setting|
141
- if parent.key?(setting.name) && !setting.node?
142
- @config[setting.name] = parent[setting.name]
143
- elsif setting.undefined?
144
- @config[setting.name] = nil
145
- elsif setting.node?
146
- value = setting.value.create_config
147
- value.define!(parent.fetch(setting.name, EMPTY_HASH))
148
- self[setting.name] = value
149
- else
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 raise_unknown_setting_error(name)
157
- raise ArgumentError, "+#{name}+ is not a setting name"
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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/constants'
4
+
5
+ module Dry
6
+ # Shared constants
7
+ #
8
+ # @api private
9
+ module Configurable
10
+ include Dry::Core::Constants
11
+ end
12
+ 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