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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +23 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +28 -0
- data/CHANGELOG.md +81 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +17 -0
- data/LICENSE +20 -0
- data/README.md +41 -0
- data/Rakefile +12 -0
- data/dry-configurable.gemspec +29 -0
- data/lib/dry-configurable.rb +1 -0
- data/lib/dry/configurable.rb +192 -0
- data/lib/dry/configurable/config.rb +169 -0
- data/lib/dry/configurable/error.rb +7 -0
- data/lib/dry/configurable/setting.rb +37 -0
- data/lib/dry/configurable/settings.rb +92 -0
- data/lib/dry/configurable/settings/argument_parser.rb +50 -0
- data/lib/dry/configurable/test_interface.rb +26 -0
- data/lib/dry/configurable/version.rb +6 -0
- data/rakelib/rubocop.rake +18 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -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
|
data/.codeclimate.yml
ADDED
@@ -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/**/*
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
@@ -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)
|
data/CONTRIBUTING.md
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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 [][chat]
|
8
|
+
|
9
|
+
[][gem]
|
10
|
+
[][travis]
|
11
|
+
[](https://codeclimate.com/github/dry-rb/dry-configurable/maintainability)
|
12
|
+
[](https://codeclimate.com/github/dry-rb/dry-configurable/test_coverage)
|
13
|
+
[][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.
|
data/Rakefile
ADDED
@@ -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,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,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: []
|