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.
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(piotr.kuczynski@gmail.com ifredwu@gmail.com railsjedi@gmail.com)
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/railsconfig/config'
14
+ s.homepage = 'https://github.com/rubyconfig/config'
15
15
  s.license = 'MIT'
16
- s.extra_rdoc_files = %w(README.md CHANGELOG.md LICENSE.md)
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\/|\.md$|\.gemspec$)/ =~ file }
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-schema', '~> 1.0'
30
+ s.add_dependency 'dry-validation', '~> 1.0', '>= 1.0.0'
29
31
 
30
- s.add_development_dependency 'rake', '~> 12.0', '>= 12.0.0'
32
+ s.add_development_dependency 'rake', '~> 12.0', '>= 12.0.0'
31
33
 
32
34
  # Testing
33
- s.add_development_dependency 'appraisal', '~> 2.2', '>= 2.2.0'
34
- s.add_development_dependency 'rails', '~> 5.2', '>= 5.2.2'
35
- s.add_development_dependency 'rspec', '~> 3.7', '>= 3.7.0'
36
- s.add_development_dependency 'rspec-rails', '~> 3.7', '>= 3.7.2'
37
- s.add_development_dependency 'test-unit', '~> 3.2', '>= 3.2.7'
38
- s.add_development_dependency 'sqlite3', '~> 1.3', '>= 1.3.13'
39
-
40
- # Static code analysis
41
- s.add_development_dependency 'mdl', '~> 0.5', '>= 0.5.0'
42
-
43
- # Version 0.62 requires Ruby 2.2
44
- s.add_development_dependency 'rubocop', '~> 0.62'
45
-
46
- if ENV['TRAVIS']
47
- s.add_development_dependency 'codeclimate-test-reporter', '~> 1.0.9'
48
- s.add_development_dependency 'simplecov', '~> 0.13.0'
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
- # Ensures the setup only gets run once
16
- @@_ran_once = false
17
-
18
- mattr_accessor :const_name, :use_env, :env_prefix, :env_separator,
19
- :env_converter, :env_parse_values, :fail_on_missing
20
- @@const_name = 'Settings'
21
- @@use_env = false
22
- @@env_prefix = @@const_name
23
- @@env_separator = '.'
24
- @@env_converter = :downcase
25
- @@env_parse_values = true
26
- @@fail_on_missing = false
27
-
28
- # deep_merge options
29
- mattr_accessor :knockout_prefix, :merge_nil_values, :overwrite_arrays, :merge_hash_arrays
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 if @@_ran_once == false
37
- @@_ran_once = true
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, "settings.yml").to_s,
65
- File.join(config_root, "settings", "#{env}.yml").to_s,
66
- File.join(config_root, "environments", "#{env}.yml").to_s,
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
- ActionController::Base.class_eval do
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
@@ -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.send("#{k}=".to_sym, v)
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
- mattr_writer :schema
9
- @@schema = nil
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
- @@schema = Dry::Schema.define(&block)
11
+ # Delay require until optional schema validation is requested
12
+ require 'dry-validation'
13
+ @schema = Dry::Schema.define(&block)
14
14
  else
15
- @@schema
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
- if Config.schema
9
- v_res = Config.schema.(self.to_hash)
7
+ validate_using!(Config.validation_contract)
8
+ validate_using!(Config.schema)
9
+ end
10
+
11
+ private
10
12
 
11
- unless v_res.success?
12
- error = Config::Validation::Error.format(v_res)
13
- raise Config::Validation::Error.new("Config validation failed:\n\n#{error}")
14
- end
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