dry-configurable 0.9.0 → 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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -21
  3. data/LICENSE +1 -1
  4. data/README.md +15 -27
  5. data/dry-configurable.gemspec +27 -18
  6. data/lib/dry-configurable.rb +2 -0
  7. data/lib/dry/configurable.rb +21 -146
  8. data/lib/dry/configurable/class_methods.rb +103 -0
  9. data/lib/dry/configurable/compiler.rb +45 -0
  10. data/lib/dry/configurable/config.rb +79 -136
  11. data/lib/dry/configurable/constants.rb +12 -0
  12. data/lib/dry/configurable/dsl.rb +62 -0
  13. data/lib/dry/configurable/dsl/args.rb +58 -0
  14. data/lib/dry/configurable/{error.rb → errors.rb} +5 -1
  15. data/lib/dry/configurable/instance_methods.rb +46 -0
  16. data/lib/dry/configurable/methods.rb +32 -0
  17. data/lib/dry/configurable/setting.rb +91 -17
  18. data/lib/dry/configurable/settings.rb +42 -87
  19. data/lib/dry/configurable/test_interface.rb +3 -5
  20. data/lib/dry/configurable/version.rb +3 -1
  21. metadata +30 -25
  22. data/.codeclimate.yml +0 -12
  23. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  24. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  25. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  26. data/.github/workflows/ci.yml +0 -70
  27. data/.github/workflows/docsite.yml +0 -34
  28. data/.github/workflows/sync_configs.yml +0 -30
  29. data/.gitignore +0 -9
  30. data/.rspec +0 -4
  31. data/.rubocop.yml +0 -89
  32. data/CODE_OF_CONDUCT.md +0 -13
  33. data/CONTRIBUTING.md +0 -29
  34. data/Gemfile +0 -20
  35. data/Rakefile +0 -12
  36. data/docsite/source/index.html.md +0 -55
  37. data/docsite/source/testing.html.md +0 -27
  38. data/lib/dry/configurable/settings/argument_parser.rb +0 -50
  39. data/rakelib/rubocop.rake +0 -18
@@ -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,45 +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
7
- VALID_NAME = /\A[a-z_]\w*\z/i
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
8
23
 
24
+ # @api private
9
25
  attr_reader :name
10
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
11
37
  attr_reader :options
12
38
 
13
- attr_reader :processor
39
+ # Specialized Setting which includes nested settings
40
+ #
41
+ # @api private
42
+ class Nested < Setting
43
+ CONSTRUCTOR = Config.method(:new)
14
44
 
15
- def initialize(name, value, processor, options = EMPTY_HASH)
16
- unless VALID_NAME =~ name.to_s
17
- raise ArgumentError, "+#{name}+ is not a valid setting name"
45
+ # @api private
46
+ def pristine
47
+ with(input: input.pristine)
18
48
  end
19
- @name = name.to_sym
20
- @value = value
21
- @processor = processor
49
+
50
+ # @api private
51
+ def constructor
52
+ CONSTRUCTOR
53
+ end
54
+ end
55
+
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
22
62
  @options = options
23
63
  end
24
64
 
65
+ # @api private
25
66
  def value
26
- Undefined.default(@value, nil)
67
+ @value ||= evaluate
68
+ end
69
+
70
+ # @api private
71
+ def nested(settings)
72
+ Nested.new(name, input: settings, **options)
73
+ end
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)
27
83
  end
28
84
 
29
- def undefined?
30
- Undefined.equal?(@value)
85
+ # @api private
86
+ def constructor
87
+ options[:constructor] || DEFAULT_CONSTRUCTOR
31
88
  end
32
89
 
90
+ # @api private
33
91
  def reader?
34
- options[:reader]
92
+ options[:reader].equal?(true)
93
+ end
94
+
95
+ # @api private
96
+ def writer?(meth)
97
+ writer_name.equal?(meth)
35
98
  end
36
99
 
37
- def node?
38
- Settings === @value
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
39
112
  end
40
113
 
41
- def reserved?
42
- options[:reserved]
114
+ # @api private
115
+ def evaluate
116
+ @value = constructor[input.equal?(Undefined) ? nil : input]
43
117
  end
44
118
  end
45
119
  end
@@ -1,116 +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
-
15
- class DSL
16
- def self.call(&block)
17
- new.instance_exec do
18
- instance_exec(&block)
19
- @settings
20
- end
21
- end
22
-
23
- def initialize
24
- @settings = Settings.new
25
- end
26
-
27
- def setting(*args, &block)
28
- @settings.add(*args, &block)
29
- end
30
- end
31
-
32
- # Capture nested config definition
33
- #
34
- # @return [Dry::Configurable::Setting]
35
- def self.capture(&block)
36
- DSL.(&block)
37
- end
38
-
39
- attr_reader :settings
14
+ include Dry::Equalizer(:elements)
40
15
 
41
- attr_reader :config_class
16
+ include Enumerable
42
17
 
43
- attr_reader :index
44
- private :index
18
+ # @api private
19
+ attr_reader :elements
45
20
 
46
- def initialize(settings = ::Concurrent::Array.new)
47
- @settings = settings
48
- @config_class = Config[self]
49
- @index = settings.map { |s| [s.name, s] }.to_h
50
- yield(self) if block_given?
21
+ # @api private
22
+ def initialize(elements = EMPTY_ARRAY)
23
+ initialize_elements(elements)
51
24
  end
52
25
 
53
- def add(key, value = Undefined, options = Undefined, &block)
54
- extended = singleton_class < Configurable
55
- raise_already_defined_config(key) if extended && configured?
56
-
57
- *args, opts = Parser.(value, options, block)
58
-
59
- Setting.new(key, *args, { **opts, reserved: reserved?(key) }).tap do |s|
60
- settings.delete_if { |e| e.name.eql?(s.name) }
61
- settings << s
62
- index[s.name] = s
63
- @names = nil
64
- end
65
- end
66
-
67
- def each
68
- settings.each { |s| yield(s) }
69
- end
70
-
71
- def names
72
- @names ||= index.keys.to_set
26
+ # @api private
27
+ def <<(setting)
28
+ elements[setting.name] = setting
29
+ self
73
30
  end
74
31
 
32
+ # @api private
75
33
  def [](name)
76
- index[name]
77
- end
78
-
79
- def empty?
80
- settings.empty?
34
+ elements[name]
81
35
  end
82
36
 
83
- def name?(name)
84
- index.key?(name)
37
+ # @api private
38
+ def key?(name)
39
+ keys.include?(name)
85
40
  end
86
41
 
87
- def dup
88
- Settings.new(settings.dup)
42
+ # @api private
43
+ def keys
44
+ elements.keys
89
45
  end
90
46
 
91
- def freeze
92
- settings.freeze
93
- super
47
+ # @api private
48
+ def each(&block)
49
+ elements.values.each(&block)
94
50
  end
95
51
 
96
- def create_config
97
- config_class.new
52
+ # @api private
53
+ def pristine
54
+ self.class.new(map(&:pristine))
98
55
  end
99
56
 
100
- def config_defined?
101
- config_class.config_defined?
102
- end
57
+ private
103
58
 
104
- def reserved?(name)
105
- reserved_names.include?(name)
59
+ # @api private
60
+ def initialize_copy(source)
61
+ initialize_elements(source.map(&:dup))
106
62
  end
107
63
 
108
- def reserved_names
109
- @reserved_names ||= [
110
- config_class.instance_methods(false),
111
- config_class.superclass.instance_methods(false),
112
- %i(class public_send)
113
- ].reduce(:+)
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
+ }
114
69
  end
115
70
  end
116
71
  end