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