dry-configurable 0.11.5

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: f0faa56dea70f112144295394747175b526e116cdfd09d3ccfa047b85dd06b11
4
+ data.tar.gz: 48fc6304ef41ea05076765bbe8f7286e90ed69ee54b388650d3654b74b4ebfda
5
+ SHA512:
6
+ metadata.gz: 9c6c284433350053c27e745a8bcb0a0c000c22d4fe684c39eeb20340d026b764f8d80ee155a30ba88af66f4e9ba1022e1572accfa5aff4657b463978534d964e
7
+ data.tar.gz: ed4d650fb3f2d7f92d05bad9575c452bf67138489a734e91ccc4fb6d9fa5932a6dee72430a84c7b2994b8d28bb42a6d7c4dda8e5b82e9ce51ac588a0888f1b2b
@@ -0,0 +1,177 @@
1
+ ## 0.11.5 2020-03-23
2
+
3
+
4
+ ### Fixed
5
+
6
+ - When settings are copied or cloned, unevaluated values will no longer be copied. This prevents unintended crashes when settings have constructors expecting a certain type of value, but that value is yet to be provided (Fixed via #87) (@timriley)
7
+
8
+
9
+ [Compare v0.11.4...v0.11.5](https://github.com/dry-rb/dry-configurable/compare/v0.11.4...v0.11.5)
10
+
11
+ ## 0.11.4 2020-03-16
12
+
13
+
14
+ ### Fixed
15
+
16
+ - `Config#update` returns `self` again (issue #60 fixed via #92) (@solnic)
17
+
18
+ ### Changed
19
+
20
+ - `Setting#inspect` no longer uses its value - this could cause crashes when inspecting settings that are yet to have a value applied (e.g. when they have a constructor that expects a value to be present) (@timriley)
21
+
22
+ [Compare v0.11.3...v0.11.4](https://github.com/dry-rb/dry-configurable/compare/v0.11.3...v0.11.4)
23
+
24
+ ## 0.11.3 2020-02-22
25
+
26
+
27
+ ### Fixed
28
+
29
+ - Retrieving settings by a string name works again (issue #82) (@waiting-for-dev)
30
+
31
+
32
+ [Compare v0.11.2...v0.11.3](https://github.com/dry-rb/dry-configurable/compare/v0.11.2...v0.11.3)
33
+
34
+ ## 0.11.2 2020-02-20
35
+
36
+
37
+ ### Fixed
38
+
39
+ - Warning about redefined `Setting#value` is gone (@solnic)
40
+
41
+
42
+ [Compare v0.11.1...v0.11.2](https://github.com/dry-rb/dry-configurable/compare/v0.11.1...v0.11.2)
43
+
44
+ ## 0.11.1 2020-02-18
45
+
46
+
47
+ ### Fixed
48
+
49
+ - You can use `:settings` as a config key again (issue #80) (@solnic)
50
+ - Setting value is lazy-evaluated now, which fixes some cases where a constructor could crash with a `nil` value (@solnic)
51
+
52
+
53
+ [Compare v0.11.0...v0.11.1](https://github.com/dry-rb/dry-configurable/compare/v0.11.0...v0.11.1)
54
+
55
+ ## 0.11.0 2020-02-15
56
+
57
+ Complete rewrite of the library while keeping the public API intact. See #78 for a detailed overview.
58
+
59
+ ### Changed
60
+
61
+ - Accessing config in a parent class no longer prevents you from adding more settings in a child class (@solnic)
62
+ - (internal) New low-level Setting and Config API (@solnic)
63
+ - (internal) `config` objects use method_missing now (@solnic)
64
+
65
+ [Compare v0.10.0...v0.11.0](https://github.com/dry-rb/dry-configurable/compare/v0.10.0...v0.11.0)
66
+
67
+ ## 0.10.0 2020-01-31
68
+
69
+ YANKED because the change also broke inheritance for classes that used `configured` before other classes inherited from them.
70
+
71
+ ### Changed
72
+
73
+ - Inheriting settings no longer defines the config object. This change fixed a use case where parent class that already used its config would prevent a child class from adding new settings (@solnic)
74
+
75
+ [Compare v0.9.0...v0.10.0](https://github.com/dry-rb/dry-configurable/compare/v0.9.0...v0.10.0)
76
+
77
+ ## 0.9.0 2019-11-06
78
+
79
+
80
+ ### Fixed
81
+
82
+ - Support for reserved names in settings. Some Kernel methods (`public_send` and `class` specifically) are not available if you use access settings via method call. Same for methods of the `Config` class. You can still access them with `[]` and `[]=`. Ruby keywords are fully supported. Invalid names containing special symbols (including `!` and `?`) are rejected. Note that these changes don't affect the `reader` option, if you define a setting named `:class` and pass `reader: true` ... well ... (flash-gordon)
83
+ - Settings can be redefined in subclasses without a warning about overriding exsting methods (flash-gordon)
84
+ - Fix warnings about using keyword arguments in 2.7 (koic)
85
+
86
+
87
+ [Compare v0.8.3...v0.9.0](https://github.com/dry-rb/dry-configurable/compare/v0.8.3...v0.9.0)
88
+
89
+ ## 0.8.3 2019-05-29
90
+
91
+
92
+ ### Fixed
93
+
94
+ - `Configurable#dup` and `Configurable#clone` make a copy of instance-level config so that it doesn't get mutated/shared across instances (flash-gordon)
95
+
96
+
97
+ [Compare v0.8.2...v0.8.3](https://github.com/dry-rb/dry-configurable/compare/v0.8.2...v0.8.3)
98
+
99
+ ## 0.8.2 2019-02-25
100
+
101
+
102
+ ### Fixed
103
+
104
+ - Test interface support for modules ([Neznauy](https://github.com/Neznauy))
105
+
106
+
107
+ [Compare v0.8.1...v0.8.2](https://github.com/dry-rb/dry-configurable/compare/v0.8.1...v0.8.2)
108
+
109
+ ## 0.8.1 2019-02-06
110
+
111
+
112
+ ### Fixed
113
+
114
+ - `.configure` doesn't require a block, this makes the behavior consistent with the previous versions ([flash-gordon](https://github.com/flash-gordon))
115
+
116
+
117
+ [Compare v0.8.0...v0.8.1](https://github.com/dry-rb/dry-configurable/compare/v0.8.0...v0.8.1)
118
+
119
+ ## 0.8.0 2019-02-05
120
+
121
+
122
+ ### Added
123
+
124
+ - Support for instance-level configuration landed. For usage, `include` the module instead of extending ([flash-gordon](https://github.com/flash-gordon))
125
+
126
+ ```ruby
127
+ class App
128
+ include Dry::Configurable
129
+
130
+ setting :database
131
+ end
132
+
133
+ production = App.new
134
+ production.config.database = ENV['DATABASE_URL']
135
+ production.finalize!
136
+
137
+ development = App.new
138
+ development.config.database = 'jdbc:sqlite:memory'
139
+ development.finalize!
140
+ ```
141
+ - Config values can be set from a hash with `.update`. Nested settings are supported ([flash-gordon](https://github.com/flash-gordon))
142
+
143
+ ```ruby
144
+ class App
145
+ extend Dry::Configurable
146
+
147
+ setting :db do
148
+ setting :host
149
+ setting :port
150
+ end
151
+
152
+ config.update(YAML.load(File.read("config.yml")))
153
+ end
154
+ ```
155
+
156
+ ### Fixed
157
+
158
+ - 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))
159
+
160
+ ### Changed
161
+
162
+ - [BREAKING] Minimal supported Ruby version is set to 2.3 ([flash-gordon](https://github.com/flash-gordon))
163
+
164
+ [Compare v0.7.0...v0.8.0](https://github.com/dry-rb/dry-configurable/compare/v0.7.0...v0.8.0)
165
+
166
+ ## 0.7.0 2017-04-25
167
+
168
+
169
+ ### Added
170
+
171
+ - Introduce `Configurable.finalize!` which freezes config and its dependencies ([qcam](https://github.com/qcam))
172
+
173
+ ### Fixed
174
+
175
+ - Allow for boolean false as default setting value ([yuszuv](https://github.com/yuszuv))
176
+ - Convert nested configs to nested hashes with `Config#to_h` ([saverio-kantox](https://github.com/saverio-kantox))
177
+ - Disallow modification on frozen config ([qcam](https://github.com/qcam))
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2020 dry-rb team
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,29 @@
1
+ [gem]: https://rubygems.org/gems/dry-configurable
2
+ [actions]: https://github.com/dry-rb/dry-configurable/actions
3
+ [codacy]: https://www.codacy.com/gh/dry-rb/dry-configurable
4
+ [chat]: https://dry-rb.zulipchat.com
5
+ [inchpages]: http://inch-ci.org/github/dry-rb/dry-configurable
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://badge.fury.io/rb/dry-configurable.svg)][gem]
10
+ [![CI Status](https://github.com/dry-rb/dry-configurable/workflows/ci/badge.svg)][actions]
11
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/0276a97990e04eb0ac722b3e7f3620b5)][codacy]
12
+ [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/0276a97990e04eb0ac722b3e7f3620b5)][codacy]
13
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-configurable.svg?branch=master)][inchpages]
14
+
15
+ ## Links
16
+
17
+ * [User documentation](http://dry-rb.org/gems/dry-configurable)
18
+ * [API documentation](http://rubydoc.info/gems/dry-configurable)
19
+
20
+ ## Supported Ruby versions
21
+
22
+ This library officially supports the following Ruby versions:
23
+
24
+ * MRI >= `2.4`
25
+ * jruby >= `9.2`
26
+
27
+ ## License
28
+
29
+ See `LICENSE` file.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
3
+
4
+ lib = File.expand_path('lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'dry/configurable/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'dry-configurable'
10
+ spec.authors = ["Andy Holland"]
11
+ spec.email = ["andyholland1991@aol.com"]
12
+ spec.license = 'MIT'
13
+ spec.version = Dry::Configurable::VERSION.dup
14
+
15
+ spec.summary = "A mixin to add configuration functionality to your classes"
16
+ spec.description = spec.summary
17
+ spec.homepage = 'https://dry-rb.org/gems/dry-configurable'
18
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-configurable.gemspec", "lib/**/*"]
19
+ spec.bindir = 'bin'
20
+ spec.executables = []
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
24
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-configurable/blob/master/CHANGELOG.md'
25
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-configurable'
26
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-configurable/issues'
27
+
28
+ spec.required_ruby_version = ">= 2.4.0"
29
+
30
+ # to update dependencies edit project.yml
31
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
32
+ spec.add_runtime_dependency "dry-core", "~> 0.4", ">= 0.4.7"
33
+ spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
34
+
35
+ spec.add_development_dependency "bundler"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "rspec"
38
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable'
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/array'
4
+
5
+ require 'dry/configurable/constants'
6
+ require 'dry/configurable/class_methods'
7
+ require 'dry/configurable/instance_methods'
8
+ require 'dry/configurable/config'
9
+ require 'dry/configurable/setting'
10
+
11
+ module Dry
12
+ # A simple configuration mixin
13
+ #
14
+ # @example class-level configuration
15
+ #
16
+ # class App
17
+ # extend Dry::Configurable
18
+ #
19
+ # setting :database do
20
+ # setting :dsn, 'sqlite:memory'
21
+ # end
22
+ # end
23
+ #
24
+ # App.config.database.dsn = 'jdbc:sqlite:memory'
25
+ # App.config.database.dsn
26
+ # # => "jdbc:sqlite:memory"
27
+ #
28
+ # @example instance-level configuration
29
+ #
30
+ # class App
31
+ # include Dry::Configurable
32
+ #
33
+ # setting :database
34
+ # end
35
+ #
36
+ # production = App.new
37
+ # production.config.database = ENV['DATABASE_URL']
38
+ # production.finalize!
39
+ #
40
+ # development = App.new
41
+ # development.config.database = 'jdbc:sqlite:memory'
42
+ # development.finalize!
43
+ #
44
+ # @api public
45
+ module Configurable
46
+ # @api private
47
+ def self.extended(klass)
48
+ super
49
+ klass.extend(ClassMethods)
50
+ end
51
+
52
+ # @api private
53
+ def self.included(klass)
54
+ super
55
+ klass.class_eval do
56
+ extend(ClassMethods)
57
+ include(InstanceMethods)
58
+
59
+ class << self
60
+ undef :config
61
+ undef :configure
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require 'dry/configurable/constants'
6
+ require 'dry/configurable/dsl'
7
+ require 'dry/configurable/methods'
8
+ require 'dry/configurable/settings'
9
+
10
+ module Dry
11
+ module Configurable
12
+ module ClassMethods
13
+ include Methods
14
+
15
+ # @api private
16
+ def inherited(klass)
17
+ super
18
+
19
+ parent_settings = (respond_to?(:config) ? config._settings : _settings)
20
+
21
+ klass.instance_variable_set('@_settings', parent_settings)
22
+ end
23
+
24
+ # Add a setting to the configuration
25
+ #
26
+ # @param [Mixed] key
27
+ # The accessor key for the configuration value
28
+ # @param [Mixed] default
29
+ # The default config value
30
+ #
31
+ # @yield
32
+ # If a block is given, it will be evaluated in the context of
33
+ # a new configuration class, and bound as the default value
34
+ #
35
+ # @return [Dry::Configurable::Config]
36
+ #
37
+ # @api public
38
+ def setting(*args, &block)
39
+ setting = __config_dsl__.setting(*args, &block)
40
+
41
+ _settings << setting
42
+
43
+ __config_reader__.define(setting.name) if setting.reader?
44
+
45
+ self
46
+ end
47
+
48
+ # Return declared settings
49
+ #
50
+ # @return [Set<Symbol>]
51
+ #
52
+ # @api public
53
+ def settings
54
+ @settings ||= Set[*_settings.map(&:name)]
55
+ end
56
+
57
+ # Return declared settings
58
+ #
59
+ # @return [Settings]
60
+ #
61
+ # @api public
62
+ def _settings
63
+ @_settings ||= Settings.new
64
+ end
65
+
66
+ # Return configuration
67
+ #
68
+ # @return [Config]
69
+ #
70
+ # @api public
71
+ def config
72
+ @config ||= Config.new(_settings)
73
+ end
74
+
75
+ # @api private
76
+ def __config_dsl__
77
+ @dsl ||= DSL.new
78
+ end
79
+
80
+ # @api private
81
+ def __config_reader__
82
+ @__config_reader__ ||=
83
+ begin
84
+ reader = Module.new do
85
+ def self.define(name)
86
+ define_method(name) do
87
+ config[name]
88
+ end
89
+ end
90
+ end
91
+
92
+ if included_modules.include?(InstanceMethods)
93
+ include(reader)
94
+ end
95
+
96
+ extend(reader)
97
+
98
+ reader
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable/setting'
4
+ require 'dry/configurable/settings'
5
+
6
+ module Dry
7
+ module Configurable
8
+ # Setting compiler used internally by the DSL
9
+ #
10
+ # @api private
11
+ class Compiler
12
+ def call(ast)
13
+ Settings.new.tap do |settings|
14
+ ast.each do |node|
15
+ settings << visit(node)
16
+ end
17
+ end
18
+ end
19
+
20
+ # @api private
21
+ def visit(node)
22
+ type, rest = node
23
+ public_send(:"visit_#{type}", rest)
24
+ end
25
+
26
+ # @api private
27
+ def visit_constructor(node)
28
+ setting, constructor = node
29
+ visit(setting).with(constructor: constructor)
30
+ end
31
+
32
+ # @api private
33
+ def visit_setting(node)
34
+ name, default, opts = node
35
+ Setting.new(name, **opts, default: default)
36
+ end
37
+
38
+ # @api private
39
+ def visit_nested(node)
40
+ parent, children = node
41
+ visit(parent).nested(call(children))
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ require 'dry/equalizer'
6
+
7
+ require 'dry/configurable/constants'
8
+ require 'dry/configurable/errors'
9
+
10
+ module Dry
11
+ module Configurable
12
+ # Config exposes setting values through a convenient API
13
+ #
14
+ # @api public
15
+ class Config
16
+ include Dry::Equalizer(:values)
17
+
18
+ # @api private
19
+ attr_reader :_settings
20
+
21
+ # @api private
22
+ attr_reader :_resolved
23
+
24
+ # @api private
25
+ def initialize(settings)
26
+ @_settings = settings.dup
27
+ @_resolved = Concurrent::Map.new
28
+ end
29
+
30
+ # Get config value by a key
31
+ #
32
+ # @param [String,Symbol] name
33
+ #
34
+ # @return Config value
35
+ def [](name)
36
+ name = name.to_sym
37
+ raise ArgumentError, "+#{name}+ is not a setting name" unless _settings.key?(name)
38
+
39
+ _settings[name].value
40
+ end
41
+
42
+ # Set config value.
43
+ # Note that finalized configs cannot be changed.
44
+ #
45
+ # @param [String,Symbol] name
46
+ # @param [Object] value
47
+ def []=(name, value)
48
+ public_send(:"#{name}=", value)
49
+ end
50
+
51
+ # Update config with new values
52
+ #
53
+ # @param values [Hash] A hash with new values
54
+ #
55
+ # @return [Config]
56
+ #
57
+ # @api public
58
+ def update(values)
59
+ values.each do |key, value|
60
+ case value
61
+ when Hash
62
+ self[key].update(value)
63
+ else
64
+ self[key] = value
65
+ end
66
+ end
67
+ self
68
+ end
69
+
70
+ # Dump config into a hash
71
+ #
72
+ # @return [Hash]
73
+ #
74
+ # @api public
75
+ def values
76
+ _settings
77
+ .map { |setting| [setting.name, setting.value] }
78
+ .map { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
79
+ .to_h
80
+ end
81
+ alias_method :to_h, :values
82
+ alias_method :to_hash, :values
83
+
84
+ # @api private
85
+ def finalize!
86
+ _settings.freeze
87
+ freeze
88
+ end
89
+
90
+ # @api private
91
+ def pristine
92
+ self.class.new(_settings.pristine)
93
+ end
94
+
95
+ # @api private
96
+ def respond_to_missing?(meth, include_private = false)
97
+ super || _settings.key?(resolve(meth))
98
+ end
99
+
100
+ private
101
+
102
+ # @api private
103
+ def method_missing(meth, *args)
104
+ setting = _settings[resolve(meth)]
105
+
106
+ super unless setting
107
+
108
+ if setting.writer?(meth)
109
+ raise FrozenConfig, 'Cannot modify frozen config' if frozen?
110
+
111
+ _settings << setting.with(input: args[0])
112
+ else
113
+ setting.value
114
+ end
115
+ end
116
+
117
+ # @api private
118
+ def resolve(meth)
119
+ _resolved.fetch(meth) { _resolved[meth] = meth.to_s.tr('=', '').to_sym }
120
+ end
121
+
122
+ # @api private
123
+ def initialize_copy(source)
124
+ super
125
+ @_settings = source._settings.dup
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/constants'
4
+
5
+ module Dry
6
+ # Shared constants
7
+ #
8
+ # @api private
9
+ module Configurable
10
+ include Dry::Core::Constants
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable/constants'
4
+ require 'dry/configurable/setting'
5
+ require 'dry/configurable/settings'
6
+ require 'dry/configurable/compiler'
7
+ require 'dry/configurable/dsl/args'
8
+
9
+ module Dry
10
+ module Configurable
11
+ # Setting DSL used by the class API
12
+ #
13
+ # @api private
14
+ class DSL
15
+ VALID_NAME = /\A[a-z_]\w*\z/i.freeze
16
+
17
+ # @api private
18
+ attr_reader :compiler
19
+
20
+ # @api private
21
+ attr_reader :ast
22
+
23
+ # @api private
24
+ def initialize(&block)
25
+ @compiler = Compiler.new
26
+ @ast = []
27
+ instance_exec(&block) if block
28
+ end
29
+
30
+ # Register a new setting node and compile it into a setting object
31
+ #
32
+ # @see ClassMethods.setting
33
+ # @api public
34
+ # @return Setting
35
+ def setting(name, *args, &block)
36
+ unless VALID_NAME.match?(name.to_s)
37
+ raise ArgumentError, "#{name} is not a valid setting name"
38
+ end
39
+
40
+ args = Args.new(args)
41
+
42
+ args.ensure_valid_options
43
+
44
+ default, opts = args
45
+
46
+ node = [:setting, [name.to_sym, default, opts == default ? EMPTY_HASH : opts]]
47
+
48
+ if block
49
+ if block.arity.zero?
50
+ ast << [:nested, [node, DSL.new(&block).ast]]
51
+ else
52
+ ast << [:constructor, [node, block]]
53
+ end
54
+ else
55
+ ast << node
56
+ end
57
+
58
+ compiler.visit(ast.last)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable/constants'
4
+ require 'dry/configurable/setting'
5
+
6
+ module Dry
7
+ module Configurable
8
+ class DSL
9
+ # @api private
10
+ class Args
11
+ # @api private
12
+ attr_reader :args
13
+
14
+ # @api private
15
+ attr_reader :size
16
+
17
+ # @api private
18
+ attr_reader :opts
19
+
20
+ # @api private
21
+ def initialize(args)
22
+ @args = args
23
+ @size = args.size
24
+ @opts = Setting::OPTIONS
25
+ end
26
+
27
+ # @api private
28
+ def ensure_valid_options
29
+ return unless options
30
+
31
+ keys = options.keys - opts
32
+ raise ArgumentError, "Invalid options: #{keys.inspect}" unless keys.empty?
33
+ end
34
+
35
+ # @api private
36
+ def to_ary
37
+ [default, options || EMPTY_HASH]
38
+ end
39
+
40
+ # @api private
41
+ def default
42
+ if size.equal?(1) && options.nil?
43
+ args[0]
44
+ elsif size > 1 && options
45
+ args[0]
46
+ else
47
+ Undefined
48
+ end
49
+ end
50
+
51
+ # @api private
52
+ def options
53
+ args.detect { |arg| arg.is_a?(Hash) && (opts & arg.keys).any? }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ # Shared errors
5
+ #
6
+ # @api public
7
+ module Configurable
8
+ Error = Class.new(::StandardError)
9
+ FrozenConfig = ::Class.new(Error)
10
+ end
11
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable/config'
4
+ require 'dry/configurable/methods'
5
+
6
+ module Dry
7
+ module Configurable
8
+ # Instance-level API when `Dry::Configurable` is included in a class
9
+ #
10
+ # @api public
11
+ module InstanceMethods
12
+ include Methods
13
+
14
+ # Return object's configuration
15
+ #
16
+ # @return [Config]
17
+ #
18
+ # @api public
19
+ attr_reader :config
20
+
21
+ # @api private
22
+ def initialize(*)
23
+ @config = Config.new(self.class._settings.dup)
24
+ super
25
+ end
26
+
27
+ # Finalize the config and freeze the object
28
+ #
29
+ # @api public
30
+ def finalize!
31
+ return self if frozen?
32
+
33
+ super
34
+ freeze
35
+ end
36
+
37
+ private
38
+
39
+ # @api public
40
+ def initialize_copy(source)
41
+ super
42
+ @config = source.config.dup
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/configurable/errors'
4
+
5
+ module Dry
6
+ module Configurable
7
+ # Common API for both classes and instances
8
+ #
9
+ # @api public
10
+ module Methods
11
+ # @api public
12
+ def configure(&block)
13
+ raise FrozenConfig, 'Cannot modify frozen config' if frozen?
14
+
15
+ yield(config) if block
16
+ self
17
+ end
18
+
19
+ # Finalize and freeze configuration
20
+ #
21
+ # @return [Dry::Configurable::Config]
22
+ #
23
+ # @api public
24
+ def finalize!
25
+ return self if config.frozen?
26
+
27
+ config.finalize!
28
+ self
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require 'dry/equalizer'
6
+
7
+ require 'dry/configurable/constants'
8
+ require 'dry/configurable/config'
9
+
10
+ module Dry
11
+ module Configurable
12
+ # This class represents a setting and is used internally.
13
+ #
14
+ # @api private
15
+ class Setting
16
+ include Dry::Equalizer(:name, :value, :options, inspect: false)
17
+
18
+ OPTIONS = %i[input default reader constructor settings].freeze
19
+
20
+ DEFAULT_CONSTRUCTOR = -> v { v }.freeze
21
+
22
+ CLONABLE_VALUE_TYPES = [Array, Hash, Set, Config].freeze
23
+
24
+ # @api private
25
+ attr_reader :name
26
+
27
+ # @api private
28
+ attr_reader :writer_name
29
+
30
+ # @api private
31
+ attr_reader :input
32
+
33
+ # @api private
34
+ attr_reader :default
35
+
36
+ # @api private
37
+ attr_reader :options
38
+
39
+ # Specialized Setting which includes nested settings
40
+ #
41
+ # @api private
42
+ class Nested < Setting
43
+ CONSTRUCTOR = Config.method(:new)
44
+
45
+ # @api private
46
+ def pristine
47
+ with(input: input.pristine)
48
+ end
49
+
50
+ # @api private
51
+ def constructor
52
+ CONSTRUCTOR
53
+ end
54
+ end
55
+
56
+ # @api private
57
+ def initialize(name, input: Undefined, default: Undefined, **options)
58
+ @name = name
59
+ @writer_name = :"#{name}="
60
+ @input = input.equal?(Undefined) ? default : input
61
+ @default = default
62
+ @options = options
63
+ end
64
+
65
+ # @api private
66
+ def input_defined?
67
+ !input.equal?(Undefined)
68
+ end
69
+
70
+ # @api private
71
+ def value
72
+ @value ||= evaluate
73
+ end
74
+
75
+ # @api private
76
+ def evaluated?
77
+ instance_variable_defined?(:@value)
78
+ end
79
+
80
+ # @api private
81
+ def nested(settings)
82
+ Nested.new(name, input: settings, **options)
83
+ end
84
+
85
+ # @api private
86
+ def pristine
87
+ with(input: Undefined)
88
+ end
89
+
90
+ # @api private
91
+ def with(new_opts)
92
+ self.class.new(name, input: input, default: default, **options, **new_opts)
93
+ end
94
+
95
+ # @api private
96
+ def constructor
97
+ options[:constructor] || DEFAULT_CONSTRUCTOR
98
+ end
99
+
100
+ # @api private
101
+ def reader?
102
+ options[:reader].equal?(true)
103
+ end
104
+
105
+ # @api private
106
+ def writer?(meth)
107
+ writer_name.equal?(meth)
108
+ end
109
+
110
+ # @api private
111
+ def clonable_value?
112
+ CLONABLE_VALUE_TYPES.any? { |type| value.is_a?(type) }
113
+ end
114
+
115
+ private
116
+
117
+ # @api private
118
+ def initialize_copy(source)
119
+ super
120
+ @value = source.value.dup if source.input_defined? && source.clonable_value?
121
+ @options = source.options.dup
122
+ end
123
+
124
+ # @api private
125
+ def evaluate
126
+ @value = constructor[input.equal?(Undefined) ? nil : input]
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ require 'dry/equalizer'
6
+ require 'dry/configurable/constants'
7
+
8
+ module Dry
9
+ module Configurable
10
+ # A settings map
11
+ #
12
+ # @api private
13
+ class Settings
14
+ include Dry::Equalizer(:elements)
15
+
16
+ include Enumerable
17
+
18
+ # @api private
19
+ attr_reader :elements
20
+
21
+ # @api private
22
+ def initialize(elements = EMPTY_ARRAY)
23
+ initialize_elements(elements)
24
+ end
25
+
26
+ # @api private
27
+ def <<(setting)
28
+ elements[setting.name] = setting
29
+ self
30
+ end
31
+
32
+ # @api private
33
+ def [](name)
34
+ elements[name]
35
+ end
36
+
37
+ # @api private
38
+ def key?(name)
39
+ keys.include?(name)
40
+ end
41
+
42
+ # @api private
43
+ def keys
44
+ elements.keys
45
+ end
46
+
47
+ # @api private
48
+ def each(&block)
49
+ elements.values.each(&block)
50
+ end
51
+
52
+ # @api private
53
+ def pristine
54
+ self.class.new(map(&:pristine))
55
+ end
56
+
57
+ private
58
+
59
+ # @api private
60
+ def initialize_copy(source)
61
+ initialize_elements(source.map(&:dup))
62
+ end
63
+
64
+ # @api private
65
+ def initialize_elements(elements)
66
+ @elements = elements.each_with_object(Concurrent::Map.new) { |s, m|
67
+ m[s.name] = s
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Configurable
5
+ # Methods meant to be used in a testing scenario
6
+ module TestInterface
7
+ # Resets configuration to default values
8
+ #
9
+ # @return [Dry::Configurable::Config]
10
+ #
11
+ # @api public
12
+ def reset_config
13
+ @config = config.pristine
14
+ end
15
+ end
16
+
17
+ # Mixes in test interface into the configurable module
18
+ #
19
+ # @api public
20
+ def enable_test_interface
21
+ extend Dry::Configurable::TestInterface
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Configurable
5
+ # @api public
6
+ VERSION = '0.11.5'
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-configurable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.11.5
5
+ platform: ruby
6
+ authors:
7
+ - Andy Holland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-23 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: dry-equalizer
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.2'
61
+ - !ruby/object:Gem::Dependency
62
+ name: bundler
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: rake
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
+ - !ruby/object:Gem::Dependency
90
+ name: rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description: A mixin to add configuration functionality to your classes
104
+ email:
105
+ - andyholland1991@aol.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - CHANGELOG.md
111
+ - LICENSE
112
+ - README.md
113
+ - dry-configurable.gemspec
114
+ - lib/dry-configurable.rb
115
+ - lib/dry/configurable.rb
116
+ - lib/dry/configurable/class_methods.rb
117
+ - lib/dry/configurable/compiler.rb
118
+ - lib/dry/configurable/config.rb
119
+ - lib/dry/configurable/constants.rb
120
+ - lib/dry/configurable/dsl.rb
121
+ - lib/dry/configurable/dsl/args.rb
122
+ - lib/dry/configurable/errors.rb
123
+ - lib/dry/configurable/instance_methods.rb
124
+ - lib/dry/configurable/methods.rb
125
+ - lib/dry/configurable/setting.rb
126
+ - lib/dry/configurable/settings.rb
127
+ - lib/dry/configurable/test_interface.rb
128
+ - lib/dry/configurable/version.rb
129
+ homepage: https://dry-rb.org/gems/dry-configurable
130
+ licenses:
131
+ - MIT
132
+ metadata:
133
+ allowed_push_host: https://rubygems.org
134
+ changelog_uri: https://github.com/dry-rb/dry-configurable/blob/master/CHANGELOG.md
135
+ source_code_uri: https://github.com/dry-rb/dry-configurable
136
+ bug_tracker_uri: https://github.com/dry-rb/dry-configurable/issues
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.4.0
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.0.3
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: A mixin to add configuration functionality to your classes
156
+ test_files: []