chalk-config 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.yardopts +3 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +175 -0
- data/Rakefile +13 -0
- data/chalk-config.gemspec +24 -0
- data/demo_schema.rb +8 -0
- data/lib/chalk-config.rb +317 -0
- data/lib/chalk-config/errors.rb +8 -0
- data/lib/chalk-config/version.rb +6 -0
- data/tddium.yml +11 -0
- data/test/_lib.rb +19 -0
- data/test/functional/_lib.rb +10 -0
- data/test/functional/general.rb +102 -0
- data/test/functional/general/config.yaml +7 -0
- data/test/functional/general/missing.yaml +2 -0
- data/test/functional/general/raw.yaml +3 -0
- data/test/integration/_lib.rb +10 -0
- data/test/meta/_lib.rb +10 -0
- data/test/unit/_lib.rb +10 -0
- data/test/unit/chalk-config.rb +62 -0
- metadata +165 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Stripe
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# Chalk::Config
|
2
|
+
|
3
|
+
Maps on-disk config files into a loaded global
|
4
|
+
[configatron](https://github.com/markbates/configatron) instance,
|
5
|
+
taking into account your current environment.
|
6
|
+
|
7
|
+
`configatron` is used within many Chalk gems to control their
|
8
|
+
behavior, and is also great for configuration within your application.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
### Environment
|
13
|
+
|
14
|
+
`Chalk::Config` relies on describing your environment as an opaque
|
15
|
+
string (`production`, `qa`, etc). You can set it like so:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
Chalk::Config.environment = 'production'
|
19
|
+
```
|
20
|
+
|
21
|
+
At that point, the global `configatron` will be cleared and all
|
22
|
+
registered config reapplied, meaning you don't have to worry about
|
23
|
+
setting the environment prior to registering files.
|
24
|
+
|
25
|
+
`environment` defaults to the value `'default'`.
|
26
|
+
|
27
|
+
### Registering config files
|
28
|
+
|
29
|
+
Additional configuration files are registered using
|
30
|
+
{Chalk::Config.register}. The most basic usage looks like
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Chalk::Config.register('/path/to/file')
|
34
|
+
```
|
35
|
+
|
36
|
+
to register a YAML configuration file. You must provide an absolute
|
37
|
+
path, in order to ensure you're not relying on the present working
|
38
|
+
directory. The following is a pretty good idiom:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Chalk::Config.register(File.expand_path('../config.yaml', __FILE__))
|
42
|
+
```
|
43
|
+
|
44
|
+
By default, YAML configuration files should have a top-level key for
|
45
|
+
each environment.
|
46
|
+
|
47
|
+
A good convention is to have most configuration in a dummy `default`
|
48
|
+
environment and use YAML's native merging to keep your file somewhat
|
49
|
+
DRY (WARNING: there exists at least one gem which changes Ruby's YAML
|
50
|
+
parser in the presence of multiple merge operators on a single key, so
|
51
|
+
be wary of two `<<` calls at once.) However, it's also fine to repeat
|
52
|
+
yourself to make the file more human readable.
|
53
|
+
|
54
|
+
```yaml
|
55
|
+
# /path/to/config.yaml
|
56
|
+
|
57
|
+
default: &default
|
58
|
+
my_feature:
|
59
|
+
enable: true
|
60
|
+
shards: 2
|
61
|
+
|
62
|
+
my_service:
|
63
|
+
host: localhost
|
64
|
+
port: 2800
|
65
|
+
|
66
|
+
production:
|
67
|
+
<<: *default
|
68
|
+
my_service:
|
69
|
+
host: appserver1
|
70
|
+
port: 2800
|
71
|
+
|
72
|
+
send_emails: true
|
73
|
+
|
74
|
+
development:
|
75
|
+
<<: *default
|
76
|
+
send_emails: false
|
77
|
+
```
|
78
|
+
|
79
|
+
The configuration from the currently active environment will then be
|
80
|
+
added to the global `configatron` when you register the file:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
Chalk::Config.register('/path/to/config.yaml')
|
84
|
+
|
85
|
+
Chalk::Config.environment = 'production'
|
86
|
+
configatron.my_service.host
|
87
|
+
#=> 'appserver1'
|
88
|
+
|
89
|
+
Chalk::Config.environment = 'development'
|
90
|
+
configatron.my_service.host
|
91
|
+
#=> 'localhost'
|
92
|
+
```
|
93
|
+
|
94
|
+
Keys present in multiple files will be deep merged:
|
95
|
+
|
96
|
+
```yaml
|
97
|
+
# /path/to/site.yaml
|
98
|
+
|
99
|
+
production:
|
100
|
+
my_service:
|
101
|
+
host: otherappserver
|
102
|
+
|
103
|
+
development: {}
|
104
|
+
```
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
Chalk::Config.register('/path/to/config.yaml')
|
108
|
+
Chalk::Config.register('/path/to/site.yaml')
|
109
|
+
|
110
|
+
Chalk::Config.environment = 'production'
|
111
|
+
configatron.my_service.host
|
112
|
+
#=> 'otherappserver'
|
113
|
+
configatron.my_service.port
|
114
|
+
#=> 2800
|
115
|
+
```
|
116
|
+
|
117
|
+
You can explicitly nest a config file (only a single level of nesting
|
118
|
+
is current supported) using the `:nested` option. You can also
|
119
|
+
indicate a file has no environment keys and should be applied directly
|
120
|
+
via `:raw`:
|
121
|
+
|
122
|
+
|
123
|
+
```yaml
|
124
|
+
# /path/to/cookies.yaml
|
125
|
+
|
126
|
+
tasty: yes
|
127
|
+
```
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
Chalk::Config.register('/path/to/cookies.yaml', nested: 'cookies', raw: true)
|
131
|
+
configatron.cookies.tasty
|
132
|
+
#=> 'yes'
|
133
|
+
```
|
134
|
+
|
135
|
+
## Best practices
|
136
|
+
|
137
|
+
### Config keys for everything
|
138
|
+
|
139
|
+
Writing code that switches off the environment is usually indicative
|
140
|
+
of the antipattern:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# BAD!
|
144
|
+
if Chalk::Config.environment == 'production'
|
145
|
+
email.send!
|
146
|
+
else
|
147
|
+
puts email
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
Instead, you should create a fresh config key for all of these cases:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
if configatron.send_emails
|
155
|
+
email.send!
|
156
|
+
else
|
157
|
+
puts email
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
This means your code doesn't need to know anything about the set of
|
162
|
+
environment names, making adding a new environment easy. As well, it's
|
163
|
+
much easier to have fine-grained control and visibility over exactly
|
164
|
+
how your application behaves.
|
165
|
+
|
166
|
+
It's totally fine (and expected) to have many config keys that are
|
167
|
+
used only once.
|
168
|
+
|
169
|
+
# Contributors
|
170
|
+
|
171
|
+
- Greg Brockman
|
172
|
+
- Evan Broder
|
173
|
+
- Michelle Bu
|
174
|
+
- Nelson Elhage
|
175
|
+
- Jeremy Hoon
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'chalk-rake/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs = ['lib']
|
8
|
+
# t.warning = true
|
9
|
+
t.verbose = true
|
10
|
+
t.test_files = FileList['test/**/*.rb'].reject do |file|
|
11
|
+
file.end_with?('_lib.rb') || file.include?('/_lib/')
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chalk-config/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'chalk-config'
|
8
|
+
gem.version = Chalk::Config::VERSION
|
9
|
+
gem.authors = ['Stripe']
|
10
|
+
gem.email = ['oss@stripe.com']
|
11
|
+
gem.description = %q{Layer over configatron with conventions for environment-based and site-specific config}
|
12
|
+
gem.summary = %q{chalk-configatron uses a config_schema.yaml file to figure out how to configure your app}
|
13
|
+
gem.homepage = 'https://github.com/stripe/chalk-config'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
gem.add_dependency 'configatron', '~> 4.4'
|
20
|
+
gem.add_development_dependency 'rake'
|
21
|
+
gem.add_development_dependency 'minitest'
|
22
|
+
gem.add_development_dependency 'mocha'
|
23
|
+
gem.add_development_dependency 'chalk-rake'
|
24
|
+
end
|
data/demo_schema.rb
ADDED
data/lib/chalk-config.rb
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'yaml'
|
3
|
+
require 'chalk-config/version'
|
4
|
+
require 'chalk-config/errors'
|
5
|
+
|
6
|
+
# We don't necessarily need to keep this, but it's preferable to make
|
7
|
+
# sure no one defines any keys outside of Chalk::Config
|
8
|
+
if defined?(configatron)
|
9
|
+
raise "Someone already loaded 'configatron'. You should let chalk-config load it itself."
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'configatron'
|
13
|
+
configatron.lock!
|
14
|
+
|
15
|
+
# The main class powering Chalk's configuration.
|
16
|
+
#
|
17
|
+
# This is written using a wrapped Singleton, which makes testing
|
18
|
+
# possible (just stub `Chalk::Config.instance` to return a fresh
|
19
|
+
# instance) and helps hide implementation.
|
20
|
+
class Chalk::Config
|
21
|
+
include Singleton
|
22
|
+
|
23
|
+
# Sets the current environment. All configuration is then reapplied
|
24
|
+
# in the order it was {.register}ed. This means you don't have to
|
25
|
+
# worry about setting your environment prior to registering config
|
26
|
+
# files.
|
27
|
+
#
|
28
|
+
# @return [String] The current environment.
|
29
|
+
def self.environment=(name)
|
30
|
+
instance.send(:environment=, name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# You should generally not take any action directly off this
|
34
|
+
# value. All codepath switches should be triggered off configuration
|
35
|
+
# keys, possibly with environment assertions to ensure safety.
|
36
|
+
#
|
37
|
+
# @return [String] The current environment (default: `'default'`)
|
38
|
+
def self.environment
|
39
|
+
instance.send(:environment)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Specify the list of environments every configuration file must
|
43
|
+
# include.
|
44
|
+
#
|
45
|
+
# It's generally recommended to set this in a wrapper library, and
|
46
|
+
# use that wrapper library in all your projects. This way you can be
|
47
|
+
# defensive, and have certainty no config file slips through without
|
48
|
+
# the requisite environment keys.
|
49
|
+
#
|
50
|
+
# @param environments [Enumerable<String>] The list of required environments.
|
51
|
+
def self.required_environments=(environments)
|
52
|
+
instance.send(:required_environments=, environments)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Access the environments registered by {.required_environments=}.
|
56
|
+
#
|
57
|
+
# @return [Enumerable] The registered environments list (by default, nil)
|
58
|
+
def self.required_environments
|
59
|
+
instance.send(:required_environments)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Register a given YAML file to be included in the global
|
63
|
+
# configuration.
|
64
|
+
#
|
65
|
+
# The config will be loaded once (cached in memory) and be
|
66
|
+
# immediately deep-merged onto configatron. If you later run
|
67
|
+
# {.environment=}, then all registered configs will be reapplied in
|
68
|
+
# the order they were loaded.
|
69
|
+
#
|
70
|
+
# So for example, running
|
71
|
+
# `Chalk::Config.register('/path/to/config.yaml')` for a file with
|
72
|
+
# contents:
|
73
|
+
#
|
74
|
+
# ```yaml
|
75
|
+
# env1:
|
76
|
+
# key1: value1
|
77
|
+
# key2: value2
|
78
|
+
# ```
|
79
|
+
#
|
80
|
+
# would yield `configatron.env1.key1 == value1`,
|
81
|
+
# `configatron.env1.key2 == value2`. Later registering a file with
|
82
|
+
# contents:
|
83
|
+
#
|
84
|
+
# ```yaml
|
85
|
+
# env1:
|
86
|
+
# key1: value3
|
87
|
+
# ```
|
88
|
+
#
|
89
|
+
# would yield `configatron.env1.key1 == value3`,
|
90
|
+
# `configatron.env1.key2 == value2`.
|
91
|
+
#
|
92
|
+
# @param filepath [String] Absolute path to the config file
|
93
|
+
# @option options [Boolean] :optional If true, it's fine for the
|
94
|
+
# file to be missing, in which case this registration is
|
95
|
+
# discarded.
|
96
|
+
# @option options [Boolean] :raw If true, the file doesn't have
|
97
|
+
# environment keys and should be splatted onto configatron
|
98
|
+
# directly. Otherwise, grab just the config under the appropriate
|
99
|
+
# environment key.
|
100
|
+
# @option options [String] :nested What key to namespace all of
|
101
|
+
# this configuration under. (So `nested: 'foo'` would result in
|
102
|
+
# configuration available under `configatron.foo.*`.)
|
103
|
+
def self.register(filepath, options={})
|
104
|
+
unless filepath.start_with?('/')
|
105
|
+
raise ArgumentError.new("Register only accepts absolute paths, not #{filepath.inspect}. (This ensures that config is always correctly loaded rather than depending on your current directory. To avoid this error in the future, you may want to use a wrapper that expands paths based on a base directory.)")
|
106
|
+
end
|
107
|
+
instance.send(:register, filepath, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Register a given raw hash to be included in the global
|
111
|
+
# configuration.
|
112
|
+
#
|
113
|
+
# This allows you to specify arbitrary configuration at
|
114
|
+
# runtime. It's generally not recommended that you use this method
|
115
|
+
# unless your configuration really can't be encoded in config
|
116
|
+
# files. A common example is configuration from environment
|
117
|
+
# variables (which might be something like the name of your
|
118
|
+
# service).
|
119
|
+
#
|
120
|
+
# Like {.register}, if you later run {.environment=}, this
|
121
|
+
# configuration will be reapplied in the order it was registered.
|
122
|
+
#
|
123
|
+
# @param config [Hash] The raw configuration to be deep-merged into configatron.
|
124
|
+
def self.register_raw(config)
|
125
|
+
instance.send(:register_raw, config)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Raises if the current environment is not one of the whitelisted
|
129
|
+
# environments provided.
|
130
|
+
#
|
131
|
+
# Generally useful if you have a dev-only codepath you want to be
|
132
|
+
# *sure* never activates in production.
|
133
|
+
def self.assert_environment(environments)
|
134
|
+
environments = [environments] if environments.kind_of?(String)
|
135
|
+
return if environments.include?(environment)
|
136
|
+
|
137
|
+
raise DisallowedEnvironment.new("Current environment #{environment.inspect} is not one of the allowed environments #{environments.inspect}")
|
138
|
+
end
|
139
|
+
|
140
|
+
# Raises if the current environment is one of the blacklisted
|
141
|
+
# environments provided.
|
142
|
+
#
|
143
|
+
# Generally useful if you have a dev-only codepath you want to be
|
144
|
+
# *sure* never activates in production.
|
145
|
+
def self.assert_not_environment(environments)
|
146
|
+
environments = [environments] if environments.kind_of?(String)
|
147
|
+
return unless environments.include?(environment)
|
148
|
+
|
149
|
+
raise DisallowedEnvironment.new("Current environment #{environment.inspect} is one of the disallowed environments #{environments.inspect}")
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def initialize
|
155
|
+
# List of registered configs, in the form:
|
156
|
+
#
|
157
|
+
# file => {config: ..., options: ...}
|
158
|
+
@registrations = []
|
159
|
+
@registered_files = Set.new
|
160
|
+
@environment = 'default'
|
161
|
+
end
|
162
|
+
|
163
|
+
## The actual instance implementations
|
164
|
+
|
165
|
+
# Possibly reconfigure if the environment changes.
|
166
|
+
def environment=(name)
|
167
|
+
@environment = name
|
168
|
+
reapply_config
|
169
|
+
end
|
170
|
+
|
171
|
+
def environment
|
172
|
+
@environment
|
173
|
+
end
|
174
|
+
|
175
|
+
def required_environments=(environments)
|
176
|
+
@required_environments = environments
|
177
|
+
@registrations.each do |registration|
|
178
|
+
# Validate all existing config
|
179
|
+
validate_config(registration)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def required_environments
|
184
|
+
@required_environments
|
185
|
+
end
|
186
|
+
|
187
|
+
def register(filepath, options)
|
188
|
+
if @registered_files.include?(filepath)
|
189
|
+
raise Error.new("You've already registered #{filepath}.")
|
190
|
+
end
|
191
|
+
@registered_files << filepath
|
192
|
+
|
193
|
+
begin
|
194
|
+
config = load!(filepath)
|
195
|
+
rescue Errno::ENOENT
|
196
|
+
return if options[:optional]
|
197
|
+
raise
|
198
|
+
end
|
199
|
+
|
200
|
+
register_parsed(config, filepath, options)
|
201
|
+
end
|
202
|
+
|
203
|
+
def register_raw(config)
|
204
|
+
register_parsed(config, nil, {})
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
# Register some raw config
|
210
|
+
def register_parsed(config, filepath, options)
|
211
|
+
allow_configatron_changes do
|
212
|
+
directive = {
|
213
|
+
config: config,
|
214
|
+
filepath: filepath,
|
215
|
+
options: options,
|
216
|
+
}
|
217
|
+
|
218
|
+
validate_config(directive)
|
219
|
+
@registrations << directive
|
220
|
+
|
221
|
+
allow_configatron_changes do
|
222
|
+
mixin_config(directive)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def allow_configatron_changes(&blk)
|
228
|
+
configatron.unlock!
|
229
|
+
|
230
|
+
begin
|
231
|
+
blk.call
|
232
|
+
ensure
|
233
|
+
configatron.lock!
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def load!(filepath)
|
238
|
+
begin
|
239
|
+
loaded = YAML.load_file(filepath)
|
240
|
+
rescue Psych::BadAlias => e
|
241
|
+
# YAML parse-time errors include the filepath already, but
|
242
|
+
# load-time errors do not.
|
243
|
+
#
|
244
|
+
# Specifically, `Psych::BadAlias` (raised by doing something
|
245
|
+
# like `YAML.load('foo: *bar')`) does not:
|
246
|
+
# https://github.com/tenderlove/psych/issues/192
|
247
|
+
e.message << " (while loading #{filepath})"
|
248
|
+
raise
|
249
|
+
end
|
250
|
+
unless loaded.is_a?(Hash)
|
251
|
+
raise Error.new("YAML.load(#{filepath.inspect}) parses into a #{loaded.class}, not a Hash")
|
252
|
+
end
|
253
|
+
loaded
|
254
|
+
end
|
255
|
+
|
256
|
+
# Take a hash and mix in the environment-appropriate key to an
|
257
|
+
# existing configatron object.
|
258
|
+
def mixin_config(directive)
|
259
|
+
raw = directive[:options][:raw]
|
260
|
+
|
261
|
+
config = directive[:config]
|
262
|
+
filepath = directive[:filepath]
|
263
|
+
|
264
|
+
if !raw && filepath && config && !config.include?(environment)
|
265
|
+
# Directive is derived from a file (i.e. not runtime_config)
|
266
|
+
# with environments and that file existed, but is missing the
|
267
|
+
# environment.
|
268
|
+
raise MissingEnvironment.new("Current environment #{environment.inspect} not defined in config file #{directive[:filepath].inspect}. (HINT: you should have a YAML key of #{environment.inspect}. You may want to inherit a default via YAML's `<<` operator.)")
|
269
|
+
end
|
270
|
+
|
271
|
+
if raw
|
272
|
+
choice = config
|
273
|
+
elsif filepath && config
|
274
|
+
# Derived from file, and file present
|
275
|
+
choice = config.fetch(environment)
|
276
|
+
elsif filepath
|
277
|
+
# Derived from file, but file missing
|
278
|
+
choice = {}
|
279
|
+
else
|
280
|
+
# Manually specified runtime_config
|
281
|
+
choice = config
|
282
|
+
end
|
283
|
+
|
284
|
+
subconfigatron = configatron
|
285
|
+
if nested = directive[:options][:nested]
|
286
|
+
nested.split('.').each do |key|
|
287
|
+
subconfigatron = subconfigatron[key]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
subconfigatron.configure_from_hash(choice)
|
292
|
+
end
|
293
|
+
|
294
|
+
def validate_config(directive)
|
295
|
+
(@required_environments || []).each do |environment|
|
296
|
+
raw = directive[:options][:raw]
|
297
|
+
|
298
|
+
config = directive[:config]
|
299
|
+
filepath = directive[:filepath]
|
300
|
+
|
301
|
+
next if raw
|
302
|
+
|
303
|
+
if filepath && config && !config.include?(environment)
|
304
|
+
raise MissingEnvironment.new("Required environment #{environment.inspect} not defined in config file #{directive[:filepath].inspect}. (HINT: you should have a YAML key of #{environment.inspect}. You may want to inherit a default via YAML's `<<` operator.)")
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def reapply_config
|
310
|
+
allow_configatron_changes do
|
311
|
+
configatron.reset!
|
312
|
+
@registrations.each do |registration|
|
313
|
+
mixin_config(registration)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Chalk::Config
|
2
|
+
# Base error class for Chalk::Config
|
3
|
+
class Error < StandardError; end
|
4
|
+
# Thrown if an environment is missing from a config file.
|
5
|
+
class MissingEnvironment < Error; end
|
6
|
+
# Thrown from environment assertions.
|
7
|
+
class DisallowedEnvironment < Error; end
|
8
|
+
end
|
data/tddium.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
:tddium:
|
2
|
+
:ruby_version: ruby-1.9.3-p194
|
3
|
+
:bundler_version: 1.3.5
|
4
|
+
:test_pattern:
|
5
|
+
- "test/wholesome/[^_]*.rb"
|
6
|
+
- "test/unit/[^_]*.rb"
|
7
|
+
- "test/integration/[^_]*.rb"
|
8
|
+
- "test/functional/[^_]*.rb"
|
9
|
+
:boot_hook: >
|
10
|
+
/usr/bin/env sh -c "gem install --user-install --no-rdoc --no-ri github_api -v=0.10.1 && gem install --user-install --no-rdoc --no-ri --ignore-dependencies tddium_status_github -v=0.2.0 &&
|
11
|
+
echo '$:.concat(Dir.glob(File.join(Gem.user_dir, \"gems\", \"*\", \"lib\"))); require \"tddium_status_github\"' >> Rakefile"
|
data/test/_lib.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/spec'
|
6
|
+
require 'mocha/setup'
|
7
|
+
|
8
|
+
module Critic
|
9
|
+
class Test < ::MiniTest::Spec
|
10
|
+
def setup
|
11
|
+
# Put any stubs here that you want to apply globally
|
12
|
+
end
|
13
|
+
|
14
|
+
def fresh_chalk_config
|
15
|
+
config = Chalk::Config.send(:new)
|
16
|
+
Chalk::Config.stubs(instance: config)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path('../_lib', __FILE__)
|
2
|
+
require 'chalk-config'
|
3
|
+
|
4
|
+
class Critic::Functional::GeneralTest < Critic::Functional::Test
|
5
|
+
include Configatron::Integrations::Minitest
|
6
|
+
|
7
|
+
before do
|
8
|
+
fresh_chalk_config
|
9
|
+
|
10
|
+
Chalk::Config.environment = 'testing'
|
11
|
+
Chalk::Config.register(File.expand_path('../general/config.yaml', __FILE__))
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'loads config correctly' do
|
15
|
+
assert_equal('value1', configatron.config1)
|
16
|
+
assert_equal('value3', configatron.config2)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'allows switching environments' do
|
20
|
+
Chalk::Config.environment = 'default'
|
21
|
+
assert_equal('value1', configatron.config1)
|
22
|
+
assert_equal('value2', configatron.config2)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'register_raw' do
|
26
|
+
it 'merges in registered config' do
|
27
|
+
Chalk::Config.register_raw(foo: 'bar', config1: 'hi')
|
28
|
+
assert_equal('bar', configatron.foo)
|
29
|
+
assert_equal('hi', configatron.config1)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'environment validation continues to succeed' do
|
33
|
+
Chalk::Config.register_raw(foo: 'bar')
|
34
|
+
Chalk::Config.required_environments = ['default']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'raw files' do
|
39
|
+
it 'merges the file contents directly' do
|
40
|
+
Chalk::Config.register(File.expand_path('../general/raw.yaml', __FILE__),
|
41
|
+
raw: true)
|
42
|
+
assert_equal('there', configatron.hi)
|
43
|
+
assert_equal('bat', configatron.baz)
|
44
|
+
assert_equal('no_environment', configatron.config1)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does not try to validate presence of environments' do
|
48
|
+
Chalk::Config.required_environments = ['default']
|
49
|
+
Chalk::Config.register(File.expand_path('../general/raw.yaml', __FILE__),
|
50
|
+
raw: true)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'missing nested files' do
|
55
|
+
it 'does not create the relevant config key' do
|
56
|
+
Chalk::Config.register(File.expand_path('../general/nonexistent.yaml', __FILE__),
|
57
|
+
optional: true,
|
58
|
+
nested: 'nonexistent')
|
59
|
+
assert_raises(Configatron::UndefinedKeyError) do
|
60
|
+
configatron.nonexistent
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'environment validation continues to succeed' do
|
65
|
+
Chalk::Config.register(File.expand_path('../general/nonexistent.yaml', __FILE__),
|
66
|
+
optional: true,
|
67
|
+
nested: 'nonexistent')
|
68
|
+
Chalk::Config.required_environments = ['default']
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'nested config files' do
|
73
|
+
it 'nests' do
|
74
|
+
Chalk::Config.register(File.expand_path('../general/raw.yaml', __FILE__),
|
75
|
+
nested: 'nested',
|
76
|
+
raw: true)
|
77
|
+
assert_equal('there', configatron.nested.hi)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'deep-nests' do
|
81
|
+
Chalk::Config.register(File.expand_path('../general/raw.yaml', __FILE__),
|
82
|
+
nested: 'nested.deeply',
|
83
|
+
raw: true)
|
84
|
+
assert_equal('there', configatron.nested.deeply.hi)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'required_environments' do
|
89
|
+
it 'raises if an existing config is missing an environment' do
|
90
|
+
assert_raises(Chalk::Config::MissingEnvironment) do
|
91
|
+
Chalk::Config.required_environments = ['missing']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'raises if a new config is missing an environment' do
|
96
|
+
Chalk::Config.required_environments = ['testing']
|
97
|
+
assert_raises(Chalk::Config::MissingEnvironment) do
|
98
|
+
Chalk::Config.register(File.expand_path('../general/missing.yaml', __FILE__))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/test/meta/_lib.rb
ADDED
data/test/unit/_lib.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path('../_lib', __FILE__)
|
2
|
+
require 'chalk-config'
|
3
|
+
|
4
|
+
class Critic::Unit::ChalkConfig < Critic::Unit::Test
|
5
|
+
include Configatron::Integrations::Minitest
|
6
|
+
|
7
|
+
before do
|
8
|
+
fresh_chalk_config
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.assert_environment' do
|
12
|
+
it 'raises if the environment is not in the array' do
|
13
|
+
Chalk::Config.environment = 'hello'
|
14
|
+
assert_raises(Chalk::Config::DisallowedEnvironment) do
|
15
|
+
Chalk::Config.assert_environment(%w{foo bar})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'raises if the environment is not the string' do
|
20
|
+
Chalk::Config.environment = 'hello'
|
21
|
+
assert_raises(Chalk::Config::DisallowedEnvironment) do
|
22
|
+
Chalk::Config.assert_environment('foo')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not raise if the environment is in the array' do
|
27
|
+
Chalk::Config.environment = 'hello'
|
28
|
+
Chalk::Config.assert_environment(%w{hello there})
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not raise if the environment is the string' do
|
32
|
+
Chalk::Config.environment = 'hello'
|
33
|
+
Chalk::Config.assert_environment('hello')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '.assert_not_environment' do
|
38
|
+
it 'does not raise if the environment is not in the array' do
|
39
|
+
Chalk::Config.environment = 'hello'
|
40
|
+
Chalk::Config.assert_not_environment(%w{foo bar})
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not raise if the environment is not the string' do
|
44
|
+
Chalk::Config.environment = 'hello'
|
45
|
+
Chalk::Config.assert_not_environment('foo')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises if the environment is in the array' do
|
49
|
+
Chalk::Config.environment = 'hello'
|
50
|
+
assert_raises(Chalk::Config::DisallowedEnvironment) do
|
51
|
+
Chalk::Config.assert_not_environment(%w{hello there})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'raises if the environment is the string' do
|
56
|
+
Chalk::Config.environment = 'hello'
|
57
|
+
assert_raises(Chalk::Config::DisallowedEnvironment) do
|
58
|
+
Chalk::Config.assert_not_environment('hello')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chalk-config
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Stripe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-12-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: configatron
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '4.4'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.4'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: minitest
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mocha
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: chalk-rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Layer over configatron with conventions for environment-based and site-specific
|
95
|
+
config
|
96
|
+
email:
|
97
|
+
- oss@stripe.com
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- .gitignore
|
103
|
+
- .yardopts
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- chalk-config.gemspec
|
109
|
+
- demo_schema.rb
|
110
|
+
- lib/chalk-config.rb
|
111
|
+
- lib/chalk-config/errors.rb
|
112
|
+
- lib/chalk-config/version.rb
|
113
|
+
- tddium.yml
|
114
|
+
- test/_lib.rb
|
115
|
+
- test/functional/_lib.rb
|
116
|
+
- test/functional/general.rb
|
117
|
+
- test/functional/general/config.yaml
|
118
|
+
- test/functional/general/missing.yaml
|
119
|
+
- test/functional/general/raw.yaml
|
120
|
+
- test/integration/_lib.rb
|
121
|
+
- test/meta/_lib.rb
|
122
|
+
- test/unit/_lib.rb
|
123
|
+
- test/unit/chalk-config.rb
|
124
|
+
homepage: https://github.com/stripe/chalk-config
|
125
|
+
licenses: []
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
hash: 2093141279955038068
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
hash: 2093141279955038068
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.25
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: chalk-configatron uses a config_schema.yaml file to figure out how to configure
|
154
|
+
your app
|
155
|
+
test_files:
|
156
|
+
- test/_lib.rb
|
157
|
+
- test/functional/_lib.rb
|
158
|
+
- test/functional/general.rb
|
159
|
+
- test/functional/general/config.yaml
|
160
|
+
- test/functional/general/missing.yaml
|
161
|
+
- test/functional/general/raw.yaml
|
162
|
+
- test/integration/_lib.rb
|
163
|
+
- test/meta/_lib.rb
|
164
|
+
- test/unit/_lib.rb
|
165
|
+
- test/unit/chalk-config.rb
|