dry-configurable 0.7.0 → 0.8.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.
@@ -1,61 +1,87 @@
1
+ require 'concurrent/hash'
2
+
1
3
  module Dry
2
4
  module Configurable
3
5
  # @private
4
6
  class Config
5
- DEFAULT_PROCESSOR = ->(v) { v }.freeze
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
6
18
 
7
- def self.create(settings)
8
- klass = ::Class.new(self)
19
+ # @private
20
+ def define_accessors!
21
+ @lock.synchronize do
22
+ break if config_defined?
9
23
 
10
- settings.each do |setting|
11
- klass.__send__(:define_method, setting.name) do
12
- @config[setting.name]
13
- end
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
14
34
 
15
- klass.__send__(:define_method, "#{setting.name}=") do |value|
16
- raise_frozen_config if frozen?
17
- @config[setting.name] = setting.processor.call(value)
35
+ @config_defined = true
18
36
  end
19
37
  end
20
38
 
21
- klass.new(settings)
39
+ # @private
40
+ def config_defined?
41
+ @config_defined
42
+ end
22
43
  end
23
44
 
24
- def initialize(settings)
45
+ def initialize
25
46
  @config = ::Concurrent::Hash.new
26
-
27
- settings.each do |setting|
28
- if setting.none?
29
- @config[setting.name] = nil
30
- else
31
- public_send("#{setting.name}=", setting.value)
32
- end
33
- end
47
+ @lock = ::Mutex.new
48
+ @defined = false
34
49
  end
35
50
 
36
- def dup
37
- dup = super
38
- dup.instance_variable_set(:@config, @config.dup)
39
- dup
51
+ def defined?
52
+ @defined
40
53
  end
41
54
 
42
- def clone
43
- clone = super
44
- clone.instance_variable_set(:@config, @config.clone)
45
- clone
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)
62
+
63
+ @defined = true
64
+ end
65
+
66
+ self
46
67
  end
47
68
 
69
+ # @private
48
70
  def finalize!
71
+ define!
49
72
  @config.freeze
50
73
  freeze
51
74
  end
52
75
 
76
+ # Serialize config to a Hash
77
+ #
78
+ # @return [Hash]
79
+ #
80
+ # @api public
53
81
  def to_h
54
- @config.each_with_object({}) do |tuple, hash|
55
- key, value = tuple
56
-
82
+ @config.each_with_object({}) do |(key, value), hash|
57
83
  case value
58
- when ::Dry::Configurable::Config, ::Dry::Configurable::NestedConfig
84
+ when Config
59
85
  hash[key] = value.to_h
60
86
  else
61
87
  hash[key] = value
@@ -64,29 +90,80 @@ module Dry
64
90
  end
65
91
  alias to_hash to_h
66
92
 
93
+ # Get config value by a key
94
+ #
95
+ # @param [String,Symbol] name
96
+ #
97
+ # @return Config value
67
98
  def [](name)
68
- raise_unknown_setting_error(name) unless setting?(name)
99
+ raise_unknown_setting_error(name) unless key?(name.to_sym)
69
100
  public_send(name)
70
101
  end
71
102
 
103
+ # Set config value.
104
+ # Note that finalized configs cannot be changed.
105
+ #
106
+ # @param [String,Symbol] name
107
+ # @param [Object] value
72
108
  def []=(name, value)
73
- raise_unknown_setting_error(name) unless setting?(name)
109
+ raise_unknown_setting_error(name) unless key?(name.to_sym)
74
110
  public_send("#{name}=", value)
75
111
  end
76
112
 
113
+ # Whether config has a key
114
+ #
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
122
+ #
123
+ # @param [Hash] values to set
124
+ # @return [Config]
125
+ def update(values)
126
+ values.each do |key, value|
127
+ if self[key].is_a?(Config)
128
+ self[key].update(value)
129
+ else
130
+ self[key] = value
131
+ end
132
+ end
133
+ self
134
+ end
135
+
136
+ def dup
137
+ if self.defined?
138
+ self.class.new.define!(to_h)
139
+ else
140
+ self.class.new
141
+ end
142
+ end
143
+
77
144
  private
78
145
 
79
- def raise_frozen_config
80
- raise FrozenConfig, 'Cannot modify frozen config'
146
+ # @private
147
+ def set_values!(parent)
148
+ self.class.settings.each do |setting|
149
+ if parent.key?(setting.name) && !setting.node?
150
+ @config[setting.name] = parent[setting.name]
151
+ elsif setting.undefined?
152
+ @config[setting.name] = nil
153
+ elsif setting.node?
154
+ value = setting.value.create_config
155
+ value.define!(parent.fetch(setting.name, EMPTY_HASH))
156
+ self[setting.name] = value
157
+ else
158
+ self[setting.name] = setting.value
159
+ end
160
+ end
81
161
  end
82
162
 
163
+ # @private
83
164
  def raise_unknown_setting_error(name)
84
165
  raise ArgumentError, "+#{name}+ is not a setting name"
85
166
  end
86
-
87
- def setting?(name)
88
- @config.key?(name.to_sym)
89
- end
90
167
  end
91
168
  end
92
169
  end
@@ -1,5 +1,3 @@
1
- # A collection of micro-libraries, each intended to encapsulate
2
- # a common task in Ruby
3
1
  module Dry
4
2
  module Configurable
5
3
  Error = Class.new(::StandardError)
@@ -0,0 +1,37 @@
1
+ module Dry
2
+ module Configurable
3
+ # This class represents a setting and is used internally.
4
+ #
5
+ # @private
6
+ class Setting
7
+ attr_reader :name
8
+
9
+ attr_reader :options
10
+
11
+ attr_reader :processor
12
+
13
+ def initialize(name, value, processor, options = EMPTY_HASH)
14
+ @name = name.to_sym
15
+ @value = value
16
+ @processor = processor
17
+ @options = options
18
+ end
19
+
20
+ def value
21
+ Undefined.default(@value, nil)
22
+ end
23
+
24
+ def undefined?
25
+ Undefined.equal?(@value)
26
+ end
27
+
28
+ def reader?
29
+ options[:reader]
30
+ end
31
+
32
+ def node?
33
+ Settings === @value
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ require 'set'
2
+ require 'concurrent/array'
3
+ require 'dry/configurable/settings/argument_parser'
4
+ require 'dry/configurable/setting'
5
+ require 'dry/configurable/config'
6
+
7
+ module Dry
8
+ module Configurable
9
+ # A collection of settings. This is not part of the public API.
10
+ #
11
+ # @private
12
+ 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
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?
50
+ end
51
+
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) }
64
+ end
65
+
66
+ def empty?
67
+ settings.empty?
68
+ end
69
+
70
+ def name?(name)
71
+ names.include?(name)
72
+ end
73
+
74
+ def dup
75
+ Settings.new(settings.dup)
76
+ end
77
+
78
+ def freeze
79
+ settings.freeze
80
+ super
81
+ end
82
+
83
+ def create_config
84
+ config_class.new
85
+ end
86
+
87
+ def config_defined?
88
+ config_class.config_defined?
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,50 @@
1
+ module Dry
2
+ # Argument parser
3
+ #
4
+ # Passing and array or arguments, it will decide which one are arguments
5
+ # and which one are options.
6
+ #
7
+ # We have a limitation if setting the value without options, as a hash
8
+ # having the same key as one of the valid options, will parse the value
9
+ # as options. In this case, all unknown options will be reject with an exception.
10
+ #
11
+ # @example
12
+ # p = Dry::Configurable::ArgumentParser.new.('db:sqlite', reader: true)
13
+ #
14
+ # p[0] # => 'db:sqlite'
15
+ # p[1] # => ArgumentParser::DEFAULT_PROCESSOR
16
+ # p[2] # => { reader: true }
17
+ module Configurable
18
+ class Settings
19
+ # @private
20
+ class ArgumentParser
21
+ DEFAULT_PROCESSOR = ->(v) { v }
22
+
23
+ # @private
24
+ def call(val, opts, block)
25
+ if block && block.parameters.empty?
26
+ raise ArgumentError unless Undefined.equal?(opts)
27
+
28
+ processor = DEFAULT_PROCESSOR
29
+
30
+ value, options = Settings.capture(&block), val
31
+ else
32
+ processor = block || DEFAULT_PROCESSOR
33
+
34
+ if Undefined.equal?(opts) && val.is_a?(Hash) && val.key?(:reader)
35
+ value, options = Undefined, val
36
+ else
37
+ value, options = val, opts
38
+ end
39
+ end
40
+
41
+ [value, processor, options(Undefined.default(options, EMPTY_HASH))]
42
+ end
43
+
44
+ def options(reader: false)
45
+ { reader: reader }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -8,7 +8,11 @@ module Dry
8
8
  #
9
9
  # @api public
10
10
  def reset_config
11
- create_config
11
+ @config = if self.is_a?(Module)
12
+ _settings.create_config
13
+ else
14
+ self.class._settings.create_config
15
+ end
12
16
  end
13
17
  end
14
18
 
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  module Configurable
3
3
  # @api public
4
- VERSION = '0.7.0'.freeze
4
+ VERSION = '0.8.3'.freeze
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,69 +1,89 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Holland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-25 00:00:00.000000000 Z
11
+ date: 2019-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 0.4.7
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '0.4'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.4.7
27
47
  - !ruby/object:Gem::Dependency
28
48
  name: bundler
29
49
  requirement: !ruby/object:Gem::Requirement
30
50
  requirements:
31
- - - '>='
51
+ - - ">="
32
52
  - !ruby/object:Gem::Version
33
53
  version: '0'
34
54
  type: :development
35
55
  prerelease: false
36
56
  version_requirements: !ruby/object:Gem::Requirement
37
57
  requirements:
38
- - - '>='
58
+ - - ">="
39
59
  - !ruby/object:Gem::Version
40
60
  version: '0'
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: rake
43
63
  requirement: !ruby/object:Gem::Requirement
44
64
  requirements:
45
- - - '>='
65
+ - - ">="
46
66
  - !ruby/object:Gem::Version
47
67
  version: '0'
48
68
  type: :development
49
69
  prerelease: false
50
70
  version_requirements: !ruby/object:Gem::Requirement
51
71
  requirements:
52
- - - '>='
72
+ - - ">="
53
73
  - !ruby/object:Gem::Version
54
74
  version: '0'
55
75
  - !ruby/object:Gem::Dependency
56
76
  name: rspec
57
77
  requirement: !ruby/object:Gem::Requirement
58
78
  requirements:
59
- - - '>='
79
+ - - ">="
60
80
  - !ruby/object:Gem::Version
61
81
  version: '0'
62
82
  type: :development
63
83
  prerelease: false
64
84
  version_requirements: !ruby/object:Gem::Requirement
65
85
  requirements:
66
- - - '>='
86
+ - - ">="
67
87
  - !ruby/object:Gem::Version
68
88
  version: '0'
69
89
  description:
@@ -73,12 +93,12 @@ executables: []
73
93
  extensions: []
74
94
  extra_rdoc_files: []
75
95
  files:
76
- - .codeclimate.yml
77
- - .gitignore
78
- - .rspec
79
- - .ruby-version
80
- - .travis.yml
96
+ - ".codeclimate.yml"
97
+ - ".gitignore"
98
+ - ".rspec"
99
+ - ".travis.yml"
81
100
  - CHANGELOG.md
101
+ - CONTRIBUTING.md
82
102
  - Gemfile
83
103
  - LICENSE
84
104
  - README.md
@@ -86,48 +106,37 @@ files:
86
106
  - dry-configurable.gemspec
87
107
  - lib/dry-configurable.rb
88
108
  - lib/dry/configurable.rb
89
- - lib/dry/configurable/argument_parser.rb
90
109
  - lib/dry/configurable/config.rb
91
- - lib/dry/configurable/config/value.rb
92
110
  - lib/dry/configurable/error.rb
93
- - lib/dry/configurable/nested_config.rb
111
+ - lib/dry/configurable/setting.rb
112
+ - lib/dry/configurable/settings.rb
113
+ - lib/dry/configurable/settings/argument_parser.rb
94
114
  - lib/dry/configurable/test_interface.rb
95
115
  - lib/dry/configurable/version.rb
96
116
  - rakelib/rubocop.rake
97
- - spec/integration/configurable_spec.rb
98
- - spec/spec_helper.rb
99
- - spec/support/shared_examples/configurable.rb
100
- - spec/unit/dry/configurable/argument_parser_spec.rb
101
- - spec/unit/dry/configurable/config/value_spec.rb
102
- - spec/unit/dry/configurable/config_spec.rb
103
- homepage: https://github.com/dryrb/dry-configurable
117
+ homepage: https://github.com/dry-rb/dry-configurable
104
118
  licenses:
105
119
  - MIT
106
- metadata: {}
120
+ metadata:
121
+ source_code_uri: https://github.com/dry-rb/dry-configurable
122
+ changelog_uri: https://github.com/dry-rb/dry-configurable/blob/master/CHANGELOG.md
107
123
  post_install_message:
108
124
  rdoc_options: []
109
125
  require_paths:
110
126
  - lib
111
127
  required_ruby_version: !ruby/object:Gem::Requirement
112
128
  requirements:
113
- - - '>='
129
+ - - ">="
114
130
  - !ruby/object:Gem::Version
115
- version: '0'
131
+ version: 2.3.0
116
132
  required_rubygems_version: !ruby/object:Gem::Requirement
117
133
  requirements:
118
- - - '>='
134
+ - - ">="
119
135
  - !ruby/object:Gem::Version
120
136
  version: '0'
121
137
  requirements: []
122
- rubyforge_project:
123
- rubygems_version: 2.4.8
138
+ rubygems_version: 3.0.3
124
139
  signing_key:
125
140
  specification_version: 4
126
141
  summary: A mixin to add configuration functionality to your classes
127
- test_files:
128
- - spec/integration/configurable_spec.rb
129
- - spec/spec_helper.rb
130
- - spec/support/shared_examples/configurable.rb
131
- - spec/unit/dry/configurable/argument_parser_spec.rb
132
- - spec/unit/dry/configurable/config/value_spec.rb
133
- - spec/unit/dry/configurable/config_spec.rb
142
+ test_files: []