dry-configurable 0.8.3

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