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 CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- conker (0.11.0)
4
+ conker (0.12.0)
5
5
  activesupport
6
6
  addressable
7
7
 
data/TODO.txt ADDED
@@ -0,0 +1,4 @@
1
+ Things to do.
2
+
3
+ * Add a "required" declaration (required in all environments)
4
+ * Consider rejecting unexpected keys (i.e. typo-checking) for the hash case
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.11.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 use:
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, hash)
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) && !ENV[varname]
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 ENV[varname] && @environment != 'test'
139
- interpret_value(ENV[varname], @declaration_opts[:type])
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,2 @@
1
+ # An actually empty file isn't valid YAML, so this file is just very unhelpful.
2
+ dummy_value_that_nobody_could_possibly_care_about: 42
@@ -0,0 +1 @@
1
+ PORT: 3000
@@ -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
- # N.B. this doesn't catch if we *changed* any env vars (rather than adding
11
- # new ones).
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 'basic usage' do
22
- def setup!(env = :development)
23
- Conker.module_eval do
24
- setup_config! env,
25
- A_SECRET: api_credential(development: nil),
26
- PORT: required_in_production(type: :integer, default: 42)
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
- it 'exposes declared variables as top-level constants' do
31
- setup!
32
- ::A_SECRET.should be_nil
33
- ::PORT.should == 42
34
- end
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
- it 'does not turn random environment variables into constants' do
37
- ENV['PATH'].should_not be_empty
38
- setup!
39
- expect { ::PATH }.to raise_error(NameError, /PATH/)
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
- it 'lets environment variables override environmental defaults' do
43
- ENV['A_SECRET'] = 'beefbeefbeefbeef'
44
- setup!
45
- ::A_SECRET.should == 'beef' * 4
46
- end
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
- it 'throws useful errors if required variables are missing' do
49
- ENV['A_SECRET'].should be_nil
50
- ENV['PORT'] = '42'
51
- expect { setup! :production }.to raise_error(/A_SECRET/)
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
- describe 'required variables' do
57
- def setup!
58
- env = @env # capture it for block scope
59
- Conker.module_eval do
60
- setup_config! env,
61
- APPNAME: optional(default: 'conker'),
62
- PORT: required_in_production(type: :integer, default: 3000)
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
- describe 'in development' do
67
- before { @env = :development }
68
+ describe 'in development' do
69
+ before { @env = :development }
68
70
 
69
- it 'allows optional variables to be missing' do
70
- ENV['APPNAME'].should be_nil
71
- ENV['PORT'] = '80'
72
- expect { setup! }.not_to raise_error
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
- it 'allows required_in_production variables to be missing' do
76
- ENV['APPNAME'] = 'widget'
77
- ENV['PORT'].should be_nil
78
- expect { setup! }.not_to raise_error
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
- it 'allows optional variables to be missing' do
86
- ENV['APPNAME'].should be_nil
87
- ENV['PORT'] = '80'
88
- expect { setup! }.not_to raise_error
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 'throws a useful error if required_in_production variables are missing' do
92
- ENV['APPNAME'] = 'widget'
93
- ENV['PORT'].should be_nil
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
- describe 'defaults' do
101
- def setup!(env = :development)
102
- Conker.module_eval do
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
- it 'uses the default value if none is specified' do
114
- ENV['NUM_THREADS'].should be_nil
115
- setup!
116
- NUM_THREADS.should == 2
117
- end
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
- it 'allows overriding defaults for specific environments' do
120
- ENV['NUM_THREADS'].should be_nil
121
- setup! :test
122
- NUM_THREADS.should == 1
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
- describe 'typed variables' do
128
- describe 'boolean' do
129
- def setup_sprocket_enabled!(value_string)
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
- it 'parses "true"' do
137
- setup_sprocket_enabled! 'true'
138
- SPROCKET_ENABLED.should be_true
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
- it 'parses "false"' do
142
- setup_sprocket_enabled! 'false'
143
- SPROCKET_ENABLED.should be_false
144
- end
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
- it 'accepts "1" as true' do
147
- setup_sprocket_enabled! '1'
148
- SPROCKET_ENABLED.should be_true
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
- it 'accepts "0" as false' do
152
- setup_sprocket_enabled! '0'
153
- SPROCKET_ENABLED.should be_false
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
- describe 'integer' do
158
- def setup_num_threads!(value_string)
159
- ENV['NUM_THREADS'] = value_string
160
- Conker.module_eval do
161
- setup_config! :development, :NUM_THREADS => optional(type: :integer, default: 2)
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
- it 'parses "42"' do
166
- setup_num_threads! '42'
167
- NUM_THREADS.should == 42
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
- it 'throws an error if the value is not an integer' do
171
- expect { setup_num_threads! 'one hundred' }.to raise_error(/one hundred/)
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 'float' do
176
- def setup_log_probability!(value_string)
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! :development, :LOG_PROBABILITY => optional(type: :float, default: 1.0)
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 'parses "0.5"' do
184
- setup_log_probability! '0.5'
185
- LOG_PROBABILITY.should == 0.5
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 'throws an error if the value is not a float' do
189
- expect { setup_log_probability! 'zero' }.to raise_error(/zero/)
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
- describe 'url' do
194
- def setup_api_url!(value_string)
195
- ENV['API_URL'] = value_string
196
- Conker.module_eval do
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 'exposes a URI object, not a string' do
202
- setup_api_url! 'http://localhost:4321/'
203
- API_URL.host.should == 'localhost'
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
- it 'parses the default value too' do
207
- setup_api_url! nil
208
- API_URL.host.should == 'example.com'
209
- end
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 'addressable' do
213
- def setup_api_url!(value_string)
214
- ENV['API_URL'] = value_string
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! :development, :API_URL => optional(type: :addressable, default: 'http://example.com/foo')
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 an Addressable::URI object, not a string' do
221
- setup_api_url! 'http://localhost:4321/'
222
- API_URL.host.should == 'localhost'
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 'parses the default value too' do
226
- setup_api_url! nil
227
- API_URL.host.should == 'example.com'
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
- describe 'timestamp' do
232
- xit 'seems to have bit rotted'
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.11.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: