config_reader 3.0.3 → 3.1.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +157 -37
- data/lib/config_reader/config_hash.rb +14 -6
- data/lib/config_reader/version.rb +1 -1
- data/lib/config_reader.rb +64 -17
- data/spec/config_reader_spec.rb +195 -8
- data/spec/spec_helper.rb +1 -0
- metadata +3 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8ed2bf310952ce4e846d92391a28d703d4d33beccb4801e98525909a38c4e16
|
|
4
|
+
data.tar.gz: 8ec8728939e738129bc359458165ff904d754a97b5efe6331a08a02d341c4cdc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67a2478345db255b9ae177058bd347f7b3d665995d77f797c25907de185c60af866824bf6001d68e6ec8dbab90badff0f8e3c0ae72db7a81d0810581ad666104
|
|
7
|
+
data.tar.gz: b730fa2dac028ab694207c13b1fde1019b205258e572c59e2c2c58cd3487a817c6d9636f0e33980f2ef8b7eedbeb320663c3ea3c7ba94e47dca34bed029286f1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.1.0 2026-04-18
|
|
4
|
+
|
|
5
|
+
Add `parse_path` and `dig_path` for dotted user input.
|
|
6
|
+
Fix config loading edge cases for false values, arrays, and missing config state.
|
|
7
|
+
Update the README and load Sekrets lazily unless it is configured.
|
|
8
|
+
|
|
9
|
+
## 3.0.4 2024-06-10
|
|
10
|
+
|
|
11
|
+
Make reload actually reload, useful in development.
|
|
12
|
+
|
|
3
13
|
## 3.0.3 2025-01-06
|
|
4
14
|
|
|
5
15
|
Fix a bug in deep merge where values weren't getting set properly.
|
data/README.md
CHANGED
|
@@ -1,59 +1,179 @@
|
|
|
1
1
|
# ConfigReader
|
|
2
2
|
|
|
3
|
-
[](https://codeclimate.com/github/UnderpantsGnome/config_reader-gem)
|
|
4
3
|

|
|
5
4
|

|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
`ConfigReader` loads environment-specific settings from YAML, merges each
|
|
7
|
+
environment with `defaults`, and exposes the result through method access,
|
|
8
|
+
hash access, and `dig`.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
## Installation
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
site_url: http://localhost:3000
|
|
15
|
-
host_name: example.com
|
|
16
|
-
mail_from: noreply@example.com
|
|
17
|
-
site_name: example
|
|
18
|
-
admin_email: admin@example.com
|
|
12
|
+
Add the gem to your application's Gemfile:
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
```ruby
|
|
15
|
+
gem "config_reader"
|
|
16
|
+
```
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
If you use encrypted config files with `sekrets_file`, add `sekrets` too:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem "sekrets", "~> 1.14"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Config Format
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
`defaults` is required. `config.environment` must match one of the top-level
|
|
27
|
+
environment keys in the file.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
```yaml
|
|
30
|
+
defaults:
|
|
31
|
+
site_url: http://localhost:3000
|
|
32
|
+
host_name: example.com
|
|
33
|
+
mail_from: noreply@example.com
|
|
34
|
+
features:
|
|
35
|
+
search: true
|
|
36
|
+
|
|
37
|
+
production:
|
|
38
|
+
site_url: http://example.com
|
|
39
|
+
|
|
40
|
+
test:
|
|
41
|
+
features:
|
|
42
|
+
search: false
|
|
43
|
+
```
|
|
29
44
|
|
|
30
45
|
## Setup
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
```ruby
|
|
48
|
+
class MyConfig < ConfigReader
|
|
49
|
+
configure do |config|
|
|
50
|
+
config.environment = Rails.env
|
|
51
|
+
config.config_file = "config/my_config.yml"
|
|
52
|
+
config.sekrets_file = "config/my_config.yml.enc" # optional
|
|
53
|
+
config.ignore_missing_keys = false # default
|
|
54
|
+
config.permitted_classes = [] # optional
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`config_file` may be an exact path. If that path does not exist, ConfigReader
|
|
60
|
+
also checks the current directory and `config/`.
|
|
40
61
|
|
|
41
62
|
## Usage
|
|
42
63
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
Top-level and nested values are available through methods, `[]`, and `dig`:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
MyConfig.mail_from
|
|
68
|
+
MyConfig[:mail_from]
|
|
69
|
+
MyConfig["mail_from"]
|
|
70
|
+
|
|
71
|
+
MyConfig.features.search
|
|
72
|
+
MyConfig[:features][:search]
|
|
73
|
+
MyConfig.dig(:features, :search)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Arrays work with `dig` too:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
MyConfig.dig(:servers, 0, :host)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If you want to read a dotted path from user input, use `dig_path`:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
#!/usr/bin/env ruby
|
|
46
86
|
|
|
47
|
-
|
|
87
|
+
require "bundler/setup"
|
|
88
|
+
require_relative "../app/lib/config"
|
|
89
|
+
|
|
90
|
+
print Config.dig_path(ARGV.fetch(0))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`parse_path` is also public if you need the normalized path segments:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
MyConfig.parse_path("servers.0.host")
|
|
97
|
+
# => [:servers, 0, :host]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
String paths treat numeric segments as array indexes. If you need a literal key
|
|
101
|
+
that contains `.` or looks numeric, pass an array instead:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
MyConfig.dig_path([:numeric_keys, "0"])
|
|
105
|
+
MyConfig.dig_path(["smtp.example.com"])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can inspect the resolved config for all environments and reload it at
|
|
109
|
+
runtime:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
MyConfig.envs["production"]
|
|
113
|
+
MyConfig.reload
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
By default, missing keys raise `KeyError`. To return `nil` instead:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class LenientConfig < ConfigReader
|
|
120
|
+
configure do |config|
|
|
121
|
+
config.environment = Rails.env
|
|
122
|
+
config.config_file = "config/my_config.yml"
|
|
123
|
+
config.ignore_missing_keys = true
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Sekrets
|
|
48
129
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
130
|
+
ConfigReader supports Sekrets integration, but only loads the `sekrets` gem
|
|
131
|
+
when `sekrets_file` is configured.
|
|
132
|
+
|
|
133
|
+
The sekrets file uses the same structure as the main config file. Sekrets
|
|
134
|
+
values are merged after the normal defaults plus environment merge, so matching
|
|
135
|
+
sekrets values override plain YAML values.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
class SecureConfig < ConfigReader
|
|
139
|
+
configure do |config|
|
|
140
|
+
config.environment = Rails.env
|
|
141
|
+
config.config_file = "config/my_config.yml"
|
|
142
|
+
config.sekrets_file = "config/my_config.yml.enc"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See <https://github.com/ahoward/sekrets> for more information.
|
|
148
|
+
|
|
149
|
+
## Advanced YAML
|
|
150
|
+
|
|
151
|
+
ERB is evaluated before the YAML is parsed:
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
defaults:
|
|
155
|
+
cache_url: <%= ENV.fetch("CACHE_URL", "redis://localhost:6379/0") %>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
YAML is loaded with `Psych.safe_load`. `Symbol` is always permitted, and you
|
|
159
|
+
can allow additional classes through `permitted_classes`:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
class TypedConfig < ConfigReader
|
|
163
|
+
configure do |config|
|
|
164
|
+
config.environment = Rails.env
|
|
165
|
+
config.config_file = "config/my_config.yml"
|
|
166
|
+
config.permitted_classes = [Date, Time]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
- Fork the project.
|
|
174
|
+
- Make your change.
|
|
175
|
+
- Add or update tests.
|
|
176
|
+
- Open a pull request.
|
|
57
177
|
|
|
58
178
|
## Copyright
|
|
59
179
|
|
|
@@ -7,18 +7,26 @@ class ConfigReader
|
|
|
7
7
|
config_hash.ignore_missing_keys = ignore_missing_keys
|
|
8
8
|
|
|
9
9
|
hash.each_pair do |key, value|
|
|
10
|
-
config_hash[key.to_sym] =
|
|
11
|
-
convert_hash(value, ignore_missing_keys)
|
|
12
|
-
else
|
|
13
|
-
value
|
|
14
|
-
end
|
|
10
|
+
config_hash[key.to_sym] = convert_value(value, ignore_missing_keys)
|
|
15
11
|
end
|
|
16
12
|
|
|
17
13
|
config_hash
|
|
18
14
|
end
|
|
19
15
|
|
|
16
|
+
def self.convert_value(value, ignore_missing_keys = false)
|
|
17
|
+
if value.is_a?(Hash)
|
|
18
|
+
convert_hash(value, ignore_missing_keys)
|
|
19
|
+
elsif value.is_a?(Array)
|
|
20
|
+
value.map { |item| convert_value(item, ignore_missing_keys) }
|
|
21
|
+
else
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def [](key)
|
|
21
|
-
|
|
27
|
+
key = key.to_sym if key.respond_to?(:to_sym)
|
|
28
|
+
|
|
29
|
+
fetch(key)
|
|
22
30
|
rescue KeyError => e
|
|
23
31
|
raise e unless ignore_missing_keys
|
|
24
32
|
end
|
data/lib/config_reader.rb
CHANGED
|
@@ -2,11 +2,6 @@ require "config_reader/version"
|
|
|
2
2
|
require "config_reader/config_hash"
|
|
3
3
|
require "psych"
|
|
4
4
|
|
|
5
|
-
begin
|
|
6
|
-
require "sekrets"
|
|
7
|
-
rescue LoadError
|
|
8
|
-
end
|
|
9
|
-
|
|
10
5
|
begin
|
|
11
6
|
require "erb"
|
|
12
7
|
rescue LoadError
|
|
@@ -36,7 +31,7 @@ class ConfigReader
|
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
def deep_merge(hash, other_hash)
|
|
39
|
-
hash.merge(other_hash) do |
|
|
34
|
+
hash.merge(other_hash) do |_key, this_val, other_val|
|
|
40
35
|
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
|
41
36
|
deep_merge(this_val, other_val)
|
|
42
37
|
else
|
|
@@ -45,13 +40,36 @@ class ConfigReader
|
|
|
45
40
|
end
|
|
46
41
|
end
|
|
47
42
|
|
|
43
|
+
def dig_path(path, separator: ".")
|
|
44
|
+
dig(*parse_path(path, separator: separator))
|
|
45
|
+
end
|
|
46
|
+
|
|
48
47
|
def dig(*args)
|
|
49
|
-
|
|
48
|
+
if args.respond_to?(:map!)
|
|
49
|
+
args.map! do |arg|
|
|
50
|
+
(arg.respond_to?(:to_sym) && !arg.is_a?(Integer)) ? arg.to_sym : arg
|
|
51
|
+
end
|
|
52
|
+
end
|
|
50
53
|
|
|
51
54
|
config.dig(*args)
|
|
52
55
|
end
|
|
53
56
|
|
|
57
|
+
def parse_path(path, separator: ".")
|
|
58
|
+
case path
|
|
59
|
+
when String
|
|
60
|
+
raise ArgumentError, "Path must not be blank" if path.strip.empty?
|
|
61
|
+
|
|
62
|
+
path.split(separator).map { |segment| normalize_string_path_segment(segment) }
|
|
63
|
+
when Array
|
|
64
|
+
path.map { |segment| normalize_array_path_segment(segment) }
|
|
65
|
+
else
|
|
66
|
+
raise ArgumentError, "Path must be a String or Array"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
54
70
|
def find_config
|
|
71
|
+
return nil unless configuration.config_file
|
|
72
|
+
|
|
55
73
|
return configuration.config_file if File.exist?(configuration.config_file)
|
|
56
74
|
|
|
57
75
|
%w[. config].each do |dir|
|
|
@@ -78,16 +96,25 @@ class ConfigReader
|
|
|
78
96
|
|
|
79
97
|
def load_sekrets
|
|
80
98
|
if configuration.sekrets_file
|
|
81
|
-
if
|
|
82
|
-
raise ArgumentError,
|
|
83
|
-
"You specified a sekrets_file, but the sekrets gem isn't available."
|
|
84
|
-
else
|
|
99
|
+
if sekrets_available?
|
|
85
100
|
::Sekrets.settings_for(configuration.sekrets_file) ||
|
|
86
101
|
raise("No sekrets found")
|
|
102
|
+
else
|
|
103
|
+
raise ArgumentError,
|
|
104
|
+
"You specified a sekrets_file, but the sekrets gem isn't available."
|
|
87
105
|
end
|
|
88
106
|
end
|
|
89
107
|
end
|
|
90
108
|
|
|
109
|
+
def sekrets_available?
|
|
110
|
+
return true if defined?(::Sekrets)
|
|
111
|
+
|
|
112
|
+
require "sekrets"
|
|
113
|
+
true
|
|
114
|
+
rescue LoadError
|
|
115
|
+
false
|
|
116
|
+
end
|
|
117
|
+
|
|
91
118
|
def load_yaml
|
|
92
119
|
permitted_classes = configuration.permitted_classes.to_a + [Symbol]
|
|
93
120
|
|
|
@@ -99,7 +126,7 @@ class ConfigReader
|
|
|
99
126
|
)
|
|
100
127
|
else
|
|
101
128
|
Psych.safe_load_file(
|
|
102
|
-
|
|
129
|
+
find_config,
|
|
103
130
|
aliases: true,
|
|
104
131
|
permitted_classes: permitted_classes
|
|
105
132
|
)
|
|
@@ -122,6 +149,7 @@ class ConfigReader
|
|
|
122
149
|
|
|
123
150
|
def merge_configs(conf, sekrets)
|
|
124
151
|
defaults = conf["defaults"]
|
|
152
|
+
raise "No defaults config found" unless defaults
|
|
125
153
|
|
|
126
154
|
if sekrets&.[]("defaults")
|
|
127
155
|
defaults = deep_merge(defaults, sekrets["defaults"])
|
|
@@ -129,7 +157,8 @@ class ConfigReader
|
|
|
129
157
|
|
|
130
158
|
merge_all_configs(conf, defaults, sekrets)
|
|
131
159
|
|
|
132
|
-
@envs[configuration.environment]
|
|
160
|
+
@envs[configuration.environment] ||
|
|
161
|
+
raise("No config found for environment \"#{configuration.environment}\"")
|
|
133
162
|
end
|
|
134
163
|
|
|
135
164
|
def method_missing(key, *_args, &_block)
|
|
@@ -137,15 +166,33 @@ class ConfigReader
|
|
|
137
166
|
raise ArgumentError.new("ConfigReader is immutable")
|
|
138
167
|
end
|
|
139
168
|
|
|
140
|
-
config[key]
|
|
169
|
+
config[key]
|
|
141
170
|
end
|
|
142
171
|
|
|
143
172
|
def reload
|
|
144
|
-
merge_configs(load_config, load_sekrets)
|
|
173
|
+
@config = merge_configs(load_config, load_sekrets)
|
|
145
174
|
end
|
|
146
175
|
|
|
147
|
-
def respond_to_missing?(m,
|
|
148
|
-
config.key?(m)
|
|
176
|
+
def respond_to_missing?(m, include_private = false)
|
|
177
|
+
super || config.key?(m)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def normalize_array_path_segment(segment)
|
|
181
|
+
if segment.is_a?(String) || segment.is_a?(Symbol)
|
|
182
|
+
segment.to_sym
|
|
183
|
+
elsif segment.is_a?(Integer)
|
|
184
|
+
segment
|
|
185
|
+
else
|
|
186
|
+
raise ArgumentError, "Path segments must be Strings, Symbols, or Integers"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def normalize_string_path_segment(segment)
|
|
191
|
+
if segment.is_a?(String) && segment.match?(/\A\d+\z/)
|
|
192
|
+
segment.to_i
|
|
193
|
+
else
|
|
194
|
+
normalize_array_path_segment(segment)
|
|
195
|
+
end
|
|
149
196
|
end
|
|
150
197
|
end
|
|
151
198
|
|
data/spec/config_reader_spec.rb
CHANGED
|
@@ -53,6 +53,70 @@ describe "ConfigReader" do
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
describe ".parse_path" do
|
|
57
|
+
it "parses dotted paths into dig segments" do
|
|
58
|
+
expect(TestConfig.parse_path("nested_key.value")).to eq(%i[nested_key value])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "parses array indexes from dotted paths" do
|
|
62
|
+
expect(TestConfig.parse_path("items.0.name")).to eq([:items, 0, :name])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "normalizes array input" do
|
|
66
|
+
expect(TestConfig.parse_path(["nested_key", :value, 0])).to eq(
|
|
67
|
+
[:nested_key, :value, 0]
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "rejects blank strings" do
|
|
72
|
+
expect { TestConfig.parse_path("") }.to raise_error(
|
|
73
|
+
ArgumentError,
|
|
74
|
+
"Path must not be blank"
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "rejects unsupported types" do
|
|
79
|
+
expect { TestConfig.parse_path(Object.new) }.to raise_error(
|
|
80
|
+
ArgumentError,
|
|
81
|
+
"Path must be a String or Array"
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe ".dig_path" do
|
|
87
|
+
it "digs through dotted paths" do
|
|
88
|
+
expect(TestConfig.dig_path("nested_key.value")).to eq("test")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "digs through arrays" do
|
|
92
|
+
config_class = Class.new(ConfigReader) do
|
|
93
|
+
class << self
|
|
94
|
+
def reload
|
|
95
|
+
ConfigReader::ConfigHash.convert_hash(
|
|
96
|
+
"items" => [{ "name" => "first" }]
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
expect(config_class.dig_path("items.0.name")).to eq("first")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "allows array input for numeric-looking keys" do
|
|
106
|
+
config_class = Class.new(ConfigReader) do
|
|
107
|
+
class << self
|
|
108
|
+
def reload
|
|
109
|
+
ConfigReader::ConfigHash.convert_hash(
|
|
110
|
+
"numeric_keys" => { "0" => "zero" }
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
expect(config_class.dig_path([:numeric_keys, "0"])).to eq("zero")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
56
120
|
describe "ignoring KeyNotFound" do
|
|
57
121
|
it "should not raise on missing key with [] accessor" do
|
|
58
122
|
expect { NoKeyNoErrorConfig[:no_key] }.to_not raise_error
|
|
@@ -145,21 +209,144 @@ describe "ConfigReader" do
|
|
|
145
209
|
expect(SekretsConfig["nested_key"]["value"]).to eq("test_sekret")
|
|
146
210
|
end
|
|
147
211
|
|
|
148
|
-
it "should find
|
|
149
|
-
|
|
212
|
+
it "should find values that only exist in sekrets" do
|
|
213
|
+
config_class = Class.new(ConfigReader) do
|
|
214
|
+
class << self
|
|
215
|
+
def load_config
|
|
216
|
+
{
|
|
217
|
+
"defaults" => { "app_name" => "default_app" },
|
|
218
|
+
"test" => { "app_name" => "test_app" }
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def load_sekrets
|
|
223
|
+
{
|
|
224
|
+
"test" => {
|
|
225
|
+
"sekrets_only" => { "value" => "test_sekret_only" }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
configure do |config|
|
|
232
|
+
config.environment = "test"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
expect(config_class.sekrets_only.value).to eq("test_sekret_only")
|
|
237
|
+
expect(config_class[:sekrets_only][:value]).to eq("test_sekret_only")
|
|
238
|
+
expect(config_class["sekrets_only"]["value"]).to eq("test_sekret_only")
|
|
150
239
|
end
|
|
151
240
|
|
|
152
|
-
it "
|
|
153
|
-
expect(SekretsConfig
|
|
241
|
+
it "shouldn't need to have all keys duplicated in the environment section" do
|
|
242
|
+
expect(SekretsConfig.nested_key.only_in_test_env).to be true
|
|
154
243
|
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
155
246
|
|
|
156
|
-
|
|
157
|
-
|
|
247
|
+
describe "regressions" do
|
|
248
|
+
it "does not load sekrets on a plain require" do
|
|
249
|
+
ruby_code = <<~RUBY
|
|
250
|
+
require "config_reader"
|
|
251
|
+
abort("sekrets loaded") if defined?(::Sekrets)
|
|
252
|
+
RUBY
|
|
253
|
+
|
|
254
|
+
stdout, stderr, status = Open3.capture3(
|
|
255
|
+
"bundle",
|
|
256
|
+
"exec",
|
|
257
|
+
"ruby",
|
|
258
|
+
"-Ilib",
|
|
259
|
+
"-e",
|
|
260
|
+
ruby_code,
|
|
261
|
+
chdir: File.expand_path("..", __dir__)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(status.success?).to be(true), stderr
|
|
265
|
+
expect(stdout).to eq("")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "returns false for existing false values via method access" do
|
|
269
|
+
config_class = Class.new(ConfigReader) do
|
|
270
|
+
class << self
|
|
271
|
+
def reload
|
|
272
|
+
ConfigReader::ConfigHash.convert_hash("feature_enabled" => false)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
158
275
|
end
|
|
159
276
|
|
|
160
|
-
|
|
161
|
-
|
|
277
|
+
expect(config_class.feature_enabled).to be(false)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "raises a clear error when config_file is not set" do
|
|
281
|
+
config_class = Class.new(ConfigReader) do
|
|
282
|
+
configure do |config|
|
|
283
|
+
config.environment = "test"
|
|
284
|
+
end
|
|
162
285
|
end
|
|
286
|
+
|
|
287
|
+
expect { config_class.load_config }.to raise_error("No config file set")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "raises a clear error when the configured environment is missing" do
|
|
291
|
+
config_class = Class.new(ConfigReader) do
|
|
292
|
+
configure do |config|
|
|
293
|
+
config.environment = "missing"
|
|
294
|
+
config.config_file = "spec/test_config.yml"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
expect { config_class.reload }.to raise_error(
|
|
299
|
+
'No config found for environment "missing"'
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it "loads YAML without ERB support" do
|
|
304
|
+
config_class = Class.new(ConfigReader) do
|
|
305
|
+
configure do |config|
|
|
306
|
+
config.environment = "test"
|
|
307
|
+
config.config_file = "spec/test_config.yml"
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
hide_const("ERB")
|
|
312
|
+
|
|
313
|
+
expect(config_class.load_yaml.dig("test", "app_name")).to eq("test_app")
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "supports arrays when digging through config values" do
|
|
317
|
+
config_class = Class.new(ConfigReader) do
|
|
318
|
+
class << self
|
|
319
|
+
def reload
|
|
320
|
+
ConfigReader::ConfigHash.convert_hash(
|
|
321
|
+
"items" => [{ "name" => "first", "enabled" => false }]
|
|
322
|
+
)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
expect(config_class[:items][0][:name]).to eq("first")
|
|
328
|
+
expect(config_class.dig(:items, 0, :name)).to eq("first")
|
|
329
|
+
expect(config_class.dig("items", 0, "enabled")).to be(false)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
it "raises a clear error when defaults are missing" do
|
|
333
|
+
config_class = Class.new(ConfigReader) do
|
|
334
|
+
class << self
|
|
335
|
+
def load_config
|
|
336
|
+
{ "test" => { "app_name" => "test_app" } }
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def load_sekrets
|
|
340
|
+
nil
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
configure do |config|
|
|
345
|
+
config.environment = "test"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
expect { config_class.reload }.to raise_error("No defaults config found")
|
|
163
350
|
end
|
|
164
351
|
end
|
|
165
352
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: config_reader
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Moen
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: abbrev
|
|
@@ -144,8 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
144
143
|
- !ruby/object:Gem::Version
|
|
145
144
|
version: '0'
|
|
146
145
|
requirements: []
|
|
147
|
-
rubygems_version: 3.
|
|
148
|
-
signing_key:
|
|
146
|
+
rubygems_version: 3.6.9
|
|
149
147
|
specification_version: 4
|
|
150
148
|
summary: Provides a way to manage environment specific configuration settings.
|
|
151
149
|
test_files: []
|