confset 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/confset.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "confset/options"
|
4
|
+
require "confset/configuration"
|
5
|
+
require "confset/version"
|
6
|
+
require "confset/sources/yaml_source"
|
7
|
+
require "confset/sources/hash_source"
|
8
|
+
require "confset/sources/env_source"
|
9
|
+
require "confset/validation/schema"
|
10
|
+
require "deep_merge"
|
11
|
+
|
12
|
+
module Confset
|
13
|
+
extend Confset::Validation::Schema
|
14
|
+
extend Confset::Configuration.new(
|
15
|
+
# general options
|
16
|
+
const_name: "Settings",
|
17
|
+
use_env: false,
|
18
|
+
env_prefix: "Settings",
|
19
|
+
env_separator: ".",
|
20
|
+
env_converter: :downcase,
|
21
|
+
env_parse_values: true,
|
22
|
+
fail_on_missing: false,
|
23
|
+
# deep_merge options
|
24
|
+
knockout_prefix: nil,
|
25
|
+
merge_nil_values: true,
|
26
|
+
overwrite_arrays: true,
|
27
|
+
merge_hash_arrays: false,
|
28
|
+
validation_contract: nil,
|
29
|
+
evaluate_erb_in_yaml: true
|
30
|
+
)
|
31
|
+
|
32
|
+
def self.setup
|
33
|
+
yield self unless @_ran_once
|
34
|
+
@_ran_once = true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a populated Options instance from a settings file. If a second file is given, then the sections of that
|
38
|
+
# file will overwrite existing sections of the first file.
|
39
|
+
def self.load_files(*sources)
|
40
|
+
config = Options.new
|
41
|
+
|
42
|
+
# add settings sources
|
43
|
+
[sources].flatten.compact.each do |source|
|
44
|
+
config.add_source!(source)
|
45
|
+
end
|
46
|
+
|
47
|
+
config.add_source!(Sources::EnvSource.new(ENV)) if Confset.use_env
|
48
|
+
|
49
|
+
config.load!
|
50
|
+
config
|
51
|
+
end
|
52
|
+
|
53
|
+
# Loads and sets the settings constant!
|
54
|
+
def self.load_and_set_settings(*sources)
|
55
|
+
name = Confset.const_name
|
56
|
+
Object.send(:remove_const, name) if Object.const_defined?(name)
|
57
|
+
Object.const_set(name, Confset.load_files(sources))
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.setting_files(config_root, env)
|
61
|
+
[
|
62
|
+
File.join(config_root, "settings.yml").to_s,
|
63
|
+
File.join(config_root, "settings", "#{env}.yml").to_s,
|
64
|
+
File.join(config_root, "environments", "#{env}.yml").to_s,
|
65
|
+
*local_setting_files(config_root, env)
|
66
|
+
].freeze
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.local_setting_files(config_root, env)
|
70
|
+
[
|
71
|
+
(File.join(config_root, "settings.local.yml").to_s if env != "test"),
|
72
|
+
File.join(config_root, "settings", "#{env}.local.yml").to_s,
|
73
|
+
File.join(config_root, "environments", "#{env}.local.yml").to_s
|
74
|
+
].compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.reload!
|
78
|
+
until Confset.const_name do
|
79
|
+
const = Object.const_get(Confset.const_name)
|
80
|
+
const.reload!
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Rails integration
|
86
|
+
require("confset/integrations/rails/railtie") if defined?(::Rails)
|
87
|
+
|
88
|
+
# Sinatra integration
|
89
|
+
require("confset/integrations/sinatra") if defined?(::Sinatra)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Confset
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
6
|
+
desc "Generates a custom Rails Confset initializer file."
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
@_config_source_root ||= File.expand_path("../templates", __FILE__)
|
10
|
+
end
|
11
|
+
|
12
|
+
def copy_initializer
|
13
|
+
template "confset.rb", "config/initializers/confset.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def copy_settings
|
17
|
+
template "settings.yml", "config/settings.yml"
|
18
|
+
template "settings.local.yml", "config/settings.local.yml"
|
19
|
+
directory "settings", "config/settings"
|
20
|
+
end
|
21
|
+
|
22
|
+
def modify_gitignore
|
23
|
+
create_file ".gitignore" unless File.exist? ".gitignore"
|
24
|
+
|
25
|
+
append_to_file ".gitignore" do
|
26
|
+
"\n" +
|
27
|
+
"config/settings.local.yml\n" +
|
28
|
+
"config/settings/*.local.yml\n" +
|
29
|
+
"config/environments/*.local.yml\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Confset.setup do |config|
|
2
|
+
# Name of the constant exposing loaded settings
|
3
|
+
config.const_name = 'Settings'
|
4
|
+
|
5
|
+
# Ability to remove elements of the array set in earlier loaded settings file. For example value: '--'.
|
6
|
+
#
|
7
|
+
# config.knockout_prefix = nil
|
8
|
+
|
9
|
+
# Overwrite an existing value when merging a `nil` value.
|
10
|
+
# When set to `false`, the existing value is retained after merge.
|
11
|
+
#
|
12
|
+
# config.merge_nil_values = true
|
13
|
+
|
14
|
+
# Overwrite arrays found in previously loaded settings file. When set to `false`, arrays will be merged.
|
15
|
+
#
|
16
|
+
# config.overwrite_arrays = true
|
17
|
+
|
18
|
+
# Load environment variables from the `ENV` object and override any settings defined in files.
|
19
|
+
#
|
20
|
+
# config.use_env = false
|
21
|
+
|
22
|
+
# Define ENV variable prefix deciding which variables to load into config.
|
23
|
+
#
|
24
|
+
# Reading variables from ENV is case-sensitive. If you define lowercase value below, ensure your ENV variables are
|
25
|
+
# prefixed in the same way.
|
26
|
+
#
|
27
|
+
# When not set it defaults to `config.const_name`.
|
28
|
+
#
|
29
|
+
config.env_prefix = 'SETTINGS'
|
30
|
+
|
31
|
+
# What string to use as level separator for settings loaded from ENV variables. Default value of '.' works well
|
32
|
+
# with Heroku, but you might want to change it for example for '__' to easy override settings from command line, where
|
33
|
+
# using dots in variable names might not be allowed (eg. Bash).
|
34
|
+
#
|
35
|
+
# config.env_separator = '.'
|
36
|
+
|
37
|
+
# Ability to process variables names:
|
38
|
+
# * nil - no change
|
39
|
+
# * :downcase - convert to lower case
|
40
|
+
#
|
41
|
+
# config.env_converter = :downcase
|
42
|
+
|
43
|
+
# Parse numeric values as integers instead of strings.
|
44
|
+
#
|
45
|
+
# config.env_parse_values = true
|
46
|
+
|
47
|
+
# Validate presence and type of specific config values. Check https://github.com/dry-rb/dry-validation for details.
|
48
|
+
#
|
49
|
+
# config.schema do
|
50
|
+
# required(:name).filled
|
51
|
+
# required(:age).maybe(:int?)
|
52
|
+
# required(:email).filled(format?: EMAIL_REGEX)
|
53
|
+
# end
|
54
|
+
|
55
|
+
# Evaluate ERB in YAML config files at load time.
|
56
|
+
#
|
57
|
+
# config.evaluate_erb_in_yaml = true
|
58
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Confset::Options do
|
6
|
+
before :each do
|
7
|
+
Confset.reset
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when overriding settings via ENV variables is enabled" do
|
11
|
+
let(:config) do
|
12
|
+
Confset.load_files "spec/fixtures/settings.yml", "spec/fixtures/multilevel.yml"
|
13
|
+
end
|
14
|
+
|
15
|
+
after :all do
|
16
|
+
Confset.use_env = false
|
17
|
+
end
|
18
|
+
|
19
|
+
before :each do
|
20
|
+
ENV.clear
|
21
|
+
|
22
|
+
Confset.use_env = true
|
23
|
+
Confset.env_prefix = nil
|
24
|
+
Confset.env_separator = "."
|
25
|
+
Confset.env_converter = :downcase
|
26
|
+
Confset.env_parse_values = true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add new setting from ENV variable" do
|
30
|
+
ENV["Settings.new_var"] = "value"
|
31
|
+
|
32
|
+
expect(config.new_var).to eq("value")
|
33
|
+
end
|
34
|
+
|
35
|
+
context "should override existing setting with a value from ENV variable" do
|
36
|
+
it "for a basic values" do
|
37
|
+
ENV["Settings.size"] = "overwritten by env"
|
38
|
+
|
39
|
+
expect(config.size).to eq("overwritten by env")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "for multilevel sections" do
|
43
|
+
ENV["Settings.number_of_all_countries"] = "0"
|
44
|
+
ENV["Settings.world.countries.europe"] = "0"
|
45
|
+
|
46
|
+
expect(config.number_of_all_countries).to eq(0)
|
47
|
+
expect(config.world.countries.europe).to eq(0)
|
48
|
+
expect(config.world.countries.australia).to eq(1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "and parsing ENV variable values" do
|
53
|
+
context "is enabled" do
|
54
|
+
it 'should recognize "false" and expose as Boolean' do
|
55
|
+
ENV["Settings.new_var"] = "false"
|
56
|
+
|
57
|
+
expect(config.new_var).to eq(false)
|
58
|
+
expect(config.new_var.is_a? FalseClass).to eq(true)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should recognize "true" and expose as Boolean' do
|
62
|
+
ENV["Settings.new_var"] = "true"
|
63
|
+
|
64
|
+
expect(config.new_var).to eq(true)
|
65
|
+
expect(config.new_var.is_a? TrueClass).to eq(true)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should recognize numbers and expose them as integers" do
|
69
|
+
ENV["Settings.new_var"] = "123"
|
70
|
+
|
71
|
+
expect(config.new_var).to eq(123)
|
72
|
+
expect(config.new_var.is_a? Integer).to eq(true)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should recognize fixed point numbers and expose them as float" do
|
76
|
+
ENV["Settings.new_var"] = "1.9"
|
77
|
+
|
78
|
+
expect(config.new_var).to eq(1.9)
|
79
|
+
expect(config.new_var.is_a? Float).to eq(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should leave strings intact" do
|
83
|
+
ENV["Settings.new_var"] = "foobar"
|
84
|
+
|
85
|
+
expect(config.new_var).to eq("foobar")
|
86
|
+
expect(config.new_var.is_a? String).to eq(true)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "is disabled" do
|
91
|
+
it "should not convert numbers to integers" do
|
92
|
+
ENV["Settings.new_var"] = "123"
|
93
|
+
|
94
|
+
Confset.env_parse_values = false
|
95
|
+
|
96
|
+
expect(config.new_var).to eq("123")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "and custom ENV variables prefix is defined" do
|
102
|
+
before :each do
|
103
|
+
Confset.env_prefix = "MyConfig"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should load variables from the new prefix" do
|
107
|
+
ENV["MyConfig.new_var"] = "value"
|
108
|
+
|
109
|
+
expect(config.new_var).to eq("value")
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not load variables from the default prefix" do
|
113
|
+
ENV["Settings.new_var"] = "value"
|
114
|
+
|
115
|
+
expect(config.new_var).to eq(nil)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should skip ENV variable when partial prefix match" do
|
119
|
+
ENV["MyConfigs.new_var"] = "value"
|
120
|
+
|
121
|
+
expect(config.new_var).to eq(nil)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "and custom ENV variables separator is defined" do
|
126
|
+
before :each do
|
127
|
+
Confset.env_separator = "__"
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should load variables and correctly recognize the new separator" do
|
131
|
+
ENV["Settings__new_var"] = "value"
|
132
|
+
ENV["Settings__var.with.dot"] = "value"
|
133
|
+
ENV["Settings__world__countries__europe"] = "0"
|
134
|
+
|
135
|
+
expect(config.new_var).to eq("value")
|
136
|
+
expect(config["var.with.dot"]).to eq("value")
|
137
|
+
expect(config.world.countries.europe).to eq(0)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should ignore variables wit default separator" do
|
141
|
+
ENV["Settings.new_var"] = "value"
|
142
|
+
|
143
|
+
expect(config.new_var).to eq(nil)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "and custom ENV variables prefix includes custom ENV variables separator" do
|
148
|
+
before :each do
|
149
|
+
Confset.env_prefix = "MY_CONFIG"
|
150
|
+
Confset.env_separator = "_"
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should load environment variables which begin with the custom prefix" do
|
154
|
+
ENV["MY_CONFIG_KEY"] = "value"
|
155
|
+
|
156
|
+
expect(config.key).to eq("value")
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should not load environment variables which begin with the default prefix" do
|
160
|
+
ENV["Settings_key"] = "value"
|
161
|
+
|
162
|
+
expect(config.key).to eq(nil)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should not load environment variables which partially begin with the custom prefix" do
|
166
|
+
ENV["MY_CONFIGS_KEY"] = "value"
|
167
|
+
|
168
|
+
expect(config.key).to eq(nil)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should recognize the custom separator" do
|
172
|
+
ENV["MY_CONFIG_KEY.WITH.DOT"] = "value"
|
173
|
+
ENV["MY_CONFIG_WORLD_COUNTRIES_EUROPE"] = "0"
|
174
|
+
|
175
|
+
expect(config["key.with.dot"]).to eq("value")
|
176
|
+
expect(config.world.countries.europe).to eq(0)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should not recognize the default separator" do
|
180
|
+
ENV["MY_CONFIG.KEY"] = "value"
|
181
|
+
|
182
|
+
expect(config.key).to eq(nil)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "and variable names conversion" do
|
187
|
+
context "is enabled" do
|
188
|
+
it "should downcase variable names when :downcase conversion enabled" do
|
189
|
+
ENV["Settings.NEW_VAR"] = "value"
|
190
|
+
|
191
|
+
Confset.env_converter = :downcase
|
192
|
+
|
193
|
+
expect(config.new_var).to eq("value")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "is disabled" do
|
198
|
+
it "should not change variable names by default" do
|
199
|
+
ENV["Settings.NEW_VAR"] = "value"
|
200
|
+
|
201
|
+
Confset.env_converter = nil
|
202
|
+
|
203
|
+
expect(config.new_var).to eq(nil)
|
204
|
+
expect(config.NEW_VAR).to eq("value")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should always load ENV variables when reloading settings from files" do
|
210
|
+
ENV["Settings.new_var"] = "value"
|
211
|
+
|
212
|
+
expect(config.new_var).to eq("value")
|
213
|
+
|
214
|
+
Confset.reload!
|
215
|
+
|
216
|
+
expect(config.new_var).to eq("value")
|
217
|
+
end
|
218
|
+
|
219
|
+
context "and env variable names conflict with new namespaces" do
|
220
|
+
it "should throw a descriptive error message" do
|
221
|
+
ENV["Settings.backend_database"] = "development"
|
222
|
+
ENV["Settings.backend_database.user"] = "postgres"
|
223
|
+
|
224
|
+
expected_message = "Environment variable Settings.backend_database.user "\
|
225
|
+
"conflicts with variable Settings.backend_database"
|
226
|
+
expect { config }.to raise_error(RuntimeError, expected_message)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context "and env variable names conflict with existing namespaces" do
|
231
|
+
it "should allow overriding the namespace" do
|
232
|
+
ENV["Settings.databases"] = "new databases"
|
233
|
+
|
234
|
+
expect(config.databases).to eq("new databases")
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|