ef-config 1.4.1
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 +7 -0
- data/CHANGELOG.md +83 -0
- data/LICENSE.md +26 -0
- data/README.md +404 -0
- data/config.gemspec +51 -0
- data/lib/config.rb +107 -0
- data/lib/config/compatibility.rb +3 -0
- data/lib/config/integrations/heroku.rb +59 -0
- data/lib/config/integrations/rails/engine.rb +9 -0
- data/lib/config/integrations/rails/railtie.rb +38 -0
- data/lib/config/integrations/sinatra.rb +26 -0
- data/lib/config/options.rb +191 -0
- data/lib/config/rack/reloader.rb +15 -0
- data/lib/config/sources/hash_source.rb +16 -0
- data/lib/config/sources/yaml_source.rb +31 -0
- data/lib/config/tasks/heroku.rake +7 -0
- data/lib/config/validation/error.rb +24 -0
- data/lib/config/validation/schema.rb +21 -0
- data/lib/config/validation/validate.rb +19 -0
- data/lib/config/version.rb +3 -0
- data/lib/generators/config/install_generator.rb +32 -0
- data/lib/generators/config/templates/config.rb +45 -0
- data/lib/generators/config/templates/settings.local.yml +0 -0
- data/lib/generators/config/templates/settings.yml +0 -0
- data/lib/generators/config/templates/settings/development.yml +0 -0
- data/lib/generators/config/templates/settings/production.yml +0 -0
- data/lib/generators/config/templates/settings/test.yml +0 -0
- data/spec/app/rails_5/README.md +24 -0
- metadata +334 -0
data/config.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require 'config/version'
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'ef-config'
|
9
|
+
s.version = Config::VERSION
|
10
|
+
s.date = Time.now.strftime '%F'
|
11
|
+
s.authors = ['Hadley', 'Piotr Kuczynski', 'Fred Wu', 'Jacques Crocker']
|
12
|
+
s.email = %w(hadley@everfest.com)
|
13
|
+
s.summary = 'Effortless multi-environment settings in Rails, Sinatra, Pandrino and others'
|
14
|
+
s.description = 'Everfest fork of config gem'
|
15
|
+
s.homepage = 'https://bitbucket.org/everfest/config'
|
16
|
+
s.license = 'MIT'
|
17
|
+
s.extra_rdoc_files = %w(README.md CHANGELOG.md LICENSE.md)
|
18
|
+
s.rdoc_options = ['--charset=UTF-8']
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split($/)
|
21
|
+
s.files.select! { |file| /(^lib\/|\.md$|\.gemspec$)/ =~ file }
|
22
|
+
s.files += Dir.glob('doc/**/*')
|
23
|
+
|
24
|
+
s.require_paths = ['lib']
|
25
|
+
s.required_ruby_version = '>= 2.0.0'
|
26
|
+
|
27
|
+
s.add_dependency 'activesupport', '>= 3.0'
|
28
|
+
s.add_dependency 'deep_merge', '~> 1.1.1'
|
29
|
+
s.add_dependency 'dry-validation', '~> 0.10.4' if RUBY_VERSION >= '2.1'
|
30
|
+
|
31
|
+
s.add_development_dependency 'bundler', '~> 1.13', '>= 1.13.6'
|
32
|
+
s.add_development_dependency 'rake', '~> 12.0', '>= 12.0.0'
|
33
|
+
|
34
|
+
# Testing
|
35
|
+
s.add_development_dependency 'appraisal', '~> 2.1', '>= 2.1.0'
|
36
|
+
s.add_development_dependency 'rails', '~> 5.0', '>= 5.0.1'
|
37
|
+
s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
|
38
|
+
s.add_development_dependency 'rspec-rails', '~> 3.5', '>= 3.5.2'
|
39
|
+
s.add_development_dependency 'test-unit', '~> 3.2', '>= 3.2.1'
|
40
|
+
s.add_development_dependency 'sqlite3', '~> 1.3', '>= 1.3.11'
|
41
|
+
s.add_development_dependency 'pry'
|
42
|
+
|
43
|
+
# Static code analysis
|
44
|
+
s.add_development_dependency 'mdl', '~> 0.4', '>= 0.4.0'
|
45
|
+
s.add_development_dependency 'rubocop', '~> 0.46', '>= 0.46.0'
|
46
|
+
|
47
|
+
if ENV['TRAVIS']
|
48
|
+
s.add_development_dependency 'simplecov', '~> 0.12.0'
|
49
|
+
s.add_development_dependency 'codeclimate-test-reporter', '~> 1.0.3'
|
50
|
+
end
|
51
|
+
end
|
data/lib/config.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
require 'config/compatibility'
|
4
|
+
require 'config/options'
|
5
|
+
require 'config/version'
|
6
|
+
require 'config/integrations/rails/engine' if defined?(::Rails)
|
7
|
+
require 'config/sources/yaml_source'
|
8
|
+
require 'config/sources/hash_source'
|
9
|
+
require 'config/validation/schema' if RUBY_VERSION >= '2.1'
|
10
|
+
require 'deep_merge'
|
11
|
+
|
12
|
+
module Config
|
13
|
+
extend Config::Validation::Schema if RUBY_VERSION >= '2.1'
|
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, :env_converter, :env_parse_values
|
19
|
+
@@const_name = 'Settings'
|
20
|
+
@@use_env = false
|
21
|
+
@@env_prefix = @@const_name
|
22
|
+
@@env_separator = '.'
|
23
|
+
@@env_converter = :downcase
|
24
|
+
@@env_parse_values = true
|
25
|
+
|
26
|
+
# deep_merge options
|
27
|
+
mattr_accessor :knockout_prefix, :overwrite_arrays
|
28
|
+
@@knockout_prefix = nil
|
29
|
+
@@overwrite_arrays = true
|
30
|
+
|
31
|
+
def self.setup
|
32
|
+
yield self if @@_ran_once == false
|
33
|
+
@@_ran_once = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a populated Options instance from a settings file. If a second file is given, then the sections of that
|
37
|
+
# file will overwrite existing sections of the first file.
|
38
|
+
def self.load_files(*files)
|
39
|
+
config = Options.new
|
40
|
+
|
41
|
+
# add settings sources
|
42
|
+
[files].flatten.compact.uniq.each do |file|
|
43
|
+
config.add_source!(file.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
config.load!
|
47
|
+
config.load_env! if @@use_env
|
48
|
+
config
|
49
|
+
end
|
50
|
+
|
51
|
+
# Loads and sets the settings constant!
|
52
|
+
def self.load_and_set_settings(*files)
|
53
|
+
Kernel.send(:remove_const, Config.const_name) if Kernel.const_defined?(Config.const_name)
|
54
|
+
Kernel.const_set(Config.const_name, Config.load_files(files))
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.load_and_set_nested_settings(base_path, default_file_name)
|
58
|
+
Kernel.send(:remove_const, Config.const_name) if Kernel.const_defined?(Config.const_name)
|
59
|
+
|
60
|
+
config = Options.new
|
61
|
+
config.add_source!(File.join(base_path, default_file_name).to_s)
|
62
|
+
#config.add_source!(File.join(base_path, priority_file_name).to_s)
|
63
|
+
|
64
|
+
folder_names = Dir.glob("#{base_path}/**/**").select {|f| File.directory? f}
|
65
|
+
|
66
|
+
folder_names.each do |folder_name|
|
67
|
+
namespace_path = folder_name.to_s.sub("#{base_path.to_s}/", "")
|
68
|
+
namespaces = namespace_path.split("/")
|
69
|
+
|
70
|
+
namespaces.each_with_index do |namespace, i|
|
71
|
+
path = namespaces.first(i + 1).join("/")
|
72
|
+
namespaced_default_file = File.join(base_path, path, default_file_name).to_s
|
73
|
+
#priority_default_file = File.join(base_path, path, priority_file_name).to_s
|
74
|
+
|
75
|
+
config.add_source!(namespaced_default_file.to_s, path)
|
76
|
+
#config.add_source!(priority_default_file.to_s, path)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
config.load!
|
81
|
+
config.load_env! if @@use_env
|
82
|
+
|
83
|
+
Kernel.const_set(Config.const_name, config)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.setting_files(config_root, env)
|
87
|
+
[
|
88
|
+
File.join(config_root, "settings.yml").to_s,
|
89
|
+
File.join(config_root, "settings", "#{env}.yml").to_s,
|
90
|
+
File.join(config_root, "environments", "#{env}.yml").to_s,
|
91
|
+
|
92
|
+
File.join(config_root, "settings.local.yml").to_s,
|
93
|
+
File.join(config_root, "settings", "#{env}.local.yml").to_s,
|
94
|
+
File.join(config_root, "environments", "#{env}.local.yml").to_s
|
95
|
+
].freeze
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.reload!
|
99
|
+
Kernel.const_get(Config.const_name).reload!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Rails integration
|
104
|
+
require('config/integrations/rails/railtie') if defined?(::Rails)
|
105
|
+
|
106
|
+
# Sinatra integration
|
107
|
+
require('config/integrations/sinatra') if defined?(::Sinatra)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
module Config
|
4
|
+
module Integrations
|
5
|
+
class Heroku < Struct.new(:app)
|
6
|
+
def invoke
|
7
|
+
puts 'Setting vars...'
|
8
|
+
heroku_command = "config:set #{vars}"
|
9
|
+
heroku(heroku_command)
|
10
|
+
puts 'Vars set:'
|
11
|
+
puts heroku_command
|
12
|
+
end
|
13
|
+
|
14
|
+
def vars
|
15
|
+
# Load only local options to Heroku
|
16
|
+
Config.load_and_set_settings(
|
17
|
+
Rails.root.join("config", "settings.local.yml").to_s,
|
18
|
+
Rails.root.join("config", "settings", "#{environment}.local.yml").to_s,
|
19
|
+
Rails.root.join("config", "environments", "#{environment}.local.yml").to_s
|
20
|
+
)
|
21
|
+
|
22
|
+
out = ''
|
23
|
+
dotted_hash = to_dotted_hash Kernel.const_get(Config.const_name).to_hash, {}, Config.const_name
|
24
|
+
dotted_hash.each {|key, value| out += " #{key}=#{value} "}
|
25
|
+
out
|
26
|
+
end
|
27
|
+
|
28
|
+
def environment
|
29
|
+
heroku("run 'echo $RAILS_ENV'").chomp[/(\w+)\z/]
|
30
|
+
end
|
31
|
+
|
32
|
+
def heroku(command)
|
33
|
+
with_app = app ? " --app #{app}" : ""
|
34
|
+
`heroku #{command}#{with_app}`
|
35
|
+
end
|
36
|
+
|
37
|
+
def `(command)
|
38
|
+
Bundler.with_clean_env { super }
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_dotted_hash(source, target = {}, namespace = nil)
|
42
|
+
prefix = "#{namespace}." if namespace
|
43
|
+
case source
|
44
|
+
when Hash
|
45
|
+
source.each do |key, value|
|
46
|
+
to_dotted_hash(value, target, "#{prefix}#{key}")
|
47
|
+
end
|
48
|
+
when Array
|
49
|
+
source.each_with_index do |value, index|
|
50
|
+
to_dotted_hash(value, target, "#{prefix}#{index}")
|
51
|
+
end
|
52
|
+
else
|
53
|
+
target[namespace] = source
|
54
|
+
end
|
55
|
+
target
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Config
|
2
|
+
module Integrations
|
3
|
+
module Rails
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
def preload
|
6
|
+
# Manually load the custom initializer before everything else
|
7
|
+
initializer = ::Rails.root.join('config', 'initializers', 'config.rb')
|
8
|
+
require initializer if File.exist?(initializer)
|
9
|
+
|
10
|
+
# Parse the settings before any of the initializers
|
11
|
+
Config.load_and_set_settings(
|
12
|
+
Config.setting_files(::Rails.root.join('config'), ::Rails.env)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Load rake tasks (eg. Heroku)
|
17
|
+
rake_tasks do
|
18
|
+
Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f }
|
19
|
+
end
|
20
|
+
|
21
|
+
config.before_configuration { preload }
|
22
|
+
|
23
|
+
# Development environment should reload settings on every request
|
24
|
+
if ::Rails.env.development?
|
25
|
+
initializer :config_reload_on_development do
|
26
|
+
ActionController::Base.class_eval do
|
27
|
+
if ::Rails::VERSION::MAJOR >= 4
|
28
|
+
prepend_before_action { ::Config.reload! }
|
29
|
+
else
|
30
|
+
prepend_before_filter { ::Config.reload! }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "config/rack/reloader"
|
2
|
+
|
3
|
+
module Config
|
4
|
+
# provide helper to register within your Sinatra app
|
5
|
+
#
|
6
|
+
# set :root, File.dirname(__FILE__)
|
7
|
+
# register Config
|
8
|
+
#
|
9
|
+
def self.registered(app)
|
10
|
+
app.configure do |inner_app|
|
11
|
+
|
12
|
+
env = inner_app.environment || ENV["RACK_ENV"]
|
13
|
+
root = inner_app.root
|
14
|
+
|
15
|
+
# use Padrino settings if applicable
|
16
|
+
if defined?(Padrino)
|
17
|
+
env = Padrino.env
|
18
|
+
root = Padrino.root
|
19
|
+
end
|
20
|
+
|
21
|
+
Config.load_and_set_settings(Config.setting_files(File.join(root, 'config'), env))
|
22
|
+
|
23
|
+
inner_app.use(::Config::Rack::Reloader) if inner_app.development?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'config/validation/validate' if RUBY_VERSION >= '2.1'
|
3
|
+
|
4
|
+
module Config
|
5
|
+
class Options < OpenStruct
|
6
|
+
include Enumerable
|
7
|
+
include Validation::Validate if RUBY_VERSION >= '2.1'
|
8
|
+
|
9
|
+
def keys
|
10
|
+
marshal_dump.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
marshal_dump.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_source!(source, namespace=nil)
|
18
|
+
# handle yaml file paths
|
19
|
+
source = (Sources::YAMLSource.new(source, namespace)) if source.is_a?(String)
|
20
|
+
source = (Sources::HashSource.new(source, namespace)) if source.is_a?(Hash)
|
21
|
+
|
22
|
+
@config_sources ||= []
|
23
|
+
@config_sources << source
|
24
|
+
end
|
25
|
+
|
26
|
+
def prepend_source!(source)
|
27
|
+
source = (Sources::YAMLSource.new(source)) if source.is_a?(String)
|
28
|
+
source = (Sources::HashSource.new(source)) if source.is_a?(Hash)
|
29
|
+
|
30
|
+
@config_sources ||= []
|
31
|
+
@config_sources.unshift(source)
|
32
|
+
end
|
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
|
+
keys = variable.to_s.split(Config.env_separator)
|
41
|
+
|
42
|
+
next if keys.shift != (Config.env_prefix || Config.const_name)
|
43
|
+
|
44
|
+
keys.map! { |key|
|
45
|
+
case Config.env_converter
|
46
|
+
when :downcase then
|
47
|
+
key.downcase.to_sym
|
48
|
+
when nil then
|
49
|
+
key.to_sym
|
50
|
+
else
|
51
|
+
raise "Invalid ENV variables name converter: #{Config.env_converter}"
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
leaf = keys[0...-1].inject(hash) { |h, key|
|
56
|
+
h[key] ||= {}
|
57
|
+
}
|
58
|
+
|
59
|
+
leaf[keys.last] = Config.env_parse_values ? __value(value) : value
|
60
|
+
end
|
61
|
+
|
62
|
+
merge!(hash)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias :load_env! :reload_env!
|
66
|
+
|
67
|
+
# look through all our sources and rebuild the configuration
|
68
|
+
def reload!
|
69
|
+
conf = {}
|
70
|
+
@config_sources.each do |source|
|
71
|
+
source_conf = source.load
|
72
|
+
|
73
|
+
if conf.empty?
|
74
|
+
conf = source_conf
|
75
|
+
else
|
76
|
+
DeepMerge.deep_merge!(source_conf,
|
77
|
+
conf,
|
78
|
+
preserve_unmergeables: false,
|
79
|
+
knockout_prefix: Config.knockout_prefix,
|
80
|
+
overwrite_arrays: Config.overwrite_arrays)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# swap out the contents of the OStruct with a hash (need to recursively convert)
|
85
|
+
marshal_load(__convert(conf).marshal_dump)
|
86
|
+
|
87
|
+
reload_env! if Config.use_env
|
88
|
+
validate! if RUBY_VERSION >= '2.1'
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
alias :load! :reload!
|
94
|
+
|
95
|
+
def reload_from_files(*files)
|
96
|
+
Config.load_and_set_settings(files)
|
97
|
+
reload!
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_hash
|
101
|
+
result = {}
|
102
|
+
marshal_dump.each do |k, v|
|
103
|
+
if v.instance_of? Config::Options
|
104
|
+
result[k] = v.to_hash
|
105
|
+
elsif v.instance_of? Array
|
106
|
+
result[k] = descend_array(v)
|
107
|
+
else
|
108
|
+
result[k] = v
|
109
|
+
end
|
110
|
+
end
|
111
|
+
result
|
112
|
+
end
|
113
|
+
|
114
|
+
def each(*args, &block)
|
115
|
+
marshal_dump.each(*args, &block)
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_json(*args)
|
119
|
+
require "json" unless defined?(JSON)
|
120
|
+
to_hash.to_json(*args)
|
121
|
+
end
|
122
|
+
|
123
|
+
def merge!(hash)
|
124
|
+
current = to_hash
|
125
|
+
DeepMerge.deep_merge!(hash.dup,
|
126
|
+
current,
|
127
|
+
preserve_unmergeables: false,
|
128
|
+
overwrite_arrays: Config.overwrite_arrays)
|
129
|
+
marshal_load(__convert(current).marshal_dump)
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Some keywords that don't play nicely with OpenStruct
|
134
|
+
SETTINGS_RESERVED_NAMES = %w{select collect test}
|
135
|
+
|
136
|
+
# An alternative mechanism for property access.
|
137
|
+
# This let's you do foo['bar'] along with foo.bar.
|
138
|
+
def [](param)
|
139
|
+
return super if SETTINGS_RESERVED_NAMES.include?(param)
|
140
|
+
send("#{param}")
|
141
|
+
end
|
142
|
+
|
143
|
+
def []=(param, value)
|
144
|
+
send("#{param}=", value)
|
145
|
+
end
|
146
|
+
|
147
|
+
SETTINGS_RESERVED_NAMES.each do |name|
|
148
|
+
define_method name do
|
149
|
+
self[name]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
protected
|
154
|
+
|
155
|
+
def descend_array(array)
|
156
|
+
array.map do |value|
|
157
|
+
if value.instance_of? Config::Options
|
158
|
+
value.to_hash
|
159
|
+
elsif value.instance_of? Array
|
160
|
+
descend_array(value)
|
161
|
+
else
|
162
|
+
value
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Recursively converts Hashes to Options (including Hashes inside Arrays)
|
168
|
+
def __convert(h) #:nodoc:
|
169
|
+
s = self.class.new
|
170
|
+
|
171
|
+
h.each do |k, v|
|
172
|
+
k = k.to_s if !k.respond_to?(:to_sym) && k.respond_to?(:to_s)
|
173
|
+
s.new_ostruct_member(k)
|
174
|
+
|
175
|
+
if v.is_a?(Hash)
|
176
|
+
v = v["type"] == "hash" ? v["contents"] : __convert(v)
|
177
|
+
elsif v.is_a?(Array)
|
178
|
+
v = v.collect { |e| e.instance_of?(Hash) ? __convert(e) : e }
|
179
|
+
end
|
180
|
+
|
181
|
+
s.send("#{k}=".to_sym, v)
|
182
|
+
end
|
183
|
+
s
|
184
|
+
end
|
185
|
+
|
186
|
+
# Try to convert string to a correct type
|
187
|
+
def __value(v)
|
188
|
+
Integer(v) rescue Float(v) rescue v
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|