dry-configurable 0.9.0 → 0.11.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -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 -135
  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, inspect: false)
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