confset 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +44 -0
  3. data/docs/CHANGELOG.md +15 -0
  4. data/docs/LICENSE.md +29 -0
  5. data/lib/confset/configuration.rb +37 -0
  6. data/lib/confset/integrations/heroku.rb +61 -0
  7. data/lib/confset/integrations/rails/railtie.rb +40 -0
  8. data/lib/confset/integrations/sinatra.rb +27 -0
  9. data/lib/confset/options.rb +187 -0
  10. data/lib/confset/rack/reloader.rb +17 -0
  11. data/lib/confset/sources/env_source.rb +74 -0
  12. data/lib/confset/sources/hash_source.rb +18 -0
  13. data/lib/confset/sources/yaml_source.rb +34 -0
  14. data/lib/confset/tasks/heroku.rake +9 -0
  15. data/lib/confset/validation/error.rb +13 -0
  16. data/lib/confset/validation/schema.rb +22 -0
  17. data/lib/confset/validation/validate.rb +26 -0
  18. data/lib/confset/version.rb +5 -0
  19. data/lib/confset.rb +89 -0
  20. data/lib/generators/confset/install_generator.rb +34 -0
  21. data/lib/generators/confset/templates/confset.rb +58 -0
  22. data/lib/generators/confset/templates/settings/development.yml +0 -0
  23. data/lib/generators/confset/templates/settings/production.yml +0 -0
  24. data/lib/generators/confset/templates/settings/test.yml +0 -0
  25. data/lib/generators/confset/templates/settings.local.yml +0 -0
  26. data/lib/generators/confset/templates/settings.yml +0 -0
  27. data/spec/config_env_spec.rb +238 -0
  28. data/spec/config_spec.rb +468 -0
  29. data/spec/configuration_spec.rb +16 -0
  30. data/spec/fixtures/bool_override/config1.yml +2 -0
  31. data/spec/fixtures/bool_override/config2.yml +2 -0
  32. data/spec/fixtures/custom_types/hash.yml +7 -0
  33. data/spec/fixtures/deep_merge/config1.yml +31 -0
  34. data/spec/fixtures/deep_merge/config2.yml +28 -0
  35. data/spec/fixtures/deep_merge2/config1.yml +3 -0
  36. data/spec/fixtures/deep_merge2/config2.yml +2 -0
  37. data/spec/fixtures/deep_merge3/config1.yml +1 -0
  38. data/spec/fixtures/deep_merge3/config2.yml +1 -0
  39. data/spec/fixtures/development.yml +4 -0
  40. data/spec/fixtures/empty1.yml +0 -0
  41. data/spec/fixtures/empty2.yml +0 -0
  42. data/spec/fixtures/knockout_prefix/config1.yml +40 -0
  43. data/spec/fixtures/knockout_prefix/config2.yml +28 -0
  44. data/spec/fixtures/knockout_prefix/config3.yml +14 -0
  45. data/spec/fixtures/malformed.yml +6 -0
  46. data/spec/fixtures/multilevel.yml +10 -0
  47. data/spec/fixtures/overwrite_arrays/config1.yml +17 -0
  48. data/spec/fixtures/overwrite_arrays/config2.yml +12 -0
  49. data/spec/fixtures/overwrite_arrays/config3.yml +10 -0
  50. data/spec/fixtures/reserved_keywords.yml +7 -0
  51. data/spec/fixtures/settings.yml +22 -0
  52. data/spec/fixtures/settings2.yml +1 -0
  53. data/spec/fixtures/unsafe_load.yml +12 -0
  54. data/spec/fixtures/validation/confset.yml +3 -0
  55. data/spec/fixtures/with_erb.yml +4 -0
  56. data/spec/fixtures/with_malformed_erb.yml +1 -0
  57. data/spec/options_spec.rb +255 -0
  58. data/spec/sources/env_source_spec.rb +81 -0
  59. data/spec/sources/hash_source_spec.rb +49 -0
  60. data/spec/sources/yaml_source_spec.rb +145 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/spec/support/fixture_helper.rb +14 -0
  63. data/spec/support/rails_helper.rb +24 -0
  64. data/spec/support/shared_contexts/rake.rb +8 -0
  65. data/spec/validation_spec.rb +84 -0
  66. 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
@@ -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