dry-configurable 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2952b22a9581b91b77a416925a150088ea715be486638e293beae10d5b493a8
4
+ data.tar.gz: 6cf8073234d53e391e25d03eb0b3548694ebbaa30742169895b8f1d519f258d7
5
+ SHA512:
6
+ metadata.gz: 441ba5b074dd577bd0e0d37b5a99e76dc1ab85068885832e791eba4cd7a61e5691683f96c93e930a29d59e838b294d82248074433c670466ad31f773e1994dab
7
+ data.tar.gz: 02f13c05320aca5bb6900dd831966c3edc760fd66b9bfe40f94d079da0f3af30ecbdd07cdd024807d305296caf55f1b1613c285cdec86db97fb57659c42c11a0
@@ -0,0 +1,23 @@
1
+ engines:
2
+ rubocop:
3
+ enabled: true
4
+ checks:
5
+ Rubocop/Metrics/LineLength:
6
+ enabled: true
7
+ max: 120
8
+ Rubocop/Style/Documentation:
9
+ enabled: false
10
+ Rubocop/Lint/HandleExceptions:
11
+ enabled: true
12
+ exclude:
13
+ - rakelib/*.rake
14
+ Rubocop/Style/FileName:
15
+ enabled: true
16
+ exclude:
17
+ - lib/dry-configurable.rb
18
+ ratings:
19
+ paths:
20
+ - lib/**/*.rb
21
+ exclude_paths:
22
+ - spec/**/*
23
+ - examples/**/*
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ coverage
3
+ /.bundle
4
+ vendor/bundle
5
+ tmp/
6
+ .idea/
7
+ Gemfile.lock
8
+ spec/examples.txt
9
+ pkg/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require ./spec/spec_helper.rb
@@ -0,0 +1,28 @@
1
+ language: ruby
2
+ dist: trusty
3
+ sudo: required
4
+ cache: bundler
5
+ bundler_args: --without console
6
+ after_success:
7
+ - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
8
+ rvm:
9
+ - 2.4.6
10
+ - 2.5.5
11
+ - 2.6.3
12
+ - jruby-9.2.7.0
13
+ - truffleruby
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: truffleruby
17
+ env:
18
+ global:
19
+ - JRUBY_OPTS='--dev -J-Xmx1024M'
20
+ - COVERAGE='true'
21
+ notifications:
22
+ email: false
23
+ webhooks:
24
+ urls:
25
+ - https://webhooks.gitter.im/e/19098b4253a72c9796db
26
+ on_success: change # options: [always|never|change] default: always
27
+ on_failure: always # options: [always|never|change] default: always
28
+ on_start: false # default: false
@@ -0,0 +1,81 @@
1
+ ## 0.8.3 - 2019-05-29
2
+
3
+ ## Fixed
4
+
5
+ * `Configurable#dup` and `Configurable#clone` make a copy of instance-level config so that it doesn't get mutated/shared across instances (flash-gordon)
6
+
7
+ [Compare v0.8.2...v0.8.3](https://github.com/dry-rb/dry-configurable/compare/v0.8.2...v0.8.3)
8
+
9
+ ## 0.8.2 - 2019-02-25
10
+
11
+ ## Fixed
12
+
13
+ * Test interface support for modules ([Neznauy](https://github.com/Neznauy))
14
+
15
+ [Compare v0.8.1...v0.8.2](https://github.com/dry-rb/dry-configurable/compare/v0.8.1...v0.8.2)
16
+
17
+ ## 0.8.1 - 2019-02-06
18
+
19
+ ## Fixed
20
+
21
+ * `.configure` doesn't require a block, this makes the behavior consistent with the previous versions ([flash-gordon](https://github.com/flash-gordon))
22
+
23
+ [Compare v0.8.0...v0.8.1](https://github.com/dry-rb/dry-configurable/compare/v0.8.0...v0.8.1)
24
+
25
+ ## 0.8.0 - 2019-02-05
26
+
27
+ ## Fixed
28
+
29
+ * A number of bugs related to inheriting settings from parent class were fixed. Ideally, new behavior will be :100: predictable but if you observe any anomaly, please report ([flash-gordon](https://github.com/flash-gordon))
30
+
31
+ ## Added
32
+
33
+ * Support for instance-level configuration landed. For usage, `include` the module instead of extending ([flash-gordon](https://github.com/flash-gordon))
34
+ ```ruby
35
+ class App
36
+ include Dry::Configurable
37
+
38
+ setting :database
39
+ end
40
+
41
+ production = App.new
42
+ production.config.database = ENV['DATABASE_URL']
43
+ production.finalize!
44
+
45
+ development = App.new
46
+ development.config.database = 'jdbc:sqlite:memory'
47
+ development.finalize!
48
+ ```
49
+ * Config values can be set from a hash with `.update`. Nested settings are supported ([flash-gordon](https://github.com/flash-gordon))
50
+ ```ruby
51
+ class App
52
+ extend Dry::Configurable
53
+
54
+ setting :db do
55
+ setting :host
56
+ setting :port
57
+ end
58
+
59
+ config.update(YAML.load(File.read("config.yml")))
60
+ end
61
+ ```
62
+
63
+ ## Changed
64
+
65
+ * [BREAKING] Minimal supported Ruby version is set to 2.3 ([flash-gordon](https://github.com/flash-gordon))
66
+
67
+ [Compare v0.7.0...v0.8.0](https://github.com/dry-rb/dry-configurable/compare/v0.7.0...v0.8.0)
68
+
69
+ ## 0.7.0 - 2017-04-25
70
+
71
+ ## Added
72
+
73
+ * Introduce `Configurable.finalize!` which freezes config and its dependencies ([qcam](https://github.com/qcam))
74
+
75
+ ## Fixed
76
+
77
+ * Allow for boolean false as default setting value ([yuszuv](https://github.com/yuszuv))
78
+ * Convert nested configs to nested hashes with `Config#to_h` ([saverio-kantox](https://github.com/saverio-kantox))
79
+ * Disallow modification on frozen config ([qcam](https://github.com/qcam))
80
+
81
+ [Compare v0.6.2...v0.7.0](https://github.com/dry-rb/dry-configurable/compare/v0.6.2...v0.7.0)
@@ -0,0 +1,29 @@
1
+ # Issue Guidelines
2
+
3
+ ## Reporting bugs
4
+
5
+ If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
+
7
+ ## Reporting feature requests
8
+
9
+ Report a feature request **only after discussing it first on [discuss.dry-rb.org](https://discuss.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
+
11
+ ## Reporting questions, support requests, ideas, concerns etc.
12
+
13
+ **PLEASE DON'T** - use [discuss.dry-rb.org](http://discuss.dry-rb.org) instead.
14
+
15
+ # Pull Request Guidelines
16
+
17
+ A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
+
19
+ Other requirements:
20
+
21
+ 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
+ 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
+ 3) Add API documentation if it's a new feature
24
+ 4) Update API documentation if it changes an existing feature
25
+ 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
+
27
+ # Asking for help
28
+
29
+ If these guidelines aren't helpful, and you're stuck, please post a message on [discuss.dry-rb.org](https://discuss.dry-rb.org).
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ platforms :mri do
7
+ gem 'codeclimate-test-reporter', require: false
8
+ gem 'simplecov', require: false
9
+ end
10
+ end
11
+
12
+ group :tools do
13
+ gem 'guard'
14
+ gem 'guard-rspec'
15
+ gem 'listen', '3.0.6'
16
+ gem 'pry-byebug', platform: :mri
17
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2017 dry-rb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ [gitter]: https://gitter.im/dry-rb/chat
2
+ [gem]: https://rubygems.org/gems/dry-configurable
3
+ [travis]: https://travis-ci.org/dry-rb/dry-configurable
4
+ [inch]: http://inch-ci.org/github/dry-rb/dry-configurable
5
+ [chat]: https://dry-rb.zulipchat.com
6
+
7
+ # dry-configurable [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
+
9
+ [![Gem Version](https://img.shields.io/gem/v/dry-configurable.svg)][gem]
10
+ [![Build Status](https://img.shields.io/travis/dry-rb/dry-configurable.svg)][travis]
11
+ [![Maintainability](https://api.codeclimate.com/v1/badges/25311e81391498d6b7c8/maintainability)](https://codeclimate.com/github/dry-rb/dry-configurable/maintainability)
12
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/25311e81391498d6b7c8/test_coverage)](https://codeclimate.com/github/dry-rb/dry-configurable/test_coverage)
13
+ [![API Documentation Coverage](http://inch-ci.org/github/dry-rb/dry-configurable.svg)][inch]
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'dry-configurable'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```sh
26
+ $ bundle
27
+ ```
28
+
29
+ Or install it yourself as:
30
+
31
+ ```sh
32
+ $ gem install dry-configurable
33
+ ```
34
+
35
+ ## Links
36
+
37
+ * [Documentation](http://dry-rb.org/gems/dry-configurable)
38
+
39
+ ## License
40
+
41
+ See `LICENSE` file.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
5
+
6
+ require 'rspec/core'
7
+ require 'rspec/core/rake_task'
8
+
9
+ task default: :spec
10
+
11
+ desc 'Run all specs in spec directory'
12
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,29 @@
1
+ require File.expand_path('../lib/dry/configurable/version', __FILE__)
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'dry-configurable'
5
+ spec.version = Dry::Configurable::VERSION
6
+ spec.authors = ['Andy Holland']
7
+ spec.email = ['andyholland1991@aol.com']
8
+ spec.summary = 'A mixin to add configuration functionality to your classes'
9
+ spec.homepage = 'https://github.com/dry-rb/dry-configurable'
10
+ spec.license = 'MIT'
11
+
12
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|bin)/}) }
13
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.metadata = {
18
+ 'source_code_uri' => 'https://github.com/dry-rb/dry-configurable',
19
+ 'changelog_uri' => 'https://github.com/dry-rb/dry-configurable/blob/master/CHANGELOG.md'
20
+ }
21
+
22
+ spec.required_ruby_version = ">= 2.3.0"
23
+ spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
24
+ spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.7'
25
+
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
28
+ spec.add_development_dependency 'rspec'
29
+ end
@@ -0,0 +1 @@
1
+ require 'dry/configurable'
@@ -0,0 +1,192 @@
1
+ require 'dry/core/constants'
2
+ require 'dry/configurable/settings'
3
+ require 'dry/configurable/error'
4
+ require 'dry/configurable/version'
5
+
6
+ # A collection of micro-libraries, each intended to encapsulate
7
+ # a common task in Ruby
8
+ module Dry
9
+ # A simple configuration mixin
10
+ #
11
+ # @example class-level configuration
12
+ #
13
+ # class App
14
+ # extend Dry::Configurable
15
+ #
16
+ # setting :database do
17
+ # setting :dsn, 'sqlite:memory'
18
+ # end
19
+ # end
20
+ #
21
+ # App.config.database.dsn = 'jdbc:sqlite:memory'
22
+ # App.config.database.dsn
23
+ # # => "jdbc:sqlite:memory"
24
+ #
25
+ # @example instance-level configuration
26
+ #
27
+ # class App
28
+ # include Dry::Configurable
29
+ #
30
+ # setting :database
31
+ # end
32
+ #
33
+ # production = App.new
34
+ # production.config.database = ENV['DATABASE_URL']
35
+ # production.finalize!
36
+ #
37
+ # development = App.new
38
+ # development.config.database = 'jdbc:sqlite:memory'
39
+ # development.finalize!
40
+ #
41
+ # @api public
42
+ module Configurable
43
+ include Dry::Core::Constants
44
+
45
+ module ClassMethods
46
+ # @private
47
+ def self.extended(base)
48
+ base.instance_exec do
49
+ @settings = Settings.new
50
+ end
51
+ end
52
+
53
+ # Add a setting to the configuration
54
+ #
55
+ # @param [Mixed] key
56
+ # The accessor key for the configuration value
57
+ # @param [Mixed] default
58
+ # The default config value
59
+ #
60
+ # @yield
61
+ # If a block is given, it will be evaluated in the context of
62
+ # and new configuration class, and bound as the default value
63
+ #
64
+ # @return [Dry::Configurable::Config]
65
+ #
66
+ # @api public
67
+ def setting(key, value = Undefined, options = Undefined, &block)
68
+ extended = singleton_class < Configurable
69
+ raise_already_defined_config(key) if _settings.config_defined?
70
+
71
+ setting = _settings.add(key, value, options, &block)
72
+
73
+ if setting.reader?
74
+ readers = extended ? singleton_class : self
75
+ readers.send(:define_method, setting.name) { config[setting.name] }
76
+ end
77
+ end
78
+
79
+ # Return an array of setting names
80
+ #
81
+ # @return [Set]
82
+ #
83
+ # @api public
84
+ def settings
85
+ _settings.names
86
+ end
87
+
88
+ # @private no, really...
89
+ def _settings
90
+ @settings
91
+ end
92
+
93
+ private
94
+
95
+ # @private
96
+ def raise_already_defined_config(key)
97
+ raise AlreadyDefinedConfig,
98
+ "Cannot add setting +#{key}+, #{self} is already configured"
99
+ end
100
+
101
+ # @private
102
+ def inherited(subclass)
103
+ parent = self
104
+ subclass.instance_exec do
105
+ @settings = parent._settings.dup
106
+ end
107
+
108
+ if singleton_class < Configurable
109
+ parent_config = @config
110
+ subclass.instance_exec do
111
+ @config = _settings.create_config
112
+ @config.define!(parent_config.to_h) if parent_config.defined?
113
+ end
114
+ end
115
+
116
+ super
117
+ end
118
+ end
119
+
120
+ class << self
121
+ # @private
122
+ def extended(base)
123
+ base.extend(ClassMethods)
124
+ base.class_eval do
125
+ @config = _settings.create_config
126
+ end
127
+ end
128
+
129
+ # @private
130
+ def included(base)
131
+ base.extend(ClassMethods)
132
+ end
133
+ end
134
+
135
+ # @private
136
+ def initialize(*)
137
+ @config = self.class._settings.create_config
138
+ super
139
+ end
140
+
141
+ # Return configuration
142
+ #
143
+ # @return [Dry::Configurable::Config]
144
+ #
145
+ # @api public
146
+ def config
147
+ return @config if @config.defined?
148
+ @config.define!
149
+ end
150
+
151
+ # Return configuration
152
+ #
153
+ # @yield [Dry::Configuration::Config]
154
+ #
155
+ # @return [Dry::Configurable::Config]
156
+ #
157
+ # @api public
158
+ def configure
159
+ raise FrozenConfig, 'Cannot modify frozen config' if frozen?
160
+ yield(config) if block_given?
161
+ self
162
+ end
163
+
164
+ # Finalize and freeze configuration
165
+ #
166
+ # @return [Dry::Configurable::Config]
167
+ #
168
+ # @api public
169
+ def finalize!
170
+ freeze
171
+ config.finalize!
172
+ end
173
+
174
+ # @api public
175
+ def dup
176
+ super.tap do |copy|
177
+ copy.instance_variable_set(:@config, config.dup)
178
+ end
179
+ end
180
+
181
+ # @api public
182
+ def clone
183
+ if frozen?
184
+ super
185
+ else
186
+ super.tap do |copy|
187
+ copy.instance_variable_set(:@config, config.dup)
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,169 @@
1
+ require 'concurrent/hash'
2
+
3
+ module Dry
4
+ module Configurable
5
+ # @private
6
+ class Config
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
18
+
19
+ # @private
20
+ def define_accessors!
21
+ @lock.synchronize do
22
+ break if config_defined?
23
+
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
34
+
35
+ @config_defined = true
36
+ end
37
+ end
38
+
39
+ # @private
40
+ def config_defined?
41
+ @config_defined
42
+ end
43
+ end
44
+
45
+ def initialize
46
+ @config = ::Concurrent::Hash.new
47
+ @lock = ::Mutex.new
48
+ @defined = false
49
+ end
50
+
51
+ def defined?
52
+ @defined
53
+ end
54
+
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
67
+ end
68
+
69
+ # @private
70
+ def finalize!
71
+ define!
72
+ @config.freeze
73
+ freeze
74
+ end
75
+
76
+ # Serialize config to a Hash
77
+ #
78
+ # @return [Hash]
79
+ #
80
+ # @api public
81
+ def to_h
82
+ @config.each_with_object({}) do |(key, value), hash|
83
+ case value
84
+ when Config
85
+ hash[key] = value.to_h
86
+ else
87
+ hash[key] = value
88
+ end
89
+ end
90
+ end
91
+ alias to_hash to_h
92
+
93
+ # Get config value by a key
94
+ #
95
+ # @param [String,Symbol] name
96
+ #
97
+ # @return Config value
98
+ def [](name)
99
+ raise_unknown_setting_error(name) unless key?(name.to_sym)
100
+ public_send(name)
101
+ end
102
+
103
+ # Set config value.
104
+ # Note that finalized configs cannot be changed.
105
+ #
106
+ # @param [String,Symbol] name
107
+ # @param [Object] value
108
+ def []=(name, value)
109
+ raise_unknown_setting_error(name) unless key?(name.to_sym)
110
+ public_send("#{name}=", value)
111
+ end
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
+
144
+ private
145
+
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
161
+ end
162
+
163
+ # @private
164
+ def raise_unknown_setting_error(name)
165
+ raise ArgumentError, "+#{name}+ is not a setting name"
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,7 @@
1
+ module Dry
2
+ module Configurable
3
+ Error = Class.new(::StandardError)
4
+ AlreadyDefinedConfig = ::Class.new(Error)
5
+ FrozenConfig = ::Class.new(Error)
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,26 @@
1
+ module Dry
2
+ module Configurable
3
+ # Methods meant to be used in a testing scenario
4
+ module TestInterface
5
+ # Resets configuration to default values
6
+ #
7
+ # @return [Dry::Configurable::Config]
8
+ #
9
+ # @api public
10
+ def reset_config
11
+ @config = if self.is_a?(Module)
12
+ _settings.create_config
13
+ else
14
+ self.class._settings.create_config
15
+ end
16
+ end
17
+ end
18
+
19
+ # Mixes in test interface into the configurable module
20
+ #
21
+ # @api public
22
+ def enable_test_interface
23
+ extend Dry::Configurable::TestInterface
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,6 @@
1
+ module Dry
2
+ module Configurable
3
+ # @api public
4
+ VERSION = '0.8.3'.freeze
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'rubocop/rake_task'
3
+
4
+ Rake::Task[:default].enhance [:rubocop]
5
+
6
+ RuboCop::RakeTask.new do |task|
7
+ task.options << '--display-cop-names'
8
+ end
9
+
10
+ namespace :rubocop do
11
+ desc 'Generate a configuration file acting as a TODO list.'
12
+ task :auto_gen_config do
13
+ exec 'bundle exec rubocop --auto-gen-config'
14
+ end
15
+ end
16
+
17
+ rescue LoadError
18
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-configurable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.3
5
+ platform: ruby
6
+ authors:
7
+ - Andy Holland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
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
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description:
90
+ email:
91
+ - andyholland1991@aol.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".codeclimate.yml"
97
+ - ".gitignore"
98
+ - ".rspec"
99
+ - ".travis.yml"
100
+ - CHANGELOG.md
101
+ - CONTRIBUTING.md
102
+ - Gemfile
103
+ - LICENSE
104
+ - README.md
105
+ - Rakefile
106
+ - dry-configurable.gemspec
107
+ - lib/dry-configurable.rb
108
+ - lib/dry/configurable.rb
109
+ - lib/dry/configurable/config.rb
110
+ - lib/dry/configurable/error.rb
111
+ - lib/dry/configurable/setting.rb
112
+ - lib/dry/configurable/settings.rb
113
+ - lib/dry/configurable/settings/argument_parser.rb
114
+ - lib/dry/configurable/test_interface.rb
115
+ - lib/dry/configurable/version.rb
116
+ - rakelib/rubocop.rake
117
+ homepage: https://github.com/dry-rb/dry-configurable
118
+ licenses:
119
+ - MIT
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
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 2.3.0
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubygems_version: 3.0.3
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: A mixin to add configuration functionality to your classes
142
+ test_files: []