dry-configurable 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +10 -21
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/ci.yml +70 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +30 -0
  9. data/.gitignore +1 -1
  10. data/.rspec +3 -1
  11. data/.rubocop.yml +89 -0
  12. data/CHANGELOG.md +87 -6
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +29 -0
  15. data/Gemfile +4 -0
  16. data/LICENSE +1 -1
  17. data/README.md +20 -32
  18. data/docsite/source/index.html.md +55 -0
  19. data/docsite/source/testing.html.md +27 -0
  20. data/dry-configurable.gemspec +10 -4
  21. data/lib/dry/configurable.rb +124 -118
  22. data/lib/dry/configurable/config.rb +136 -43
  23. data/lib/dry/configurable/error.rb +0 -2
  24. data/lib/dry/configurable/setting.rb +46 -0
  25. data/lib/dry/configurable/settings.rb +117 -0
  26. data/lib/dry/configurable/settings/argument_parser.rb +50 -0
  27. data/lib/dry/configurable/test_interface.rb +5 -1
  28. data/lib/dry/configurable/version.rb +1 -1
  29. metadata +56 -38
  30. data/.ruby-version +0 -1
  31. data/.travis.yml +0 -34
  32. data/lib/dry/configurable/argument_parser.rb +0 -91
  33. data/lib/dry/configurable/config/value.rb +0 -27
  34. data/lib/dry/configurable/nested_config.rb +0 -33
  35. data/spec/integration/configurable_spec.rb +0 -25
  36. data/spec/spec_helper.rb +0 -92
  37. data/spec/support/shared_examples/configurable.rb +0 -391
  38. data/spec/unit/dry/configurable/argument_parser_spec.rb +0 -114
  39. data/spec/unit/dry/configurable/config/value_spec.rb +0 -55
  40. data/spec/unit/dry/configurable/config_spec.rb +0 -186
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.4.0, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct)
@@ -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 [discourse.dry-rb.org](https://discourse.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 [discourse.dry-rb.org](http://discourse.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 [discourse.dry-rb.org](https://discourse.dry-rb.org) or join [our chat](https://dry-rb.zulipchat.com).
data/Gemfile CHANGED
@@ -7,10 +7,14 @@ group :test do
7
7
  gem 'codeclimate-test-reporter', require: false
8
8
  gem 'simplecov', require: false
9
9
  end
10
+
11
+ gem 'warning'
10
12
  end
11
13
 
12
14
  group :tools do
13
15
  gem 'guard'
14
16
  gem 'guard-rspec'
15
17
  gem 'listen', '3.0.6'
18
+ gem 'pry-byebug', platform: :mri
19
+ gem "ossy", git: "https://github.com/solnic/ossy.git", branch: "master"
16
20
  end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2017 dry-rb
3
+ Copyright (c) 2015-2019 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,47 +1,35 @@
1
1
  [gitter]: https://gitter.im/dry-rb/chat
2
2
  [gem]: https://rubygems.org/gems/dry-configurable
3
3
  [travis]: https://travis-ci.org/dry-rb/dry-configurable
4
- [code_climate]: https://codeclimate.com/github/dry-rb/dry-configurable
5
4
  [inch]: http://inch-ci.org/github/dry-rb/dry-configurable
5
+ [chat]: https://dry-rb.zulipchat.com
6
6
 
7
- # dry-configurable [![Join the Gitter chat](https://badges.gitter.im/Join%20Chat.svg)][gitter]
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
8
 
9
9
  [![Gem Version](https://img.shields.io/gem/v/dry-configurable.svg)][gem]
10
10
  [![Build Status](https://img.shields.io/travis/dry-rb/dry-configurable.svg)][travis]
11
- [![Code Climate](https://img.shields.io/codeclimate/github/dry-rb/dry-configurable.svg)][code_climate]
12
- [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/dry-rb/dry-configurable.svg)][code_climate]
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
13
  [![API Documentation Coverage](http://inch-ci.org/github/dry-rb/dry-configurable.svg)][inch]
14
14
 
15
- ## Synopsis
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
16
18
 
17
19
  ```ruby
18
- class App
19
- extend Dry::Configurable
20
-
21
- # Pass a block for nested configuration (works to any depth)
22
- setting :database do
23
- # Can pass a default value
24
- setting :dsn, 'sqlite:memory'
25
- end
26
- # Defaults to nil if no default value is given
27
- setting :adapter
28
- # Passing the reader option as true will create reader method for the class
29
- setting :pool, 5, reader: true
30
- # Passing the reader attributes works with nested configuration
31
- setting :uploader, reader: true do
32
- setting :bucket, 'dev'
33
- end
34
- end
35
-
36
- App.configure do |config|
37
- config.database.dsn = 'jdbc:sqlite:memory'
38
- end
39
-
40
- App.config.database.dsn
41
- # => 'jdbc:sqlite:memory'
42
- App.config.adapter # => nil
43
- App.pool # => 5
44
- App.uploader.bucket # => 'dev'
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
45
33
  ```
46
34
 
47
35
  ## Links
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: Introduction & Usage
3
+ description: Thread-safe configuration mixin
4
+ layout: gem-single
5
+ order: 7
6
+ type: gem
7
+ name: dry-configurable
8
+ sections:
9
+ - testing
10
+ ---
11
+
12
+ ### Introduction
13
+
14
+ `dry-configurable` is a simple mixin to add thread-safe configuration behaviour to your classes. There are many libraries that make use of configuration, and each seemed to have their own implementation with a similar or duplicate interface, so we thought it was strange that this behaviour had not already been encapsulated into a reusable gem, hence `dry-configurable` was born.
15
+
16
+ ### Usage
17
+
18
+ `dry-configurable` is extremely simple to use, just extend the mixin and use the `setting` macro to add configuration options:
19
+
20
+ ```ruby
21
+ class App
22
+ extend Dry::Configurable
23
+
24
+ # Pass a block for nested configuration (works to any depth)
25
+ setting :database do
26
+ # Can pass a default value
27
+ setting :dsn, 'sqlite:memory'
28
+ end
29
+ # Defaults to nil if no default value is given
30
+ setting :adapter
31
+ # Pre-process values
32
+ setting(:path, 'test') { |value| Pathname(value) }
33
+ # Passing the reader option as true will create attr_reader method for the class
34
+ setting :pool, 5, reader: true
35
+ # Passing the reader attributes works with nested configuration
36
+ setting :uploader, reader: true do
37
+ setting :bucket, 'dev'
38
+ end
39
+ end
40
+
41
+ App.config.database.dsn
42
+ # => "sqlite:memory"
43
+
44
+ App.config.database.dsn = 'jdbc:sqlite:memory'
45
+ App.config.database.dsn
46
+ # => "jdbc:sqlite:memory"
47
+ App.config.adapter
48
+ # => nil
49
+ App.config.path
50
+ # => #<Pathname:test>
51
+ App.pool
52
+ # => 5
53
+ App.uploader.bucket
54
+ # => 'dev'
55
+ ```
@@ -0,0 +1,27 @@
1
+ ---
2
+ title: Testing
3
+ layout: gem-single
4
+ name: dry-configurable
5
+ ---
6
+
7
+ ### How to reset the config to its original state on testing environment
8
+
9
+ update `spec_helper.rb` :
10
+
11
+ ```ruby
12
+ require "dry/configurable/test_interface"
13
+
14
+ # this is your module/class that extended by Dry::Configurable
15
+ module AwesomeModule
16
+ enable_test_interface
17
+ end
18
+ ```
19
+
20
+ and on spec file (`xxx_spec.rb`) :
21
+
22
+ ```ruby
23
+ before(:all) { AwesomeModule.reset_config }
24
+ # or
25
+ before(:each) { AwesomeModule.reset_config }
26
+
27
+ ```
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require File.expand_path('../lib/dry/configurable/version', __FILE__)
3
2
 
4
3
  Gem::Specification.new do |spec|
@@ -7,15 +6,22 @@ Gem::Specification.new do |spec|
7
6
  spec.authors = ['Andy Holland']
8
7
  spec.email = ['andyholland1991@aol.com']
9
8
  spec.summary = 'A mixin to add configuration functionality to your classes'
10
- spec.homepage = 'https://github.com/dryrb/dry-configurable'
9
+ spec.homepage = 'https://github.com/dry-rb/dry-configurable'
11
10
  spec.license = 'MIT'
12
11
 
13
- spec.files = `git ls-files -z`.split("\x0")
14
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
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) }
15
14
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
15
  spec.require_paths = ['lib']
17
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.4.0"
18
23
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
24
+ spec.add_runtime_dependency 'dry-core', '~> 0.4', '>= 0.4.7'
19
25
 
20
26
  spec.add_development_dependency 'bundler'
21
27
  spec.add_development_dependency 'rake'
@@ -1,9 +1,6 @@
1
- require 'concurrent'
2
- require 'dry/configurable/config'
1
+ require 'dry/core/constants'
2
+ require 'dry/configurable/settings'
3
3
  require 'dry/configurable/error'
4
- require 'dry/configurable/nested_config'
5
- require 'dry/configurable/argument_parser'
6
- require 'dry/configurable/config/value'
7
4
  require 'dry/configurable/version'
8
5
 
9
6
  # A collection of micro-libraries, each intended to encapsulate
@@ -11,7 +8,7 @@ require 'dry/configurable/version'
11
8
  module Dry
12
9
  # A simple configuration mixin
13
10
  #
14
- # @example
11
+ # @example class-level configuration
15
12
  #
16
13
  # class App
17
14
  # extend Dry::Configurable
@@ -21,30 +18,122 @@ module Dry
21
18
  # end
22
19
  # end
23
20
  #
24
- # App.configure do |config|
25
- # config.database.dsn = 'jdbc:sqlite:memory'
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
26
31
  # end
27
32
  #
28
- # App.config.database.dsn
29
- # # => "jdbc:sqlite:memory'"
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!
30
40
  #
31
41
  # @api public
32
42
  module Configurable
33
- # @private
34
- def self.extended(base)
35
- base.class_eval do
36
- @_config_mutex = ::Mutex.new
37
- @_settings = ::Concurrent::Array.new
38
- @_reader_attributes = ::Concurrent::Array.new
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
+ # a 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
+ raise_already_defined_config(key) if _settings.config_defined?
69
+
70
+ setting = _settings.add(key, value, options, &block)
71
+
72
+ if setting.reader?
73
+ readers = singleton_class < Configurable ? singleton_class : self
74
+ readers.send(:define_method, setting.name) { config[setting.name] }
75
+ end
76
+ end
77
+
78
+ # Return an array of setting names
79
+ #
80
+ # @return [Set]
81
+ #
82
+ # @api public
83
+ def settings
84
+ _settings.names
85
+ end
86
+
87
+ # @private no, really...
88
+ def _settings
89
+ @settings
90
+ end
91
+
92
+ private
93
+
94
+ # @private
95
+ def raise_already_defined_config(key)
96
+ raise AlreadyDefinedConfig,
97
+ "Cannot add setting +#{key}+, #{self} is already configured"
98
+ end
99
+
100
+ # @private
101
+ def inherited(subclass)
102
+ parent = self
103
+ subclass.instance_exec do
104
+ @settings = parent._settings.dup
105
+ end
106
+
107
+ if singleton_class < Configurable
108
+ parent_config = @config
109
+ subclass.instance_exec do
110
+ @config = _settings.create_config
111
+ @config.define!(parent_config.to_h) if parent_config.defined?
112
+ end
113
+ end
114
+
115
+ super
116
+ end
117
+ end
118
+
119
+ class << self
120
+ # @private
121
+ def extended(base)
122
+ base.extend(ClassMethods)
123
+ base.class_eval do
124
+ @config = _settings.create_config
125
+ end
126
+ end
127
+
128
+ # @private
129
+ def included(base)
130
+ base.extend(ClassMethods)
39
131
  end
40
132
  end
41
133
 
42
134
  # @private
43
- def inherited(subclass)
44
- subclass.instance_variable_set(:@_config_mutex, ::Mutex.new)
45
- subclass.instance_variable_set(:@_settings, @_settings.clone)
46
- subclass.instance_variable_set(:@_reader_attributes, @_reader_attributes.clone)
47
- subclass.instance_variable_set(:@_config, @_config.clone) if defined?(@_config)
135
+ def initialize(*)
136
+ @config = self.class._settings.create_config
48
137
  super
49
138
  end
50
139
 
@@ -54,8 +143,8 @@ module Dry
54
143
  #
55
144
  # @api public
56
145
  def config
57
- return @_config if defined?(@_config)
58
- create_config
146
+ return @config if @config.defined?
147
+ @config.define!
59
148
  end
60
149
 
61
150
  # Return configuration
@@ -66,8 +155,9 @@ module Dry
66
155
  #
67
156
  # @api public
68
157
  def configure
69
- raise_frozen_config if frozen?
158
+ raise FrozenConfig, 'Cannot modify frozen config' if frozen?
70
159
  yield(config) if block_given?
160
+ self
71
161
  end
72
162
 
73
163
  # Finalize and freeze configuration
@@ -80,106 +170,22 @@ module Dry
80
170
  config.finalize!
81
171
  end
82
172
 
83
- # Add a setting to the configuration
84
- #
85
- # @param [Mixed] key
86
- # The accessor key for the configuration value
87
- # @param [Mixed] default
88
- # The default config value
89
- #
90
- # @yield
91
- # If a block is given, it will be evaluated in the context of
92
- # and new configuration class, and bound as the default value
93
- #
94
- # @return [Dry::Configurable::Config]
95
- #
96
173
  # @api public
97
- def setting(key, *args, &block)
98
- raise_already_defined_config(key) if defined?(@_config)
99
- value, options = ArgumentParser.call(args)
100
- if block
101
- if block.parameters.empty?
102
- value = _config_for(&block)
103
- else
104
- processor = block
105
- end
174
+ def dup
175
+ super.tap do |copy|
176
+ copy.instance_variable_set(:@config, config.dup)
106
177
  end
107
-
108
- _settings << ::Dry::Configurable::Config::Value.new(
109
- key,
110
- !value.nil? ? value : ::Dry::Configurable::Config::Value::NONE,
111
- processor || ::Dry::Configurable::Config::DEFAULT_PROCESSOR
112
- )
113
- store_reader_options(key, options) if options.any?
114
178
  end
115
179
 
116
- # Return an array of setting names
117
- #
118
- # @return [Array]
119
- #
120
180
  # @api public
121
- def settings
122
- _settings.map(&:name)
123
- end
124
-
125
- # @private no, really...
126
- def _settings
127
- @_settings
128
- end
129
-
130
- def _reader_attributes
131
- @_reader_attributes
132
- end
133
-
134
- private
135
-
136
- # @private
137
- def _config_for(&block)
138
- ::Dry::Configurable::NestedConfig.new(&block)
139
- end
140
-
141
- # @private
142
- def create_config
143
- @_config_mutex.synchronize do
144
- create_config_for_nested_configurations
145
- @_config = ::Dry::Configurable::Config.create(_settings) unless _settings.empty?
181
+ def clone
182
+ if frozen?
183
+ super
184
+ else
185
+ super.tap do |copy|
186
+ copy.instance_variable_set(:@config, config.dup)
187
+ end
146
188
  end
147
189
  end
148
-
149
- # @private
150
- def create_config_for_nested_configurations
151
- nested_configs.map(&:create_config)
152
- end
153
-
154
- # @private
155
- def nested_configs
156
- _settings.select { |setting| setting.value.is_a?(::Dry::Configurable::NestedConfig) }.map(&:value)
157
- end
158
-
159
- # @private
160
- def raise_already_defined_config(key)
161
- raise AlreadyDefinedConfig,
162
- "Cannot add setting +#{key}+, #{self} is already configured"
163
- end
164
-
165
- # @private
166
- def raise_frozen_config
167
- raise FrozenConfig, 'Cannot modify frozen config'
168
- end
169
-
170
- # @private
171
- def store_reader_options(key, options)
172
- _reader_attributes << key if options.fetch(:reader, false)
173
- end
174
-
175
- # @private
176
- def method_missing(method, *args, &block)
177
- _reader_attributes.include?(method) ? config.public_send(method, *args, &block) : super
178
- end
179
-
180
- # @private
181
- def respond_to_missing?(method, _include_private = false)
182
- _reader_attributes.include?(method) || super
183
- end
184
190
  end
185
191
  end