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