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
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
|