dry-configurable 0.8.2 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
4
+ # Shared errors
5
+ #
6
+ # @api public
2
7
  module Configurable
3
8
  Error = Class.new(::StandardError)
4
- AlreadyDefinedConfig = ::Class.new(Error)
5
9
  FrozenConfig = ::Class.new(Error)
6
10
  end
7
11
  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
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable/errors'
4
+
5
+ module Dry
6
+ module Configurable
7
+ # Common API for both classes and instances
8
+ #
9
+ # @api public
10
+ module Methods
11
+ # @api public
12
+ def configure(&block)
13
+ raise FrozenConfig, 'Cannot modify frozen config' if frozen?
14
+
15
+ yield(config) if block
16
+ self
17
+ end
18
+
19
+ # Finalize and freeze configuration
20
+ #
21
+ # @return [Dry::Configurable::Config]
22
+ #
23
+ # @api public
24
+ def finalize!
25
+ return self if config.frozen?
26
+
27
+ config.finalize!
28
+ self
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,36 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require 'dry/equalizer'
6
+
7
+ require 'dry/configurable/constants'
8
+ require 'dry/configurable/config'
9
+
1
10
  module Dry
2
11
  module Configurable
3
12
  # This class represents a setting and is used internally.
4
13
  #
5
- # @private
14
+ # @api private
6
15
  class Setting
16
+ include Dry::Equalizer(:name, :value, :options)
17
+
18
+ OPTIONS = %i[input default reader constructor settings].freeze
19
+
20
+ DEFAULT_CONSTRUCTOR = -> v { v }.freeze
21
+
22
+ CLONABLE_VALUE_TYPES = [Array, Hash, Set, Config].freeze
23
+
24
+ # @api private
7
25
  attr_reader :name
8
26
 
27
+ # @api private
28
+ attr_reader :writer_name
29
+
30
+ # @api private
31
+ attr_reader :input
32
+
33
+ # @api private
34
+ attr_reader :default
35
+
36
+ # @api private
9
37
  attr_reader :options
10
38
 
11
- attr_reader :processor
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
12
55
 
13
- def initialize(name, value, processor, options = EMPTY_HASH)
14
- @name = name.to_sym
15
- @value = value
16
- @processor = processor
56
+ # @api private
57
+ def initialize(name, input: Undefined, default: Undefined, **options)
58
+ @name = name
59
+ @writer_name = :"#{name}="
60
+ @input = input.equal?(Undefined) ? default : input
61
+ @default = default
17
62
  @options = options
18
63
  end
19
64
 
65
+ # @api private
20
66
  def value
21
- Undefined.default(@value, nil)
67
+ @value ||= evaluate
22
68
  end
23
69
 
24
- def undefined?
25
- Undefined.equal?(@value)
70
+ # @api private
71
+ def nested(settings)
72
+ Nested.new(name, input: settings, **options)
26
73
  end
27
74
 
75
+ # @api private
76
+ def pristine
77
+ with(input: Undefined)
78
+ end
79
+
80
+ # @api private
81
+ def with(new_opts)
82
+ self.class.new(name, input: input, default: default, **options, **new_opts)
83
+ end
84
+
85
+ # @api private
86
+ def constructor
87
+ options[:constructor] || DEFAULT_CONSTRUCTOR
88
+ end
89
+
90
+ # @api private
28
91
  def reader?
29
- options[:reader]
92
+ options[:reader].equal?(true)
93
+ end
94
+
95
+ # @api private
96
+ def writer?(meth)
97
+ writer_name.equal?(meth)
98
+ end
99
+
100
+ # @api private
101
+ def clonable_value?
102
+ CLONABLE_VALUE_TYPES.any? { |type| value.is_a?(type) }
103
+ end
104
+
105
+ private
106
+
107
+ # @api private
108
+ def initialize_copy(source)
109
+ super
110
+ @value = source.value.dup if source.clonable_value?
111
+ @options = source.options.dup
30
112
  end
31
113
 
32
- def node?
33
- Settings === @value
114
+ # @api private
115
+ def evaluate
116
+ @value = constructor[input.equal?(Undefined) ? nil : input]
34
117
  end
35
118
  end
36
119
  end
@@ -1,91 +1,71 @@
1
- require 'set'
2
- require 'concurrent/array'
3
- require 'dry/configurable/settings/argument_parser'
4
- require 'dry/configurable/setting'
5
- require 'dry/configurable/config'
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ require 'dry/equalizer'
6
+ require 'dry/configurable/constants'
6
7
 
7
8
  module Dry
8
9
  module Configurable
9
- # A collection of settings. This is not part of the public API.
10
+ # A settings map
10
11
  #
11
- # @private
12
+ # @api private
12
13
  class Settings
13
- Parser = ArgumentParser.new.freeze
14
+ include Dry::Equalizer(:elements)
14
15
 
15
- class DSL
16
- def self.call(&block)
17
- new.instance_exec do
18
- instance_exec(&block)
19
- @settings
20
- end
21
- end
16
+ include Enumerable
22
17
 
23
- def initialize
24
- @settings = Settings.new
25
- end
18
+ # @api private
19
+ attr_reader :elements
26
20
 
27
- def setting(*args, &block)
28
- @settings.add(*args, &block)
29
- end
21
+ # @api private
22
+ def initialize(elements = EMPTY_ARRAY)
23
+ initialize_elements(elements)
30
24
  end
31
25
 
32
- # Capture nested config definition
33
- #
34
- # @return [Dry::Configurable::Setting]
35
- def self.capture(&block)
36
- DSL.(&block)
26
+ # @api private
27
+ def <<(setting)
28
+ elements[setting.name] = setting
29
+ self
37
30
  end
38
31
 
39
- attr_reader :settings
40
-
41
- attr_reader :config_class
42
-
43
- attr_reader :names
44
-
45
- def initialize(settings = ::Concurrent::Array.new)
46
- @settings = settings
47
- @config_class = Config[self]
48
- @names = Set.new(settings.map(&:name))
49
- yield(self) if block_given?
32
+ # @api private
33
+ def [](name)
34
+ elements[name]
50
35
  end
51
36
 
52
- def add(key, value = Undefined, options = Undefined, &block)
53
- extended = singleton_class < Configurable
54
- raise_already_defined_config(key) if extended && configured?
55
-
56
- Setting.new(key, *Parser.(value, options, block)).tap do |s|
57
- settings << s
58
- names << s.name
59
- end
60
- end
61
-
62
- def each
63
- settings.each { |s| yield(s) }
37
+ # @api private
38
+ def key?(name)
39
+ keys.include?(name)
64
40
  end
65
41
 
66
- def empty?
67
- settings.empty?
42
+ # @api private
43
+ def keys
44
+ elements.keys
68
45
  end
69
46
 
70
- def name?(name)
71
- names.include?(name)
47
+ # @api private
48
+ def each(&block)
49
+ elements.values.each(&block)
72
50
  end
73
51
 
74
- def dup
75
- Settings.new(settings.dup)
52
+ # @api private
53
+ def pristine
54
+ self.class.new(map(&:pristine))
76
55
  end
77
56
 
78
- def freeze
79
- settings.freeze
80
- super
81
- end
57
+ private
82
58
 
83
- def create_config
84
- config_class.new
59
+ # @api private
60
+ def initialize_copy(source)
61
+ initialize_elements(source.map(&:dup))
85
62
  end
86
63
 
87
- def config_defined?
88
- config_class.config_defined?
64
+ # @api private
65
+ def initialize_elements(elements)
66
+ @elements = elements.each_with_object(Concurrent::Map.new) { |s, m|
67
+ m[s.name] = s
68
+ }
89
69
  end
90
70
  end
91
71
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Configurable
3
5
  # Methods meant to be used in a testing scenario
@@ -8,11 +10,7 @@ module Dry
8
10
  #
9
11
  # @api public
10
12
  def reset_config
11
- @config = if self.is_a?(Module)
12
- _settings.create_config
13
- else
14
- self.class._settings.create_config
15
- end
13
+ @config = config.pristine
16
14
  end
17
15
  end
18
16
 
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Configurable
3
5
  # @api public
4
- VERSION = '0.8.2'.freeze
6
+ VERSION = '0.11.2'
5
7
  end
6
8
  end