confset 1.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 +7 -0
- data/Rakefile +44 -0
- data/docs/CHANGELOG.md +15 -0
- data/docs/LICENSE.md +29 -0
- data/lib/confset/configuration.rb +37 -0
- data/lib/confset/integrations/heroku.rb +61 -0
- data/lib/confset/integrations/rails/railtie.rb +40 -0
- data/lib/confset/integrations/sinatra.rb +27 -0
- data/lib/confset/options.rb +187 -0
- data/lib/confset/rack/reloader.rb +17 -0
- data/lib/confset/sources/env_source.rb +74 -0
- data/lib/confset/sources/hash_source.rb +18 -0
- data/lib/confset/sources/yaml_source.rb +34 -0
- data/lib/confset/tasks/heroku.rake +9 -0
- data/lib/confset/validation/error.rb +13 -0
- data/lib/confset/validation/schema.rb +22 -0
- data/lib/confset/validation/validate.rb +26 -0
- data/lib/confset/version.rb +5 -0
- data/lib/confset.rb +89 -0
- data/lib/generators/confset/install_generator.rb +34 -0
- data/lib/generators/confset/templates/confset.rb +58 -0
- data/lib/generators/confset/templates/settings/development.yml +0 -0
- data/lib/generators/confset/templates/settings/production.yml +0 -0
- data/lib/generators/confset/templates/settings/test.yml +0 -0
- data/lib/generators/confset/templates/settings.local.yml +0 -0
- data/lib/generators/confset/templates/settings.yml +0 -0
- data/spec/config_env_spec.rb +238 -0
- data/spec/config_spec.rb +468 -0
- data/spec/configuration_spec.rb +16 -0
- data/spec/fixtures/bool_override/config1.yml +2 -0
- data/spec/fixtures/bool_override/config2.yml +2 -0
- data/spec/fixtures/custom_types/hash.yml +7 -0
- data/spec/fixtures/deep_merge/config1.yml +31 -0
- data/spec/fixtures/deep_merge/config2.yml +28 -0
- data/spec/fixtures/deep_merge2/config1.yml +3 -0
- data/spec/fixtures/deep_merge2/config2.yml +2 -0
- data/spec/fixtures/deep_merge3/config1.yml +1 -0
- data/spec/fixtures/deep_merge3/config2.yml +1 -0
- data/spec/fixtures/development.yml +4 -0
- data/spec/fixtures/empty1.yml +0 -0
- data/spec/fixtures/empty2.yml +0 -0
- data/spec/fixtures/knockout_prefix/config1.yml +40 -0
- data/spec/fixtures/knockout_prefix/config2.yml +28 -0
- data/spec/fixtures/knockout_prefix/config3.yml +14 -0
- data/spec/fixtures/malformed.yml +6 -0
- data/spec/fixtures/multilevel.yml +10 -0
- data/spec/fixtures/overwrite_arrays/config1.yml +17 -0
- data/spec/fixtures/overwrite_arrays/config2.yml +12 -0
- data/spec/fixtures/overwrite_arrays/config3.yml +10 -0
- data/spec/fixtures/reserved_keywords.yml +7 -0
- data/spec/fixtures/settings.yml +22 -0
- data/spec/fixtures/settings2.yml +1 -0
- data/spec/fixtures/unsafe_load.yml +12 -0
- data/spec/fixtures/validation/confset.yml +3 -0
- data/spec/fixtures/with_erb.yml +4 -0
- data/spec/fixtures/with_malformed_erb.yml +1 -0
- data/spec/options_spec.rb +255 -0
- data/spec/sources/env_source_spec.rb +81 -0
- data/spec/sources/hash_source_spec.rb +49 -0
- data/spec/sources/yaml_source_spec.rb +145 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/fixture_helper.rb +14 -0
- data/spec/support/rails_helper.rb +24 -0
- data/spec/support/shared_contexts/rake.rb +8 -0
- data/spec/validation_spec.rb +84 -0
- metadata +295 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 78d6fd35c87f305efd074652b7382687bee16e33a696e1ae63ec605cc31d2ff6
|
4
|
+
data.tar.gz: c823bdae1fc505a4683a1a5ff2a798d1af65d4abd1f13f40d33594209df99434
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c0aff665228c8381d782965c7342b843a3bdf0936917b26085258c1fa86ae8d8069b367fefea9237b1e73c950bb64ba89ff5bcb73d0b7f2fdb3719c749148d9c
|
7
|
+
data.tar.gz: 7a8e6957095c1f3dc0f21488c8df1e2a1109db6041579def3ed335584a34a0a3da731baa4eba634fb37d41619fca4046ce429fb3b0e866678ae13589aff4df4c
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "bundler/setup"
|
6
|
+
rescue LoadError
|
7
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
8
|
+
end
|
9
|
+
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
begin
|
13
|
+
require "rake/testtask"
|
14
|
+
require "rubocop/rake_task"
|
15
|
+
|
16
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
17
|
+
t.options = ["--display-cop-names"]
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
# no rspec available
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require "rspec/core/rake_task"
|
25
|
+
|
26
|
+
RSpec::Core::RakeTask.new(:spec)
|
27
|
+
rescue LoadError
|
28
|
+
# no rspec available
|
29
|
+
end
|
30
|
+
|
31
|
+
# Documentation
|
32
|
+
require "rdoc/task"
|
33
|
+
|
34
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
35
|
+
rdoc.rdoc_dir = "rdoc"
|
36
|
+
rdoc.title = "Confset #{Confset::VERSION}"
|
37
|
+
rdoc.options << "--line-numbers"
|
38
|
+
rdoc.rdoc_files.include("README.*")
|
39
|
+
rdoc.rdoc_files.include("CHANGELOG.*")
|
40
|
+
rdoc.rdoc_files.include("LICENSE.*")
|
41
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
42
|
+
end
|
43
|
+
|
44
|
+
task default: %i[rubocop spec]
|
data/docs/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## v1.0.0-20220527 - [Danilo Carolino](@danilogco)
|
4
|
+
|
5
|
+
Initial release
|
6
|
+
|
7
|
+
Build
|
8
|
+
|
9
|
+
Refac the project focusing on the newer versions of the Ruby
|
10
|
+
language and Ruby on Rails.
|
11
|
+
|
12
|
+
---------------------
|
13
|
+
|
14
|
+
You can find the full changelog of the original "config" gem at the link
|
15
|
+
<https://github.com/rubyconfig/config/blob/master/CHANGELOG.md>.
|
data/docs/LICENSE.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 DCO Tecnologia
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all 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,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
23
|
+
Third-party materials and licenses:
|
24
|
+
|
25
|
+
* Confset contains Config (<https://github.com/rubyconfig/config>) which is governed by the MIT license
|
26
|
+
Copyright (C) 2010-2014 Jacques Crocker, Fred Wu, Piotr Kuczynski
|
27
|
+
|
28
|
+
* Confset contains Deep Merge (deep_merge.rb) which is governed by the MIT license
|
29
|
+
Copyright (C) 2008 Steve Midgley
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
# The main configuration backbone
|
5
|
+
class Configuration < Module
|
6
|
+
# Accepts configuration options,
|
7
|
+
# initializing a module that can be used to extend
|
8
|
+
# the necessary class with the provided config methods
|
9
|
+
def initialize(**attributes)
|
10
|
+
attributes.each do |name, default|
|
11
|
+
define_reader(name, default)
|
12
|
+
define_writer(name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def define_reader(name, default)
|
18
|
+
variable = :"@#{name}"
|
19
|
+
|
20
|
+
define_method(name) do
|
21
|
+
if instance_variable_defined?(variable)
|
22
|
+
instance_variable_get(variable)
|
23
|
+
else
|
24
|
+
default
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_writer(name)
|
30
|
+
variable = :"@#{name}"
|
31
|
+
|
32
|
+
define_method("#{name}=") do |value|
|
33
|
+
instance_variable_set(variable, value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
|
5
|
+
module Confset
|
6
|
+
module Integrations
|
7
|
+
class Heroku < Struct.new(:app)
|
8
|
+
def invoke
|
9
|
+
puts "Setting vars..."
|
10
|
+
heroku_command = "config:set #{vars}"
|
11
|
+
heroku(heroku_command)
|
12
|
+
puts "Vars set:"
|
13
|
+
puts heroku_command
|
14
|
+
end
|
15
|
+
|
16
|
+
def vars
|
17
|
+
# Load only local options to Heroku
|
18
|
+
Confset.load_and_set_settings(
|
19
|
+
Rails.root.join("config", "settings.local.yml").to_s,
|
20
|
+
Rails.root.join("config", "settings", "#{environment}.local.yml").to_s,
|
21
|
+
Rails.root.join("config", "environments", "#{environment}.local.yml").to_s
|
22
|
+
)
|
23
|
+
|
24
|
+
out = ""
|
25
|
+
dotted_hash = to_dotted_hash Kernel.const_get(Confset.const_name).to_hash, {}, Confset.const_name
|
26
|
+
dotted_hash.each { |key, value| out += " #{key}=#{value} " }
|
27
|
+
out
|
28
|
+
end
|
29
|
+
|
30
|
+
def environment
|
31
|
+
heroku("run 'echo $RAILS_ENV'").chomp[/(\w+)\z/]
|
32
|
+
end
|
33
|
+
|
34
|
+
def heroku(command)
|
35
|
+
with_app = app ? " --app #{app}" : ""
|
36
|
+
`heroku #{command}#{with_app}`
|
37
|
+
end
|
38
|
+
|
39
|
+
def `(command)
|
40
|
+
Bundler.with_clean_env { super }
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_dotted_hash(source, target = {}, namespace = nil)
|
44
|
+
prefix = "#{namespace}." if namespace
|
45
|
+
case source
|
46
|
+
when Hash
|
47
|
+
source.each do |key, value|
|
48
|
+
to_dotted_hash(value, target, "#{prefix}#{key}")
|
49
|
+
end
|
50
|
+
when Array
|
51
|
+
source.each_with_index do |value, index|
|
52
|
+
to_dotted_hash(value, target, "#{prefix}#{index}")
|
53
|
+
end
|
54
|
+
else
|
55
|
+
target[namespace] = source
|
56
|
+
end
|
57
|
+
target
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Integrations
|
5
|
+
module Rails
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
def preload
|
8
|
+
# Manually load the custom initializer before everything else
|
9
|
+
initializer = ::Rails.root.join("config", "initializers", "confset.rb")
|
10
|
+
require initializer if File.exist?(initializer)
|
11
|
+
|
12
|
+
# Parse the settings before any of the initializers
|
13
|
+
Confset.load_and_set_settings(
|
14
|
+
Confset.setting_files(::Rails.root.join("config"), ::Rails.env)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Load rake tasks (eg. Heroku)
|
19
|
+
rake_tasks do
|
20
|
+
Dir[File.join(File.dirname(__FILE__), "../tasks/*.rake")].each { |f| load f }
|
21
|
+
end
|
22
|
+
|
23
|
+
config.before_configuration { preload }
|
24
|
+
|
25
|
+
# Development environment should reload settings on every request
|
26
|
+
if ::Rails.env.development?
|
27
|
+
initializer :config_reload_on_development do
|
28
|
+
ActiveSupport.on_load :action_controller_base do
|
29
|
+
if ::Rails::VERSION::MAJOR >= 4
|
30
|
+
prepend_before_action { ::Confset.reload! }
|
31
|
+
else
|
32
|
+
prepend_before_filter { ::Confset.reload! }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "confset/rack/reloader"
|
4
|
+
|
5
|
+
module Confset
|
6
|
+
# provide helper to register within your Sinatra app
|
7
|
+
#
|
8
|
+
# set :root, File.dirname(__FILE__)
|
9
|
+
# register Confset
|
10
|
+
#
|
11
|
+
def self.registered(app)
|
12
|
+
app.configure do |inner_app|
|
13
|
+
env = inner_app.environment || ENV["RACK_ENV"]
|
14
|
+
root = inner_app.root
|
15
|
+
|
16
|
+
# use Padrino settings if applicable
|
17
|
+
if defined?(Padrino)
|
18
|
+
env = Padrino.env if Padrino.respond_to?(:env)
|
19
|
+
root = Padrino.root if Padrino.respond_to?(:root)
|
20
|
+
end
|
21
|
+
|
22
|
+
Confset.load_and_set_settings(Confset.setting_files(File.join(root, "config"), env))
|
23
|
+
|
24
|
+
inner_app.use(::Confset::Rack::Reloader) if inner_app.development?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require "pathname"
|
5
|
+
require "confset/validation/validate"
|
6
|
+
|
7
|
+
module Confset
|
8
|
+
class Options < OpenStruct
|
9
|
+
include Enumerable
|
10
|
+
include Validation::Validate
|
11
|
+
|
12
|
+
def keys
|
13
|
+
marshal_dump.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
marshal_dump.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_source!(source)
|
21
|
+
# handle yaml file paths
|
22
|
+
source = (Sources::YAMLSource.new(source)) if source.is_a?(String) || source.is_a?(::Pathname)
|
23
|
+
source = (Sources::HashSource.new(source)) if source.is_a?(Hash)
|
24
|
+
|
25
|
+
@config_sources ||= []
|
26
|
+
@config_sources << source
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepend_source!(source)
|
30
|
+
source = (Sources::YAMLSource.new(source)) if source.is_a?(String) || source.is_a?(::Pathname)
|
31
|
+
source = (Sources::HashSource.new(source)) if source.is_a?(Hash)
|
32
|
+
|
33
|
+
@config_sources ||= []
|
34
|
+
@config_sources.unshift(source)
|
35
|
+
end
|
36
|
+
|
37
|
+
# look through all our sources and rebuild the configuration
|
38
|
+
def reload!
|
39
|
+
conf = {}
|
40
|
+
@config_sources.each do |source|
|
41
|
+
source_conf = source.load
|
42
|
+
|
43
|
+
if conf.empty?
|
44
|
+
conf = source_conf
|
45
|
+
else
|
46
|
+
DeepMerge.deep_merge!(
|
47
|
+
source_conf,
|
48
|
+
conf,
|
49
|
+
preserve_unmergeables: false,
|
50
|
+
knockout_prefix: Confset.knockout_prefix,
|
51
|
+
overwrite_arrays: Confset.overwrite_arrays,
|
52
|
+
merge_nil_values: Confset.merge_nil_values,
|
53
|
+
merge_hash_arrays: Confset.merge_hash_arrays
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# swap out the contents of the OStruct with a hash (need to recursively convert)
|
59
|
+
marshal_load(__convert(conf).marshal_dump)
|
60
|
+
|
61
|
+
validate!
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
alias :load! :reload!
|
67
|
+
|
68
|
+
def reload_from_files(*files)
|
69
|
+
Confset.load_and_set_settings(files)
|
70
|
+
reload!
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_hash
|
74
|
+
result = {}
|
75
|
+
marshal_dump.each do |k, v|
|
76
|
+
if v.instance_of? Confset::Options
|
77
|
+
result[k] = v.to_hash
|
78
|
+
elsif v.instance_of? Array
|
79
|
+
result[k] = descend_array(v)
|
80
|
+
else
|
81
|
+
result[k] = v
|
82
|
+
end
|
83
|
+
end
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
alias :to_h :to_hash
|
88
|
+
|
89
|
+
def each(*args, &block)
|
90
|
+
marshal_dump.each(*args, &block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_json(*args)
|
94
|
+
require "json" unless defined?(JSON)
|
95
|
+
to_hash.to_json(*args)
|
96
|
+
end
|
97
|
+
|
98
|
+
def as_json(options = nil)
|
99
|
+
to_hash.as_json(options)
|
100
|
+
end
|
101
|
+
|
102
|
+
def merge!(hash)
|
103
|
+
current = to_hash
|
104
|
+
DeepMerge.deep_merge!(
|
105
|
+
hash.dup,
|
106
|
+
current,
|
107
|
+
preserve_unmergeables: false,
|
108
|
+
knockout_prefix: Confset.knockout_prefix,
|
109
|
+
overwrite_arrays: Confset.overwrite_arrays,
|
110
|
+
merge_nil_values: Confset.merge_nil_values,
|
111
|
+
merge_hash_arrays: Confset.merge_hash_arrays
|
112
|
+
)
|
113
|
+
marshal_load(__convert(current).marshal_dump)
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Some keywords that don't play nicely with OpenStruct
|
118
|
+
SETTINGS_RESERVED_NAMES = %w[select collect test count zip min max exit!].freeze
|
119
|
+
|
120
|
+
# An alternative mechanism for property access.
|
121
|
+
# This let's you do foo['bar'] along with foo.bar.
|
122
|
+
def [](param)
|
123
|
+
return super if SETTINGS_RESERVED_NAMES.include?(param)
|
124
|
+
send("#{param}")
|
125
|
+
end
|
126
|
+
|
127
|
+
def []=(param, value)
|
128
|
+
send("#{param}=", value)
|
129
|
+
end
|
130
|
+
|
131
|
+
SETTINGS_RESERVED_NAMES.each do |name|
|
132
|
+
define_method name do
|
133
|
+
self[name]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def key?(key)
|
138
|
+
table.key?(key)
|
139
|
+
end
|
140
|
+
|
141
|
+
def has_key?(key)
|
142
|
+
table.has_key?(key)
|
143
|
+
end
|
144
|
+
|
145
|
+
def method_missing(method_name, *args)
|
146
|
+
if Confset.fail_on_missing && method_name !~ /.*(?==\z)/m
|
147
|
+
raise KeyError, "key not found: #{method_name.inspect}" unless key?(method_name)
|
148
|
+
end
|
149
|
+
super
|
150
|
+
end
|
151
|
+
|
152
|
+
def respond_to_missing?(*args)
|
153
|
+
super
|
154
|
+
end
|
155
|
+
|
156
|
+
protected
|
157
|
+
def descend_array(array)
|
158
|
+
array.map do |value|
|
159
|
+
if value.instance_of? Confset::Options
|
160
|
+
value.to_hash
|
161
|
+
elsif value.instance_of? Array
|
162
|
+
descend_array(value)
|
163
|
+
else
|
164
|
+
value
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Recursively converts Hashes to Options (including Hashes inside Arrays)
|
170
|
+
def __convert(h) # :nodoc:
|
171
|
+
s = self.class.new
|
172
|
+
|
173
|
+
h.each do |k, v|
|
174
|
+
k = k.to_s if !k.respond_to?(:to_sym) && k.respond_to?(:to_s)
|
175
|
+
|
176
|
+
if v.is_a?(Hash)
|
177
|
+
v = v["type"] == "hash" ? v["contents"] : __convert(v)
|
178
|
+
elsif v.is_a?(Array)
|
179
|
+
v = v.collect { |e| e.instance_of?(Hash) ? __convert(e) : e }
|
180
|
+
end
|
181
|
+
|
182
|
+
s[k] = v
|
183
|
+
end
|
184
|
+
s
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Rack
|
5
|
+
# Rack middleware the reloads Confset on every request (only use in dev mode)
|
6
|
+
class Reloader
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
Confset.reload!
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Sources
|
5
|
+
# Allows settings to be loaded from a "flat" hash with string keys, like ENV.
|
6
|
+
class EnvSource
|
7
|
+
attr_reader :prefix
|
8
|
+
attr_reader :separator
|
9
|
+
attr_reader :converter
|
10
|
+
attr_reader :parse_values
|
11
|
+
|
12
|
+
def initialize(env,
|
13
|
+
prefix: Confset.env_prefix || Confset.const_name,
|
14
|
+
separator: Confset.env_separator,
|
15
|
+
converter: Confset.env_converter,
|
16
|
+
parse_values: Confset.env_parse_values)
|
17
|
+
@env = env
|
18
|
+
@prefix = prefix.to_s.split(separator)
|
19
|
+
@separator = separator
|
20
|
+
@converter = converter
|
21
|
+
@parse_values = parse_values
|
22
|
+
end
|
23
|
+
|
24
|
+
def load
|
25
|
+
return {} if @env.nil? || @env.empty?
|
26
|
+
|
27
|
+
hash = Hash.new
|
28
|
+
|
29
|
+
@env.each do |variable, value|
|
30
|
+
keys = variable.to_s.split(separator)
|
31
|
+
|
32
|
+
next if keys.shift(prefix.size) != prefix
|
33
|
+
|
34
|
+
keys.map! { |key|
|
35
|
+
case converter
|
36
|
+
when :downcase then
|
37
|
+
key.downcase
|
38
|
+
when nil then
|
39
|
+
key
|
40
|
+
else
|
41
|
+
raise "Invalid ENV variables name converter: #{converter}"
|
42
|
+
end
|
43
|
+
}
|
44
|
+
|
45
|
+
leaf = keys[0...-1].inject(hash) { |h, key|
|
46
|
+
h[key] ||= {}
|
47
|
+
}
|
48
|
+
|
49
|
+
unless leaf.is_a?(Hash)
|
50
|
+
conflicting_key = (prefix + keys[0...-1]).join(separator)
|
51
|
+
raise "Environment variable #{variable} conflicts with variable #{conflicting_key}"
|
52
|
+
end
|
53
|
+
|
54
|
+
leaf[keys.last] = parse_values ? __value(value) : value
|
55
|
+
end
|
56
|
+
|
57
|
+
hash
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
# Try to convert string to a correct type
|
62
|
+
def __value(v)
|
63
|
+
case v
|
64
|
+
when "false"
|
65
|
+
false
|
66
|
+
when "true"
|
67
|
+
true
|
68
|
+
else
|
69
|
+
Integer(v) rescue Float(v) rescue v
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Sources
|
5
|
+
class HashSource
|
6
|
+
attr_accessor :hash
|
7
|
+
|
8
|
+
def initialize(hash)
|
9
|
+
@hash = hash
|
10
|
+
end
|
11
|
+
|
12
|
+
# returns hash that was passed in to initialize
|
13
|
+
def load
|
14
|
+
hash.is_a?(Hash) ? hash : {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "erb"
|
5
|
+
|
6
|
+
module Confset
|
7
|
+
module Sources
|
8
|
+
class YAMLSource
|
9
|
+
attr_accessor :path
|
10
|
+
attr_reader :evaluate_erb
|
11
|
+
|
12
|
+
def initialize(path, evaluate_erb: Confset.evaluate_erb_in_yaml)
|
13
|
+
@path = path.to_s
|
14
|
+
@evaluate_erb = !!evaluate_erb
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns a config hash from the YML file
|
18
|
+
def load
|
19
|
+
if @path && File.exist?(@path)
|
20
|
+
file_contents = IO.read(@path)
|
21
|
+
file_contents = ERB.new(file_contents).result if evaluate_erb
|
22
|
+
result = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(file_contents) : YAML.load(file_contents)
|
23
|
+
end
|
24
|
+
|
25
|
+
result || {}
|
26
|
+
|
27
|
+
rescue Psych::SyntaxError => e
|
28
|
+
raise "YAML syntax error occurred while parsing #{@path}. " \
|
29
|
+
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
|
30
|
+
"Error: #{e.message}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Validation
|
5
|
+
class Error < StandardError
|
6
|
+
def self.format(v_res)
|
7
|
+
v_res.errors.group_by(&:path).map do |path, messages|
|
8
|
+
"#{' ' * 2}#{path.join('.')}: #{messages.map(&:text).join('; ')}"
|
9
|
+
end.join("\n")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Validation
|
5
|
+
module Schema
|
6
|
+
# Assigns schema configuration option
|
7
|
+
def schema=(value)
|
8
|
+
@schema = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def schema(&block)
|
12
|
+
if block_given?
|
13
|
+
# Delay require until optional schema validation is requested
|
14
|
+
require "dry-validation"
|
15
|
+
@schema = Dry::Schema.define(&block)
|
16
|
+
else
|
17
|
+
@schema
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "confset/validation/error"
|
4
|
+
|
5
|
+
module Confset
|
6
|
+
module Validation
|
7
|
+
module Validate
|
8
|
+
def validate!
|
9
|
+
validate_using!(Confset.validation_contract)
|
10
|
+
validate_using!(Confset.schema)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def validate_using!(validator)
|
15
|
+
if validator
|
16
|
+
result = validator.call(to_hash)
|
17
|
+
|
18
|
+
return if result.success?
|
19
|
+
|
20
|
+
error = Confset::Validation::Error.format(result)
|
21
|
+
raise Confset::Validation::Error, "Confset validation failed:\n\n#{error}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|