config 2.0.0 → 3.0.0
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 +4 -4
- data/CHANGELOG.md +116 -36
- data/CONTRIBUTING.md +32 -0
- data/README.md +149 -64
- data/config.gemspec +28 -24
- data/lib/config.rb +34 -33
- data/lib/config/configuration.rb +36 -0
- data/lib/config/integrations/rails/railtie.rb +1 -1
- data/lib/config/options.rb +8 -52
- data/lib/config/sources/env_source.rb +73 -0
- data/lib/config/validation/schema.rb +8 -9
- data/lib/config/validation/validate.rb +13 -8
- data/lib/config/version.rb +1 -1
- data/lib/generators/config/templates/config.rb +6 -1
- metadata +44 -89
- data/lib/config/integrations/rails/engine.rb +0 -9
- data/spec/app/rails_5.1/README.md +0 -24
- data/spec/app/rails_5.2/README.md +0 -24
- data/spec/app/rails_5/README.md +0 -24
data/config.gemspec
CHANGED
@@ -7,44 +7,48 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Config::VERSION
|
8
8
|
s.date = Time.now.strftime '%F'
|
9
9
|
s.authors = ['Piotr Kuczynski', 'Fred Wu', 'Jacques Crocker']
|
10
|
-
s.email = %w
|
10
|
+
s.email = %w[piotr.kuczynski@gmail.com ifredwu@gmail.com railsjedi@gmail.com]
|
11
11
|
s.summary = 'Effortless multi-environment settings in Rails, Sinatra, Pandrino and others'
|
12
12
|
s.description = 'Easiest way to manage multi-environment settings in any ruby project or framework: ' +
|
13
13
|
'Rails, Sinatra, Pandrino and others'
|
14
|
-
s.homepage = 'https://github.com/
|
14
|
+
s.homepage = 'https://github.com/rubyconfig/config'
|
15
15
|
s.license = 'MIT'
|
16
|
-
s.extra_rdoc_files = %w
|
16
|
+
s.extra_rdoc_files = %w[README.md CHANGELOG.md CONTRIBUTING.md LICENSE.md]
|
17
17
|
s.rdoc_options = ['--charset=UTF-8']
|
18
|
+
s.post_install_message = "\n\e[33mThanks for installing Config\e[0m
|
19
|
+
Please consider donating to our open collective to help us maintain this project.
|
20
|
+
\n
|
21
|
+
Donate: \e[34mhttps://opencollective.com/rubyconfig/donate\e[0m\n"
|
18
22
|
|
19
23
|
s.files = `git ls-files`.split($/)
|
20
|
-
s.files.select! { |file| /(^lib
|
21
|
-
s.files += Dir.glob('doc/**/*')
|
24
|
+
s.files.select! { |file| /(^lib\/|^\w+.md$|\.gemspec$)/ =~ file }
|
22
25
|
|
23
26
|
s.require_paths = ['lib']
|
24
27
|
s.required_ruby_version = '>= 2.4.0'
|
25
28
|
|
26
|
-
s.add_dependency 'activesupport', '>= 4.2'
|
27
29
|
s.add_dependency 'deep_merge', '~> 1.2', '>= 1.2.1'
|
28
|
-
s.add_dependency 'dry-
|
30
|
+
s.add_dependency 'dry-validation', '~> 1.0', '>= 1.0.0'
|
29
31
|
|
30
|
-
s.add_development_dependency 'rake',
|
32
|
+
s.add_development_dependency 'rake', '~> 12.0', '>= 12.0.0'
|
31
33
|
|
32
34
|
# Testing
|
33
|
-
s.add_development_dependency 'appraisal',
|
34
|
-
s.add_development_dependency '
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
35
|
+
s.add_development_dependency 'appraisal', '~> 2.3', '>= 2.3.0'
|
36
|
+
s.add_development_dependency 'rspec', '~> 3.9', '>= 3.9.0'
|
37
|
+
|
38
|
+
# Default RSpec run will test against latest Rails app
|
39
|
+
unless ENV['APPRAISAL_INITIALIZED'] || ENV['GITHUB_ACTIONS']
|
40
|
+
gems_to_install = /gem "(.*?)", "(.*?)"(?!, platform: (?!\[:ruby\]))/
|
41
|
+
File.read(Dir['gemfiles/rails*.gemfile'].sort.last).scan(gems_to_install) do |name, version|
|
42
|
+
s.add_development_dependency name, version
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if ENV['GITHUB_ACTIONS']
|
47
|
+
# Code coverage is needed only in CI
|
48
|
+
s.add_development_dependency 'simplecov', '~> 0.18.5' if RUBY_ENGINE == 'ruby'
|
49
|
+
else
|
50
|
+
# Static code analysis to be used locally
|
51
|
+
s.add_development_dependency 'mdl', '~> 0.9', '>= 0.9.0'
|
52
|
+
s.add_development_dependency 'rubocop', '~> 0.85.0'
|
49
53
|
end
|
50
54
|
end
|
data/lib/config.rb
CHANGED
@@ -1,40 +1,35 @@
|
|
1
|
-
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
-
|
3
1
|
require 'config/compatibility'
|
4
2
|
require 'config/options'
|
3
|
+
require 'config/configuration'
|
5
4
|
require 'config/version'
|
6
|
-
require 'config/integrations/rails/engine' if defined?(::Rails)
|
7
5
|
require 'config/sources/yaml_source'
|
8
6
|
require 'config/sources/hash_source'
|
7
|
+
require 'config/sources/env_source'
|
9
8
|
require 'config/validation/schema'
|
10
9
|
require 'deep_merge'
|
11
10
|
|
12
11
|
module Config
|
13
12
|
extend Config::Validation::Schema
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@@knockout_prefix = nil
|
31
|
-
@@merge_nil_values = true
|
32
|
-
@@overwrite_arrays = true
|
33
|
-
@@merge_hash_arrays = false
|
13
|
+
extend Config::Configuration.new(
|
14
|
+
# general options
|
15
|
+
const_name: 'Settings',
|
16
|
+
use_env: false,
|
17
|
+
env_prefix: 'Settings',
|
18
|
+
env_separator: '.',
|
19
|
+
env_converter: :downcase,
|
20
|
+
env_parse_values: true,
|
21
|
+
fail_on_missing: false,
|
22
|
+
# deep_merge options
|
23
|
+
knockout_prefix: nil,
|
24
|
+
merge_nil_values: true,
|
25
|
+
overwrite_arrays: true,
|
26
|
+
merge_hash_arrays: false,
|
27
|
+
validation_contract: nil
|
28
|
+
)
|
34
29
|
|
35
30
|
def self.setup
|
36
|
-
yield self
|
37
|
-
|
31
|
+
yield self unless @_ran_once
|
32
|
+
@_ran_once = true
|
38
33
|
end
|
39
34
|
|
40
35
|
# Create a populated Options instance from a settings file. If a second file is given, then the sections of that
|
@@ -47,8 +42,9 @@ module Config
|
|
47
42
|
config.add_source!(file.to_s)
|
48
43
|
end
|
49
44
|
|
45
|
+
config.add_source!(Sources::EnvSource.new(ENV)) if Config.use_env
|
46
|
+
|
50
47
|
config.load!
|
51
|
-
config.load_env! if @@use_env
|
52
48
|
config
|
53
49
|
end
|
54
50
|
|
@@ -61,16 +57,21 @@ module Config
|
|
61
57
|
|
62
58
|
def self.setting_files(config_root, env)
|
63
59
|
[
|
64
|
-
File.join(config_root,
|
65
|
-
File.join(config_root,
|
66
|
-
File.join(config_root,
|
67
|
-
|
68
|
-
File.join(config_root, "settings.local.yml").to_s,
|
69
|
-
File.join(config_root, "settings", "#{env}.local.yml").to_s,
|
70
|
-
File.join(config_root, "environments", "#{env}.local.yml").to_s
|
60
|
+
File.join(config_root, 'settings.yml').to_s,
|
61
|
+
File.join(config_root, 'settings', "#{env}.yml").to_s,
|
62
|
+
File.join(config_root, 'environments', "#{env}.yml").to_s,
|
63
|
+
*local_setting_files(config_root, env)
|
71
64
|
].freeze
|
72
65
|
end
|
73
66
|
|
67
|
+
def self.local_setting_files(config_root, env)
|
68
|
+
[
|
69
|
+
(File.join(config_root, 'settings.local.yml').to_s if env != 'test'),
|
70
|
+
File.join(config_root, 'settings', "#{env}.local.yml").to_s,
|
71
|
+
File.join(config_root, 'environments', "#{env}.local.yml").to_s
|
72
|
+
].compact
|
73
|
+
end
|
74
|
+
|
74
75
|
def self.reload!
|
75
76
|
Object.const_get(Config.const_name).reload!
|
76
77
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Config
|
2
|
+
# The main configuration backbone
|
3
|
+
class Configuration < Module
|
4
|
+
# Accepts configuration options,
|
5
|
+
# initializing a module that can be used to extend
|
6
|
+
# the necessary class with the provided config methods
|
7
|
+
def initialize(**attributes)
|
8
|
+
attributes.each do |name, default|
|
9
|
+
define_reader(name, default)
|
10
|
+
define_writer(name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def define_reader(name, default)
|
17
|
+
variable = :"@#{name}"
|
18
|
+
|
19
|
+
define_method(name) do
|
20
|
+
if instance_variable_defined?(variable)
|
21
|
+
instance_variable_get(variable)
|
22
|
+
else
|
23
|
+
default
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def define_writer(name)
|
29
|
+
variable = :"@#{name}"
|
30
|
+
|
31
|
+
define_method("#{name}=") do |value|
|
32
|
+
instance_variable_set(variable, value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -23,7 +23,7 @@ module Config
|
|
23
23
|
# Development environment should reload settings on every request
|
24
24
|
if ::Rails.env.development?
|
25
25
|
initializer :config_reload_on_development do
|
26
|
-
|
26
|
+
ActiveSupport.on_load :action_controller_base do
|
27
27
|
if ::Rails::VERSION::MAJOR >= 4
|
28
28
|
prepend_before_action { ::Config.reload! }
|
29
29
|
else
|
data/lib/config/options.rb
CHANGED
@@ -31,42 +31,6 @@ module Config
|
|
31
31
|
@config_sources.unshift(source)
|
32
32
|
end
|
33
33
|
|
34
|
-
def reload_env!
|
35
|
-
return self if ENV.nil? || ENV.empty?
|
36
|
-
|
37
|
-
hash = Hash.new
|
38
|
-
|
39
|
-
ENV.each do |variable, value|
|
40
|
-
separator = Config.env_separator
|
41
|
-
prefix = (Config.env_prefix || Config.const_name).to_s.split(separator)
|
42
|
-
|
43
|
-
keys = variable.to_s.split(separator)
|
44
|
-
|
45
|
-
next if keys.shift(prefix.size) != prefix
|
46
|
-
|
47
|
-
keys.map! { |key|
|
48
|
-
case Config.env_converter
|
49
|
-
when :downcase then
|
50
|
-
key.downcase.to_sym
|
51
|
-
when nil then
|
52
|
-
key.to_sym
|
53
|
-
else
|
54
|
-
raise "Invalid ENV variables name converter: #{Config.env_converter}"
|
55
|
-
end
|
56
|
-
}
|
57
|
-
|
58
|
-
leaf = keys[0...-1].inject(hash) { |h, key|
|
59
|
-
h[key] ||= {}
|
60
|
-
}
|
61
|
-
|
62
|
-
leaf[keys.last] = Config.env_parse_values ? __value(value) : value
|
63
|
-
end
|
64
|
-
|
65
|
-
merge!(hash)
|
66
|
-
end
|
67
|
-
|
68
|
-
alias :load_env! :reload_env!
|
69
|
-
|
70
34
|
# look through all our sources and rebuild the configuration
|
71
35
|
def reload!
|
72
36
|
conf = {}
|
@@ -91,7 +55,6 @@ module Config
|
|
91
55
|
# swap out the contents of the OStruct with a hash (need to recursively convert)
|
92
56
|
marshal_load(__convert(conf).marshal_dump)
|
93
57
|
|
94
|
-
reload_env! if Config.use_env
|
95
58
|
validate!
|
96
59
|
|
97
60
|
self
|
@@ -118,6 +81,8 @@ module Config
|
|
118
81
|
result
|
119
82
|
end
|
120
83
|
|
84
|
+
alias :to_h :to_hash
|
85
|
+
|
121
86
|
def each(*args, &block)
|
122
87
|
marshal_dump.each(*args, &block)
|
123
88
|
end
|
@@ -127,6 +92,10 @@ module Config
|
|
127
92
|
to_hash.to_json(*args)
|
128
93
|
end
|
129
94
|
|
95
|
+
def as_json(options = nil)
|
96
|
+
to_hash.as_json(options)
|
97
|
+
end
|
98
|
+
|
130
99
|
def merge!(hash)
|
131
100
|
current = to_hash
|
132
101
|
DeepMerge.deep_merge!(
|
@@ -143,7 +112,7 @@ module Config
|
|
143
112
|
end
|
144
113
|
|
145
114
|
# Some keywords that don't play nicely with OpenStruct
|
146
|
-
SETTINGS_RESERVED_NAMES = %w[select collect test count zip].freeze
|
115
|
+
SETTINGS_RESERVED_NAMES = %w[select collect test count zip min max exit!].freeze
|
147
116
|
|
148
117
|
# An alternative mechanism for property access.
|
149
118
|
# This let's you do foo['bar'] along with foo.bar.
|
@@ -201,7 +170,6 @@ module Config
|
|
201
170
|
|
202
171
|
h.each do |k, v|
|
203
172
|
k = k.to_s if !k.respond_to?(:to_sym) && k.respond_to?(:to_s)
|
204
|
-
s.new_ostruct_member(k)
|
205
173
|
|
206
174
|
if v.is_a?(Hash)
|
207
175
|
v = v["type"] == "hash" ? v["contents"] : __convert(v)
|
@@ -209,21 +177,9 @@ module Config
|
|
209
177
|
v = v.collect { |e| e.instance_of?(Hash) ? __convert(e) : e }
|
210
178
|
end
|
211
179
|
|
212
|
-
s
|
180
|
+
s[k] = v
|
213
181
|
end
|
214
182
|
s
|
215
183
|
end
|
216
|
-
|
217
|
-
# Try to convert string to a correct type
|
218
|
-
def __value(v)
|
219
|
-
case v
|
220
|
-
when 'false'
|
221
|
-
false
|
222
|
-
when 'true'
|
223
|
-
true
|
224
|
-
else
|
225
|
-
Integer(v) rescue Float(v) rescue v
|
226
|
-
end
|
227
|
-
end
|
228
184
|
end
|
229
185
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Config
|
2
|
+
module Sources
|
3
|
+
# Allows settings to be loaded from a "flat" hash with string keys, like ENV.
|
4
|
+
class EnvSource
|
5
|
+
attr_reader :prefix
|
6
|
+
attr_reader :separator
|
7
|
+
attr_reader :converter
|
8
|
+
attr_reader :parse_values
|
9
|
+
|
10
|
+
def initialize(env,
|
11
|
+
prefix: Config.env_prefix || Config.const_name,
|
12
|
+
separator: Config.env_separator,
|
13
|
+
converter: Config.env_converter,
|
14
|
+
parse_values: Config.env_parse_values)
|
15
|
+
@env = env
|
16
|
+
@prefix = prefix.to_s.split(separator)
|
17
|
+
@separator = separator
|
18
|
+
@converter = converter
|
19
|
+
@parse_values = parse_values
|
20
|
+
end
|
21
|
+
|
22
|
+
def load
|
23
|
+
return {} if @env.nil? || @env.empty?
|
24
|
+
|
25
|
+
hash = Hash.new
|
26
|
+
|
27
|
+
@env.each do |variable, value|
|
28
|
+
keys = variable.to_s.split(separator)
|
29
|
+
|
30
|
+
next if keys.shift(prefix.size) != prefix
|
31
|
+
|
32
|
+
keys.map! { |key|
|
33
|
+
case converter
|
34
|
+
when :downcase then
|
35
|
+
key.downcase
|
36
|
+
when nil then
|
37
|
+
key
|
38
|
+
else
|
39
|
+
raise "Invalid ENV variables name converter: #{converter}"
|
40
|
+
end
|
41
|
+
}
|
42
|
+
|
43
|
+
leaf = keys[0...-1].inject(hash) { |h, key|
|
44
|
+
h[key] ||= {}
|
45
|
+
}
|
46
|
+
|
47
|
+
unless leaf.is_a?(Hash)
|
48
|
+
conflicting_key = (prefix + keys[0...-1]).join(separator)
|
49
|
+
raise "Environment variable #{variable} conflicts with variable #{conflicting_key}"
|
50
|
+
end
|
51
|
+
|
52
|
+
leaf[keys.last] = parse_values ? __value(value) : value
|
53
|
+
end
|
54
|
+
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Try to convert string to a correct type
|
61
|
+
def __value(v)
|
62
|
+
case v
|
63
|
+
when 'false'
|
64
|
+
false
|
65
|
+
when 'true'
|
66
|
+
true
|
67
|
+
else
|
68
|
+
Integer(v) rescue Float(v) rescue v
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -1,21 +1,20 @@
|
|
1
|
-
require 'dry-schema'
|
2
|
-
require 'config/validation/schema'
|
3
|
-
|
4
1
|
module Config
|
5
2
|
module Validation
|
6
3
|
module Schema
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
# Assigns schema configuration option
|
5
|
+
def schema=(value)
|
6
|
+
@schema = value
|
7
|
+
end
|
10
8
|
|
11
9
|
def schema(&block)
|
12
10
|
if block_given?
|
13
|
-
|
11
|
+
# Delay require until optional schema validation is requested
|
12
|
+
require 'dry-validation'
|
13
|
+
@schema = Dry::Schema.define(&block)
|
14
14
|
else
|
15
|
-
|
15
|
+
@schema
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
19
18
|
end
|
20
19
|
end
|
21
20
|
end
|
@@ -3,18 +3,23 @@ require 'config/validation/error'
|
|
3
3
|
module Config
|
4
4
|
module Validation
|
5
5
|
module Validate
|
6
|
-
|
7
6
|
def validate!
|
8
|
-
|
9
|
-
|
7
|
+
validate_using!(Config.validation_contract)
|
8
|
+
validate_using!(Config.schema)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def validate_using!(validator)
|
14
|
+
if validator
|
15
|
+
result = validator.call(to_hash)
|
16
|
+
|
17
|
+
return if result.success?
|
18
|
+
|
19
|
+
error = Config::Validation::Error.format(result)
|
20
|
+
raise Config::Validation::Error, "Config validation failed:\n\n#{error}"
|
15
21
|
end
|
16
22
|
end
|
17
|
-
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|