dry-configurable 0.7.0 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []