conker 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/TODO.txt +4 -0
- data/conker.gemspec +1 -1
- data/lib/conker.rb +29 -7
- data/open-source-TODO.txt +9 -0
- data/spec/fixtures/empty.yml +2 -0
- data/spec/fixtures/port_3000.yml +1 -0
- data/spec/lib/conker_spec.rb +236 -150
- metadata +5 -1
data/Gemfile.lock
CHANGED
data/TODO.txt
ADDED
data/conker.gemspec
CHANGED
@@ -2,7 +2,7 @@ Gem::Specification.new do |s|
|
|
2
2
|
s.name = 'conker'
|
3
3
|
s.authors = ['Sam Stokes', 'Conrad Irwin', 'Lee Mallabone', 'Martin Kleppmann']
|
4
4
|
s.email = 'supportive@rapportive.com'
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.12.0'
|
6
6
|
s.summary = %q{Conker will conquer your config.}
|
7
7
|
s.description = "Configuration library."
|
8
8
|
s.homepage = "https://github.com/rapportive/conker"
|
data/lib/conker.rb
CHANGED
@@ -1,12 +1,26 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
1
2
|
require 'active_support/core_ext/hash/indifferent_access'
|
2
3
|
require 'active_support/core_ext/hash/keys'
|
3
4
|
require 'active_support/core_ext/hash/reverse_merge'
|
4
5
|
require 'addressable/uri'
|
5
6
|
|
6
|
-
# Example
|
7
|
+
# Example that uses the process's environment:
|
7
8
|
# module Conker
|
8
9
|
# setup_config!(Rails.env, :A_SECRET => api_credential)
|
9
10
|
# end
|
11
|
+
#
|
12
|
+
# Example that uses a supplied hash of values (e.g. read from some file or
|
13
|
+
# database):
|
14
|
+
# config_values = {:A_SECRET => 'very_secret'}
|
15
|
+
# module Conker
|
16
|
+
# setup_config!(Rails.env, config_values, :A_SECRET => api_credential)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# For convenience, if your config file is YAML, you can supply the path
|
20
|
+
# directly and Conker will load and parse the file:
|
21
|
+
# module Conker
|
22
|
+
# setup_config!(Rails.env, 'config_values.yml', :A_SECRET => api_credential)
|
23
|
+
# end
|
10
24
|
module Conker
|
11
25
|
ENVIRONMENTS = %w(production development test)
|
12
26
|
DUMMY_API_KEY = 'dummy_api_key'.freeze
|
@@ -26,11 +40,18 @@ module Conker
|
|
26
40
|
|
27
41
|
class << self
|
28
42
|
# Parse a multi-key hash into globals and raise an informative error message on failure.
|
29
|
-
def setup_config!(current_env,
|
43
|
+
def setup_config!(current_env, *args)
|
44
|
+
hash = args.extract_options!
|
45
|
+
config = case args[0]
|
46
|
+
when Hash; args[0]
|
47
|
+
when String; require 'yaml'; YAML.parse_file(args[0]).to_ruby
|
48
|
+
else; ENV
|
49
|
+
end
|
50
|
+
|
30
51
|
errors = []
|
31
52
|
hash.each do |varname, declaration|
|
32
53
|
begin
|
33
|
-
Kernel.const_set(varname, declaration.evaluate(current_env, varname.to_s))
|
54
|
+
Kernel.const_set(varname, declaration.evaluate(current_env, config, varname.to_s))
|
34
55
|
rescue => error
|
35
56
|
errors << [varname, error.message]
|
36
57
|
end
|
@@ -111,8 +132,9 @@ module Conker
|
|
111
132
|
@declaration_opts = declaration_opts.with_indifferent_access
|
112
133
|
end
|
113
134
|
|
114
|
-
def evaluate(current_environment, varname)
|
135
|
+
def evaluate(current_environment, config, varname)
|
115
136
|
@environment = current_environment
|
137
|
+
@config = config
|
116
138
|
check_missing_value! varname
|
117
139
|
check_missing_default!
|
118
140
|
from_config_variable_or_default(varname)
|
@@ -120,7 +142,7 @@ module Conker
|
|
120
142
|
|
121
143
|
private
|
122
144
|
def check_missing_value!(varname)
|
123
|
-
if required_in_environments.member?(@environment.to_sym) &&
|
145
|
+
if required_in_environments.member?(@environment.to_sym) && !@config[varname]
|
124
146
|
raise MustBeDefined
|
125
147
|
end
|
126
148
|
end
|
@@ -135,8 +157,8 @@ module Conker
|
|
135
157
|
end
|
136
158
|
|
137
159
|
def from_config_variable_or_default(varname)
|
138
|
-
if
|
139
|
-
interpret_value(
|
160
|
+
if @config[varname] && @environment != 'test'
|
161
|
+
interpret_value(@config[varname], @declaration_opts[:type])
|
140
162
|
else
|
141
163
|
default_value
|
142
164
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Things to do before we open source this.
|
2
|
+
|
3
|
+
* Stop using active_support/core_ext (forces them on users)
|
4
|
+
* Remove the not-very-general use-case-specific declarations (redis_url,
|
5
|
+
api_credential), e.g. move them back into rapportive_ruby
|
6
|
+
* Improve summary and description in gemspec
|
7
|
+
* Improve rubydocs
|
8
|
+
* blog post
|
9
|
+
* README
|
@@ -0,0 +1 @@
|
|
1
|
+
PORT: 3000
|
data/spec/lib/conker_spec.rb
CHANGED
@@ -1,235 +1,321 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
|
1
3
|
require 'conker'
|
2
4
|
|
3
5
|
describe Conker do
|
4
6
|
before :each do
|
5
|
-
@env_vars = ENV.keys
|
6
7
|
@constants = Kernel.constants
|
7
8
|
end
|
8
9
|
|
9
10
|
after :each do
|
10
|
-
#
|
11
|
-
#
|
12
|
-
(ENV.keys - @env_vars).each {|k| ENV.delete k }
|
13
|
-
# Same caveat doesn't apply here, because Ruby will whinge if we redefine a
|
14
|
-
# constant.
|
11
|
+
# Don't need to worry about changing (rather than adding) constants,
|
12
|
+
# because Ruby will whinge if we do that.
|
15
13
|
(Kernel.constants - @constants).each do |k|
|
16
14
|
Kernel.send :remove_const, k
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
20
18
|
|
21
|
-
describe '
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
describe 'reading config from a hash' do
|
20
|
+
describe 'basic usage' do
|
21
|
+
def setup!(env = :development, config = {})
|
22
|
+
Conker.module_eval do
|
23
|
+
setup_config! env, config.with_indifferent_access,
|
24
|
+
A_SECRET: api_credential(development: nil),
|
25
|
+
PORT: required_in_production(type: :integer, default: 42)
|
26
|
+
end
|
27
27
|
end
|
28
|
-
end
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
it 'exposes declared variables as top-level constants' do
|
30
|
+
setup!
|
31
|
+
::A_SECRET.should be_nil
|
32
|
+
::PORT.should == 42
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
35
|
+
it 'lets values in the hash override defaults' do
|
36
|
+
setup! :development, PORT: 3000
|
37
|
+
::PORT.should == 3000
|
38
|
+
end
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
40
|
+
it 'ignores environment variables' do
|
41
|
+
ENV['A_SECRET'] = 'beefbeefbeefbeef'
|
42
|
+
begin setup! ensure ENV.delete('A_SECRET') end
|
43
|
+
::A_SECRET.should be_nil
|
44
|
+
end
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
it 'does not turn random environment variables into constants' do
|
47
|
+
ENV['PATH'].should_not be_empty
|
48
|
+
setup!
|
49
|
+
expect { ::PATH }.to raise_error(NameError, /PATH/)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'throws useful errors if required variables are missing' do
|
53
|
+
expect { setup! :production, PORT: 42 }.to raise_error(/A_SECRET/)
|
54
|
+
end
|
52
55
|
end
|
53
|
-
end
|
54
56
|
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
describe 'required variables' do
|
59
|
+
def setup!(config = {})
|
60
|
+
env = @env # capture it for block scope
|
61
|
+
Conker.module_eval do
|
62
|
+
setup_config! env, config.with_indifferent_access,
|
63
|
+
APPNAME: optional(default: 'conker'),
|
64
|
+
PORT: required_in_production(type: :integer, default: 3000)
|
65
|
+
end
|
63
66
|
end
|
64
|
-
end
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
+
describe 'in development' do
|
69
|
+
before { @env = :development }
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
it 'allows optional variables to be missing' do
|
72
|
+
expect { setup! PORT: 80 }.not_to raise_error
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'allows required_in_production variables to be missing' do
|
76
|
+
expect { setup! APPNAME: 'widget' }.not_to raise_error
|
77
|
+
end
|
73
78
|
end
|
74
79
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
80
|
+
describe 'in production' do
|
81
|
+
before { @env = :production }
|
82
|
+
|
83
|
+
it 'allows optional variables to be missing' do
|
84
|
+
expect { setup! PORT: 80 }.not_to raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'throws a useful error if required_in_production variables are missing' do
|
88
|
+
expect { setup! APPNAME: 'widget' }.to raise_error(/PORT/)
|
89
|
+
end
|
79
90
|
end
|
80
91
|
end
|
81
92
|
|
82
|
-
describe 'in production' do
|
83
|
-
before { @env = :production }
|
84
93
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
94
|
+
describe 'defaults' do
|
95
|
+
def setup!(env = :development, config = {})
|
96
|
+
Conker.module_eval do
|
97
|
+
setup_config! env, config.with_indifferent_access,
|
98
|
+
NUM_THREADS: optional(type: :integer, test: 1, default: 2)
|
99
|
+
end
|
89
100
|
end
|
90
101
|
|
91
|
-
it '
|
92
|
-
|
93
|
-
|
94
|
-
expect { setup! }.to raise_error(/PORT/)
|
102
|
+
it 'uses the specified value if one is given' do
|
103
|
+
setup! :development, NUM_THREADS: 4
|
104
|
+
NUM_THREADS.should == 4
|
95
105
|
end
|
96
|
-
end
|
97
|
-
end
|
98
106
|
|
107
|
+
it 'uses the default value if none is specified' do
|
108
|
+
setup! :development
|
109
|
+
NUM_THREADS.should == 2
|
110
|
+
end
|
99
111
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
setup_config! env, NUM_THREADS: optional(type: :integer, test: 1, default: 2)
|
112
|
+
it 'allows overriding defaults for specific environments' do
|
113
|
+
setup! :test
|
114
|
+
NUM_THREADS.should == 1
|
104
115
|
end
|
105
116
|
end
|
106
117
|
|
107
|
-
it 'uses the specified value if one is given' do
|
108
|
-
ENV['NUM_THREADS'] = '4'
|
109
|
-
setup!
|
110
|
-
NUM_THREADS.should == 4
|
111
|
-
end
|
112
118
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
119
|
+
describe 'typed variables' do
|
120
|
+
describe 'boolean' do
|
121
|
+
def setup_sprocket_enabled!(value_string)
|
122
|
+
Conker.module_eval do
|
123
|
+
setup_config! :development, {'SPROCKET_ENABLED' => value_string},
|
124
|
+
SPROCKET_ENABLED: optional(type: :boolean, default: false)
|
125
|
+
end
|
126
|
+
end
|
118
127
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
end
|
128
|
+
it 'parses "true"' do
|
129
|
+
setup_sprocket_enabled! 'true'
|
130
|
+
SPROCKET_ENABLED.should be_true
|
131
|
+
end
|
125
132
|
|
133
|
+
it 'parses "false"' do
|
134
|
+
setup_sprocket_enabled! 'false'
|
135
|
+
SPROCKET_ENABLED.should be_false
|
136
|
+
end
|
126
137
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
ENV['SPROCKET_ENABLED'] = value_string
|
131
|
-
Conker.module_eval do
|
132
|
-
setup_config! :development, :SPROCKET_ENABLED => optional(type: :boolean, default: false)
|
138
|
+
it 'accepts "1" as true' do
|
139
|
+
setup_sprocket_enabled! '1'
|
140
|
+
SPROCKET_ENABLED.should be_true
|
133
141
|
end
|
134
|
-
end
|
135
142
|
|
136
|
-
|
137
|
-
|
138
|
-
|
143
|
+
it 'accepts "0" as false' do
|
144
|
+
setup_sprocket_enabled! '0'
|
145
|
+
SPROCKET_ENABLED.should be_false
|
146
|
+
end
|
139
147
|
end
|
140
148
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
149
|
+
describe 'integer' do
|
150
|
+
def setup_num_threads!(value_string)
|
151
|
+
Conker.module_eval do
|
152
|
+
setup_config! :development, {'NUM_THREADS' => value_string},
|
153
|
+
NUM_THREADS: optional(type: :integer, default: 2)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'parses "42"' do
|
158
|
+
setup_num_threads! '42'
|
159
|
+
NUM_THREADS.should == 42
|
160
|
+
end
|
145
161
|
|
146
|
-
|
147
|
-
|
148
|
-
|
162
|
+
it 'throws an error if the value is not an integer' do
|
163
|
+
expect { setup_num_threads! 'one hundred' }.to raise_error(/one hundred/)
|
164
|
+
end
|
149
165
|
end
|
150
166
|
|
151
|
-
|
152
|
-
|
153
|
-
|
167
|
+
describe 'float' do
|
168
|
+
def setup_log_probability!(value_string)
|
169
|
+
Conker.module_eval do
|
170
|
+
setup_config! :development, {'LOG_PROBABILITY' => value_string},
|
171
|
+
LOG_PROBABILITY: optional(type: :float, default: 1.0)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'parses "0.5"' do
|
176
|
+
setup_log_probability! '0.5'
|
177
|
+
LOG_PROBABILITY.should == 0.5
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'throws an error if the value is not a float' do
|
181
|
+
expect { setup_log_probability! 'zero' }.to raise_error(/zero/)
|
182
|
+
end
|
154
183
|
end
|
155
|
-
end
|
156
184
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
185
|
+
describe 'url' do
|
186
|
+
def setup_api_url!(value_string)
|
187
|
+
Conker.module_eval do
|
188
|
+
setup_config! :development, {'API_URL' => value_string},
|
189
|
+
API_URL: optional(type: :url, default: 'http://example.com/foo')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'exposes a URI object, not a string' do
|
194
|
+
setup_api_url! 'http://localhost:4321/'
|
195
|
+
API_URL.host.should == 'localhost'
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'parses the default value too' do
|
199
|
+
setup_api_url! nil
|
200
|
+
API_URL.host.should == 'example.com'
|
162
201
|
end
|
163
202
|
end
|
164
203
|
|
165
|
-
|
166
|
-
|
167
|
-
|
204
|
+
describe 'addressable' do
|
205
|
+
def setup_api_url!(value_string)
|
206
|
+
Conker.module_eval do
|
207
|
+
setup_config! :development, {'API_URL' => value_string},
|
208
|
+
API_URL: optional(type: :addressable, default: 'http://example.com/foo')
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'exposes an Addressable::URI object, not a string' do
|
213
|
+
setup_api_url! 'http://localhost:4321/'
|
214
|
+
API_URL.host.should == 'localhost'
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'parses the default value too' do
|
218
|
+
setup_api_url! nil
|
219
|
+
API_URL.host.should == 'example.com'
|
220
|
+
end
|
168
221
|
end
|
169
222
|
|
170
|
-
|
171
|
-
|
223
|
+
describe 'timestamp' do
|
224
|
+
xit 'seems to have bit rotted'
|
172
225
|
end
|
173
226
|
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
describe 'reading config from environment variables' do
|
231
|
+
before :each do
|
232
|
+
@env_vars = ENV.keys
|
233
|
+
end
|
234
|
+
|
235
|
+
after :each do
|
236
|
+
# N.B. this doesn't catch if we *changed* any env vars (rather than adding
|
237
|
+
# new ones).
|
238
|
+
(ENV.keys - @env_vars).each {|k| ENV.delete k }
|
239
|
+
end
|
240
|
+
|
174
241
|
|
175
|
-
describe '
|
176
|
-
def
|
177
|
-
ENV['LOG_PROBABILITY'] = value_string
|
242
|
+
describe 'basic usage' do
|
243
|
+
def setup!(env = :development)
|
178
244
|
Conker.module_eval do
|
179
|
-
setup_config!
|
245
|
+
setup_config! env,
|
246
|
+
A_SECRET: api_credential(development: nil),
|
247
|
+
PORT: required_in_production(type: :integer, default: 42)
|
180
248
|
end
|
181
249
|
end
|
182
250
|
|
183
|
-
it '
|
184
|
-
|
185
|
-
|
251
|
+
it 'exposes declared variables as top-level constants' do
|
252
|
+
setup!
|
253
|
+
::A_SECRET.should be_nil
|
254
|
+
::PORT.should == 42
|
186
255
|
end
|
187
256
|
|
188
|
-
it '
|
189
|
-
|
257
|
+
it 'does not turn random environment variables into constants' do
|
258
|
+
ENV['PATH'].should_not be_empty
|
259
|
+
setup!
|
260
|
+
expect { ::PATH }.to raise_error(NameError, /PATH/)
|
190
261
|
end
|
191
|
-
end
|
192
262
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
setup_config! :development, :API_URL => optional(type: :url, default: 'http://example.com/foo')
|
198
|
-
end
|
263
|
+
it 'lets environment variables override environmental defaults' do
|
264
|
+
ENV['A_SECRET'] = 'beefbeefbeefbeef'
|
265
|
+
setup!
|
266
|
+
::A_SECRET.should == 'beef' * 4
|
199
267
|
end
|
200
268
|
|
201
|
-
it '
|
202
|
-
|
203
|
-
|
269
|
+
it 'throws useful errors if required variables are missing' do
|
270
|
+
ENV['A_SECRET'].should be_nil
|
271
|
+
ENV['PORT'] = '42'
|
272
|
+
expect { setup! :production }.to raise_error(/A_SECRET/)
|
204
273
|
end
|
274
|
+
end
|
275
|
+
end
|
205
276
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
277
|
+
|
278
|
+
describe 'reading config from a YAML file' do
|
279
|
+
def fixture(filename)
|
280
|
+
File.join(File.dirname(__FILE__), '..', 'fixtures', filename)
|
210
281
|
end
|
211
282
|
|
212
|
-
describe '
|
213
|
-
def
|
214
|
-
|
283
|
+
describe 'basic usage' do
|
284
|
+
def setup!(env = :development, filename = 'empty.yml')
|
285
|
+
fixture_path = fixture(filename)
|
215
286
|
Conker.module_eval do
|
216
|
-
setup_config!
|
287
|
+
setup_config! env, fixture_path,
|
288
|
+
A_SECRET: api_credential(development: nil),
|
289
|
+
PORT: required_in_production(type: :integer, default: 42)
|
217
290
|
end
|
218
291
|
end
|
219
292
|
|
220
|
-
it 'exposes
|
221
|
-
|
222
|
-
|
293
|
+
it 'exposes declared variables as top-level constants' do
|
294
|
+
setup!
|
295
|
+
::A_SECRET.should be_nil
|
296
|
+
::PORT.should == 42
|
223
297
|
end
|
224
298
|
|
225
|
-
it '
|
226
|
-
|
227
|
-
|
299
|
+
it 'lets values in the file override defaults' do
|
300
|
+
setup! :development, 'port_3000.yml'
|
301
|
+
::PORT.should == 3000
|
228
302
|
end
|
229
|
-
end
|
230
303
|
|
231
|
-
|
232
|
-
|
304
|
+
it 'ignores environment variables' do
|
305
|
+
ENV['A_SECRET'] = 'beefbeefbeefbeef'
|
306
|
+
begin setup! ensure ENV.delete('A_SECRET') end
|
307
|
+
::A_SECRET.should be_nil
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'does not turn random environment variables into constants' do
|
311
|
+
ENV['PATH'].should_not be_empty
|
312
|
+
setup!
|
313
|
+
expect { ::PATH }.to raise_error(NameError, /PATH/)
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'throws useful errors if required variables are missing' do
|
317
|
+
expect { setup! :production, 'port_3000.yml' }.to raise_error(/A_SECRET/)
|
318
|
+
end
|
233
319
|
end
|
234
320
|
end
|
235
321
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -71,8 +71,12 @@ files:
|
|
71
71
|
- Gemfile
|
72
72
|
- Gemfile.lock
|
73
73
|
- LICENSE.MIT
|
74
|
+
- TODO.txt
|
74
75
|
- conker.gemspec
|
75
76
|
- lib/conker.rb
|
77
|
+
- open-source-TODO.txt
|
78
|
+
- spec/fixtures/empty.yml
|
79
|
+
- spec/fixtures/port_3000.yml
|
76
80
|
- spec/lib/conker_spec.rb
|
77
81
|
homepage: https://github.com/rapportive/conker
|
78
82
|
licenses:
|