conker 0.11.0 → 0.12.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.
- 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:
|