levels 0.1.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/.gitignore +17 -0
- data/.rbenv-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +12 -0
- data/Guardfile +14 -0
- data/LICENSE +22 -0
- data/README.md +315 -0
- data/Rakefile +28 -0
- data/bin/levels +130 -0
- data/examples/01_base.rb +6 -0
- data/examples/01_merge_to_json.sh +27 -0
- data/examples/01_prod.json +8 -0
- data/examples/02_base.rb +4 -0
- data/examples/02_merge_with_file.sh +20 -0
- data/examples/02_value +1 -0
- data/levels.gemspec +20 -0
- data/lib/levels.rb +77 -0
- data/lib/levels/audit.rb +24 -0
- data/lib/levels/audit/group_observer.rb +26 -0
- data/lib/levels/audit/nested_group_observer.rb +37 -0
- data/lib/levels/audit/root_observer.rb +63 -0
- data/lib/levels/audit/value.rb +64 -0
- data/lib/levels/audit/value_observer.rb +46 -0
- data/lib/levels/audit/values.rb +66 -0
- data/lib/levels/configuration.rb +98 -0
- data/lib/levels/configured_group.rb +62 -0
- data/lib/levels/event_handler.rb +127 -0
- data/lib/levels/group.rb +61 -0
- data/lib/levels/input/json.rb +17 -0
- data/lib/levels/input/ruby.rb +120 -0
- data/lib/levels/input/system.rb +63 -0
- data/lib/levels/input/yaml.rb +17 -0
- data/lib/levels/key.rb +28 -0
- data/lib/levels/key_values.rb +54 -0
- data/lib/levels/lazy_evaluator.rb +54 -0
- data/lib/levels/level.rb +80 -0
- data/lib/levels/method_missing.rb +14 -0
- data/lib/levels/output/json.rb +33 -0
- data/lib/levels/output/system.rb +29 -0
- data/lib/levels/output/yaml.rb +19 -0
- data/lib/levels/runtime.rb +30 -0
- data/lib/levels/setup.rb +132 -0
- data/lib/levels/system/constants.rb +8 -0
- data/lib/levels/system/key_formatter.rb +15 -0
- data/lib/levels/system/key_generator.rb +50 -0
- data/lib/levels/system/key_parser.rb +67 -0
- data/lib/levels/version.rb +3 -0
- data/test/acceptance/audit_test.rb +105 -0
- data/test/acceptance/event_handler_test.rb +43 -0
- data/test/acceptance/read_json_test.rb +35 -0
- data/test/acceptance/read_ruby_test.rb +117 -0
- data/test/acceptance/read_system_test.rb +121 -0
- data/test/acceptance/read_yaml_test.rb +38 -0
- data/test/acceptance/setup_test.rb +115 -0
- data/test/acceptance/write_json_test.rb +39 -0
- data/test/acceptance/write_system_test.rb +68 -0
- data/test/acceptance/write_yaml_test.rb +33 -0
- data/test/bin/merge_test.rb +194 -0
- data/test/bin/options_test.rb +41 -0
- data/test/helper.rb +12 -0
- data/test/support/acceptance_spec.rb +58 -0
- data/test/support/base_spec.rb +14 -0
- data/test/support/bin_spec.rb +65 -0
- data/test/support/tempfile_helper.rb +35 -0
- data/test/unit/audit/group_observer_test.rb +24 -0
- data/test/unit/audit/nested_group_observer_test.rb +28 -0
- data/test/unit/audit/root_observer_test.rb +54 -0
- data/test/unit/audit/value_observer_test.rb +63 -0
- data/test/unit/audit/value_test.rb +41 -0
- data/test/unit/audit/values_test.rb +86 -0
- data/test/unit/configuration_test.rb +72 -0
- data/test/unit/configured_group_test.rb +75 -0
- data/test/unit/group_test.rb +105 -0
- data/test/unit/input/json_test.rb +32 -0
- data/test/unit/input/ruby_test.rb +140 -0
- data/test/unit/input/system_test.rb +59 -0
- data/test/unit/input/yaml_test.rb +33 -0
- data/test/unit/key_test.rb +45 -0
- data/test/unit/key_values_test.rb +106 -0
- data/test/unit/lazy_evaluator_test.rb +38 -0
- data/test/unit/level_test.rb +89 -0
- data/test/unit/levels_test.rb +23 -0
- data/test/unit/output/json_test.rb +55 -0
- data/test/unit/output/system_test.rb +32 -0
- data/test/unit/output/yaml_test.rb +38 -0
- data/test/unit/runtime_test.rb +40 -0
- data/test/unit/system/key_formatter_test.rb +43 -0
- data/test/unit/system/key_generator_test.rb +21 -0
- data/test/unit/system/key_parser_test.rb +207 -0
- metadata +215 -0
data/.gitignore
ADDED
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p194
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'minitest' do
|
5
|
+
watch(%r|^test/(.*)\/?(.*)_test\.rb|)
|
6
|
+
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m|
|
7
|
+
[
|
8
|
+
"test/#{m[1]}#{m[2]}_test.rb",
|
9
|
+
"test/#{m[1].sub(/^levels/, 'unit')}#{m[2]}_test.rb"
|
10
|
+
]
|
11
|
+
}
|
12
|
+
watch(%r|^test/helper\.rb|) { "test" }
|
13
|
+
watch(%r|^bin/|) { "test/bin" }
|
14
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Ryan Carver
|
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,315 @@
|
|
1
|
+
# Levels
|
2
|
+
|
3
|
+
[](http://travis-ci.org/rcarver/levels)
|
4
|
+
|
5
|
+
Levels is a tool for merging configuration data. A level is a set of
|
6
|
+
key/value pairs that represent your data. Multiple levels, written in a
|
7
|
+
variety of formats can be merged in a predictable, useful way to form
|
8
|
+
a final configuration.
|
9
|
+
|
10
|
+
> **KRAMER:** *I'm completely changing the configuration of the apartment. You're not gonna believe it when you see it. A whole new lifestyle.*
|
11
|
+
|
12
|
+
> **JERRY:** *What are you doing?*
|
13
|
+
|
14
|
+
> **KRAMER:** *Levels.*
|
15
|
+
|
16
|
+
## Creating a level
|
17
|
+
|
18
|
+
A level is made up of one or more groups. A group is a set of key/value
|
19
|
+
pairs. To describe a very simple web application made up of a server and
|
20
|
+
a task queue, you could write this (in JSON).
|
21
|
+
|
22
|
+
```json
|
23
|
+
{
|
24
|
+
"server": {
|
25
|
+
"hostname": "example.com"
|
26
|
+
},
|
27
|
+
"task_queue": {
|
28
|
+
"workers": 5,
|
29
|
+
"queues": ["high", "low"]
|
30
|
+
}
|
31
|
+
}
|
32
|
+
```
|
33
|
+
|
34
|
+
Now consider having a common "base" configuration, with slight
|
35
|
+
differences in development and production. Our base configuration
|
36
|
+
defines the possible keys, with default values.
|
37
|
+
|
38
|
+
A "production" level can override the relevant values like this.
|
39
|
+
|
40
|
+
```json
|
41
|
+
{
|
42
|
+
"server": {
|
43
|
+
"hostname": "example.com"
|
44
|
+
},
|
45
|
+
"task_queue": {
|
46
|
+
"workers": 5
|
47
|
+
}
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
51
|
+
The system's environment may be used as a level. To alter any value at
|
52
|
+
runtime, follow a convention to set the appropriate environment
|
53
|
+
variable.
|
54
|
+
|
55
|
+
```bash
|
56
|
+
TASK_QUEUE_WORKERS="10"
|
57
|
+
```
|
58
|
+
|
59
|
+
### Writing a level
|
60
|
+
|
61
|
+
A level may be written in one of many formats.
|
62
|
+
|
63
|
+
* **RUBY** is the most common and powerful for hand written configs.
|
64
|
+
* **JSON** is convenient for machine generated configs.
|
65
|
+
* **YAML** is good for both hand written and machine generated configs.
|
66
|
+
* **Environment Variables** are useful for local or runtime
|
67
|
+
configuration. This syntax may not be used for the "base" level.
|
68
|
+
|
69
|
+
#### Data Types
|
70
|
+
|
71
|
+
Levels has a limited understanding of data types by design. The guiding
|
72
|
+
principles are:
|
73
|
+
|
74
|
+
* It must be possible to represent any value in an environment
|
75
|
+
variable.
|
76
|
+
* Use only types that are native in JSON.
|
77
|
+
|
78
|
+
Therefore, Levels only supports the following types:
|
79
|
+
|
80
|
+
* **string** (Ruby `String`)
|
81
|
+
* **integer** (Ruby `Fixnum`)
|
82
|
+
* **float** (Ruby `Float`)
|
83
|
+
* **boolean** (Ruby `TrueClass` or `FalseClass`)
|
84
|
+
* **array** (Ruby `Array`) of values, which are also typed.
|
85
|
+
* **null** (Ruby `NilClass`)
|
86
|
+
|
87
|
+
Notice that JSON's Object is not supported. This is because groups are
|
88
|
+
objects, so key/values pairs are already available. It's difficult to
|
89
|
+
represent key/value pairs in an environment variable, so it fails that
|
90
|
+
test as well.
|
91
|
+
|
92
|
+
Fortunately, these simple types are perfectly adequate for the purposes
|
93
|
+
of system configuration.
|
94
|
+
|
95
|
+
#### Ruby Syntax
|
96
|
+
|
97
|
+
The Ruby DSL is a clean, simple format. It aims to be readable, writable and
|
98
|
+
editable. It looks like this:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
group :server
|
102
|
+
set hostname: "example.com"
|
103
|
+
|
104
|
+
group :task_queue
|
105
|
+
set workers: 5
|
106
|
+
set queues: ["high", "low"]
|
107
|
+
```
|
108
|
+
|
109
|
+
The Ruby syntax supports **computed values**.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
group :task_queue
|
113
|
+
set queues: -> { [server.hostname, "high", "low"] }
|
114
|
+
```
|
115
|
+
|
116
|
+
##### Extending the Ruby Runtime
|
117
|
+
|
118
|
+
To extend the runtime environment, add methods to `Levels::Runtime`.
|
119
|
+
Those methods can return a value directly, or return a Proc for
|
120
|
+
lazy evaluation.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
module Levels::Runtime
|
124
|
+
# This helper decrypts a value using the merged value of
|
125
|
+
# `secret_keys.sha_key`.
|
126
|
+
def encrypted(encrypted_value)
|
127
|
+
-> { SHA.decrypt(encrypted_value, secret_keys.sha_key) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
With this runtime helper, you can now write:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
group :aws
|
136
|
+
set secret_key: encrypted("your aws secret key")
|
137
|
+
```
|
138
|
+
|
139
|
+
##### Builtin runtime extensions
|
140
|
+
|
141
|
+
These functions are provided by the default Levels Runtime.
|
142
|
+
|
143
|
+
* `file(path)` reads the value from a file. The file path is
|
144
|
+
interpreted as relative to the Ruby file unless it begins with '/'.
|
145
|
+
File storage can be useful when configuring large strings such as
|
146
|
+
SSL keys.
|
147
|
+
|
148
|
+
#### JSON Syntax
|
149
|
+
|
150
|
+
JSON syntax is straightforward. Because the datatypes supported by
|
151
|
+
Levels are the same as supported by JSON, there's nothing else you need
|
152
|
+
to know.
|
153
|
+
|
154
|
+
```json
|
155
|
+
{
|
156
|
+
"server": {
|
157
|
+
"hostname": "example.com"
|
158
|
+
},
|
159
|
+
"task_queue": {
|
160
|
+
"workers": 5,
|
161
|
+
"queues": ["high", "low"]
|
162
|
+
}
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
#### YAML Syntax
|
167
|
+
|
168
|
+
YAML syntax is also exactly as you would expect.
|
169
|
+
|
170
|
+
```yaml
|
171
|
+
---
|
172
|
+
server:
|
173
|
+
hostname: example.com
|
174
|
+
task_queue:
|
175
|
+
workers: 5
|
176
|
+
queues:
|
177
|
+
- high
|
178
|
+
- low
|
179
|
+
```
|
180
|
+
|
181
|
+
#### Environment Variables syntax
|
182
|
+
|
183
|
+
The environment variables syntax has rules for defining keys and values.
|
184
|
+
|
185
|
+
The format of each key is `[PREFIX]<GROUP>_<KEY>`.
|
186
|
+
|
187
|
+
* `PREFIX` is an optional prefix for all keys.
|
188
|
+
* `GROUP` is the name of the group in all caps.
|
189
|
+
* `KEY` is the name of the key in all caps.
|
190
|
+
* `GROUP` and `KEY` are separated by an underscore (`_`).
|
191
|
+
|
192
|
+
The example looks like this (without a prefix).
|
193
|
+
|
194
|
+
```sh
|
195
|
+
SERVER_HOSTNAME="example.com"
|
196
|
+
TASK_QUEUE_WORKERS="5"
|
197
|
+
TASK_QUEUE_QUEUES="high:low"
|
198
|
+
```
|
199
|
+
|
200
|
+
##### Typecasting
|
201
|
+
|
202
|
+
You'll notice that `TASK_QUEUE_WORKERS` should be an integer, and
|
203
|
+
`TASK_QUEUE_QUEUES` should be an array. Levels will typecast each value
|
204
|
+
based on the key's type in the "base" level. Or, you may define each
|
205
|
+
value's type explicitly.
|
206
|
+
|
207
|
+
To set the type of a value, set `<GROUP>_<KEY>_TYPE` to one of the
|
208
|
+
following:
|
209
|
+
|
210
|
+
* `string` - The value is taken as is.
|
211
|
+
* `integer` - The value is converted to an integer via Ruby's `to_i`.
|
212
|
+
* `float` - The value is converted to a float via Ruby's `to_f`.
|
213
|
+
* `boolean` - The value is `true` if it's "true" or "1", else `false`.
|
214
|
+
* `array` - The value is split using colon (`:`) or
|
215
|
+
`<GROUP>_<KEY>_DELIMITER`. The values of the resulting array may be
|
216
|
+
typecast using `<GROUP>_<KEY>_TYPE_TYPE`.
|
217
|
+
|
218
|
+
Any value may be set to Ruby's `nil` (`NULL`) by setting it to an empty
|
219
|
+
string.
|
220
|
+
|
221
|
+
Some examples:
|
222
|
+
|
223
|
+
```sh
|
224
|
+
SAMPLE_MY_NULL=""
|
225
|
+
|
226
|
+
SAMPLE_MY_INT="123"
|
227
|
+
SAMPLE_MY_INT_TYPE="integer"
|
228
|
+
|
229
|
+
SAMPLE_MY_BOOL="true"
|
230
|
+
SAMPLE_MY_BOOL_TYPE="boolean"
|
231
|
+
|
232
|
+
SAMPLE_MY_STRING_ARRAY="a:b:c"
|
233
|
+
SAMPLE_MY_STRING_ARRAY_TYPE="array"
|
234
|
+
|
235
|
+
SAMPLE_MY_INT_ARRAY="1:2:3"
|
236
|
+
SAMPLE_MY_INT_ARRAY_TYPE="array"
|
237
|
+
SAMPLE_MY_INT_ARRAY_TYPE_TYPE="integer"
|
238
|
+
|
239
|
+
SAMPLE_MY_CSV_ARRAY="one,two,three"
|
240
|
+
SAMPLE_MY_CSV_ARRAY_TYPE="array"
|
241
|
+
SAMPLE_MY_CSV_ARRAY_DELIMITER=","
|
242
|
+
```
|
243
|
+
|
244
|
+
## Using a Configuration
|
245
|
+
|
246
|
+
Once a level has been written, you can read and merge it. Once merged
|
247
|
+
into a Configuration, you can use it at runtime in a Ruby process, or
|
248
|
+
output it as JSON, YAML or environment variables.
|
249
|
+
|
250
|
+
Any number of levels, including the system environment, may be merged.
|
251
|
+
The system environment is typically merged last, but it's not required.
|
252
|
+
|
253
|
+
**From the command line**, Levels can generate JSON, YAML or environment
|
254
|
+
variables. The generated configuration is written to STDOUT. Both JSON
|
255
|
+
and Environment Variables look exactly like the input formats above.
|
256
|
+
|
257
|
+
```sh
|
258
|
+
levels \
|
259
|
+
--output json \
|
260
|
+
--level "Base" \
|
261
|
+
--level "Prod" \
|
262
|
+
--system \
|
263
|
+
base.rb \
|
264
|
+
prod.json
|
265
|
+
```
|
266
|
+
|
267
|
+
**Within a Ruby program**, a `Levels::Configuration` is an object. You
|
268
|
+
can build one with `Levels.merge`.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
# Merge multiple input levels from various sources - file, API and
|
272
|
+
# environment variables.
|
273
|
+
config = Levels.merge do |levels|
|
274
|
+
levels.add "Base", HTTP.get("https://server/config.json")
|
275
|
+
levels.add "Prod", "prod.json"
|
276
|
+
levels.add_system
|
277
|
+
end
|
278
|
+
```
|
279
|
+
|
280
|
+
The resulting `config` object works like this.
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
# Dot syntax.
|
284
|
+
config.server.hostname # => "example.com"
|
285
|
+
config.task_queue.workers # => 5
|
286
|
+
config.task_queue.queues # => ["high", "low"]
|
287
|
+
|
288
|
+
# Hash syntax.
|
289
|
+
config[:server][:hostname] # => "example.com"
|
290
|
+
config[:task_queue][:workers] # => 5
|
291
|
+
config[:task_queue][:queues] # => ["high", "low"]
|
292
|
+
```
|
293
|
+
|
294
|
+
An attempt to read an unknown group or key will throw an exception.
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
config.some_group # raises Levels::UnknownGroup
|
298
|
+
config.server.some_value # raises Levels::UnknownKey
|
299
|
+
```
|
300
|
+
|
301
|
+
You can find out if a group or key exists.
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
config.defined?(:other) # => false
|
305
|
+
config.defined?(:server) # => true
|
306
|
+
config.server.defined?(:other) # => false
|
307
|
+
config.server.defined?(:hostname) # => true
|
308
|
+
```
|
309
|
+
|
310
|
+
## Author
|
311
|
+
|
312
|
+
Ryan Carver / @rcarver
|
313
|
+
|
314
|
+
Copyright (c) Ryan Carver 2012. Made available under the MIT license.
|
315
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
desc "Run all tests"
|
4
|
+
task :default => :testall
|
5
|
+
|
6
|
+
desc "Run all of the tests"
|
7
|
+
task :testall => [:test, :examples]
|
8
|
+
|
9
|
+
desc "Run the unit tests"
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs.push "lib", "test"
|
12
|
+
t.test_files = FileList['test/**/*_test.rb']
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Run the examples"
|
17
|
+
task :examples do
|
18
|
+
Dir["examples/*.sh"].each do |script|
|
19
|
+
sh script
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'bundler'
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
rescue
|
27
|
+
STDERR.puts "bundler is not available"
|
28
|
+
end
|
data/bin/levels
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'levels'
|
4
|
+
require 'optparse'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
# If "--system" is the last flag, and no PREFIX is set, then
|
8
|
+
# the first file argument will be interpreted as the PREFIX.
|
9
|
+
#
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# # This would be interpreted as "one.rb is the system prefix".
|
13
|
+
# --level One --system one.rb
|
14
|
+
# # What we want is
|
15
|
+
# --level One --system '' one.rb
|
16
|
+
#
|
17
|
+
def fix_system_is_last_arg(argv)
|
18
|
+
index = ARGV.index("--system") or return
|
19
|
+
value = argv[index + 1] or return
|
20
|
+
|
21
|
+
unless value =~ /^-/ || value =~ /^[A-Z_]/
|
22
|
+
ARGV.insert(index + 1, "")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
fix_system_is_last_arg(ARGV)
|
27
|
+
|
28
|
+
# By default we'll output JSON
|
29
|
+
@output = true
|
30
|
+
@output_format = "json"
|
31
|
+
|
32
|
+
# Colorize by default.
|
33
|
+
@colorize = true
|
34
|
+
|
35
|
+
# If output is "system", change the prefix.
|
36
|
+
@system_output_prefix = nil
|
37
|
+
|
38
|
+
# Read input from the system.
|
39
|
+
@system = false
|
40
|
+
@system_input_prefix = nil
|
41
|
+
|
42
|
+
# Accumulate the names of levels for file arguments.
|
43
|
+
@level_names = []
|
44
|
+
|
45
|
+
OptionParser.new do |opts|
|
46
|
+
|
47
|
+
opts.banner = "Usage: levels [options] [files]"
|
48
|
+
|
49
|
+
opts.on("-l", "--level [NAME]", "Name the level from a file.") do |n|
|
50
|
+
@level_names << n
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-s", "--system [PREFIX]", "Read the system as a level.") do |p|
|
54
|
+
@system = true
|
55
|
+
@system_input_prefix = p if p && !p.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-o", "--output FORMAT", "The format to output. (json, system)") do |o|
|
59
|
+
@output_format = o
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on("-p", "--prefix PREFIX", "Prefix for system output.") do |p|
|
63
|
+
@system_output_prefix = p
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("-n", "--no-output", "Don't output the result.") do
|
67
|
+
@output = false
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("--[no-]color", "Colorize the output.") do |bool|
|
71
|
+
@colorize = bool
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on("-v", "--version", "Show the levels version.") do
|
75
|
+
STDOUT.puts "Levels #{Levels::VERSION}"
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("-h", "--help", "Show this help.") do
|
79
|
+
STDOUT.puts opts.to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
end.parse!
|
83
|
+
|
84
|
+
# Files are any remaining arguments after parsing.
|
85
|
+
@files = ARGV.dup
|
86
|
+
|
87
|
+
@setup = Levels.setup
|
88
|
+
|
89
|
+
# Read each file into a level.
|
90
|
+
@files.each.with_index do |file, index|
|
91
|
+
pn = Pathname.new(file)
|
92
|
+
level_name = @level_names[index] || pn.basename.to_s
|
93
|
+
STDERR.puts "Add level #{level_name.inspect} from #{pn.basename}"
|
94
|
+
|
95
|
+
@setup.add level_name, file
|
96
|
+
end
|
97
|
+
|
98
|
+
# Read the system using the existing levels as a base, then add it
|
99
|
+
# as another level.
|
100
|
+
if @system
|
101
|
+
level_name = "System Environment"
|
102
|
+
if @system_input_prefix
|
103
|
+
STDERR.puts "Add level #{level_name.inspect} with prefix #{@system_input_prefix}"
|
104
|
+
else
|
105
|
+
STDERR.puts "Add level #{level_name.inspect}"
|
106
|
+
end
|
107
|
+
@setup.add_system @system_input_prefix, level_name, ENV
|
108
|
+
end
|
109
|
+
|
110
|
+
# Write the configuration to stdout.
|
111
|
+
if @output && (@files.any? || @system)
|
112
|
+
configuration = @setup.merge
|
113
|
+
configuration.event_handler = Levels::CliEventHandler.new(STDERR, @colorize)
|
114
|
+
|
115
|
+
output = nil
|
116
|
+
|
117
|
+
case @output_format
|
118
|
+
when "json"
|
119
|
+
output = Levels::Output::JSON.new
|
120
|
+
when "yaml"
|
121
|
+
output = Levels::Output::YAML.new
|
122
|
+
when "system"
|
123
|
+
key_formatter = Levels::System::KeyFormatter.new(@system_output_prefix)
|
124
|
+
output = Levels::Output::System.new(key_formatter)
|
125
|
+
end
|
126
|
+
|
127
|
+
if output
|
128
|
+
STDOUT.puts output.generate(configuration.to_enum)
|
129
|
+
end
|
130
|
+
end
|