chalk-config 0.2.1
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/.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
|