qb 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.
- checksums.yaml +7 -0
- data/.gitignore +146 -0
- data/.qb-options.yml +4 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +4 -0
- data/Rakefile +6 -0
- data/ansible.cfg +5 -0
- data/bin/console +14 -0
- data/bin/qb +287 -0
- data/bin/setup +9 -0
- data/dev/bin/.gitkeep +0 -0
- data/dev/bin/ungem +19 -0
- data/dev/setup.yml +58 -0
- data/lib/qb.rb +5 -0
- data/lib/qb/version.rb +3 -0
- data/library/git_mkdir.py +70 -0
- data/library/qb_facts.py +38 -0
- data/qb.gemspec +27 -0
- data/requirements.yml +2 -0
- data/roles/qb.gem/.qb-options.yml +4 -0
- data/roles/qb.gem/defaults/main.yml +17 -0
- data/roles/qb.gem/files/.gitkeep +0 -0
- data/roles/qb.gem/files/.rspec +2 -0
- data/roles/qb.gem/files/Rakefile +6 -0
- data/roles/qb.gem/files/setup +7 -0
- data/roles/qb.gem/filter_plugins/ruby_constantize.py +22 -0
- data/roles/qb.gem/meta/main.yml +35 -0
- data/roles/qb.gem/tasks/main.yml +105 -0
- data/roles/qb.gem/templates/.gitkeep +0 -0
- data/roles/qb.gem/templates/.travis.yml.j2 +4 -0
- data/roles/qb.gem/templates/BSD2-LICENSE.txt.j2 +23 -0
- data/roles/qb.gem/templates/BSD3-LICENSE.txt.j2 +27 -0
- data/roles/qb.gem/templates/Gemfile.j2 +4 -0
- data/roles/qb.gem/templates/MIT-LICENSE.txt.j2 +21 -0
- data/roles/qb.gem/templates/console.j2 +14 -0
- data/roles/qb.gem/templates/gemspec.j2 +36 -0
- data/roles/qb.gem/templates/module.rb.j2 +5 -0
- data/roles/qb.gem/templates/spec.rb.j2 +11 -0
- data/roles/qb.gem/templates/spec_helper.rb.j2 +2 -0
- data/roles/qb.gem/templates/version.rb.j2 +3 -0
- data/roles/qb.git_repo/defaults/main.yml +2 -0
- data/roles/qb.git_repo/meta/main.yml +5 -0
- data/roles/qb.git_repo/tasks/main.yml +9 -0
- data/roles/qb.gitignore/defaults/main.yml +4 -0
- data/roles/qb.gitignore/meta/main.yml +12 -0
- data/roles/qb.gitignore/tasks/main.yml +31 -0
- data/roles/qb.project/.qb-options.yml +3 -0
- data/roles/qb.project/defaults/main.yml +12 -0
- data/roles/qb.project/meta/main.yml +49 -0
- data/roles/qb.project/qb/get_dir +13 -0
- data/roles/qb.project/tasks/main.yml +102 -0
- data/roles/qb.project/templates/.gitkeep +0 -0
- data/roles/qb.project/templates/README.md.j2 +2 -0
- data/roles/qb.project/templates/setup.yml.j2 +52 -0
- data/roles/qb.role/.qb-options.yml +3 -0
- data/roles/qb.role/defaults/main.yml +9 -0
- data/roles/qb.role/meta/main.yml +31 -0
- data/roles/qb.role/tasks/main.yml +117 -0
- data/roles/qb.role/templates/.gitkeep +0 -0
- data/roles/qb.role/templates/defaults/main.yml.j2 +2 -0
- data/roles/qb.role/templates/handlers/main.yml.j2 +2 -0
- data/roles/qb.role/templates/meta/main.yml.j2 +5 -0
- data/roles/qb.role/templates/tasks/main.yml.j2 +2 -0
- data/roles/qb.role/templates/vars/main.yml.j2 +2 -0
- data/scratch/case.rb +38 -0
- data/temp.yml +19 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed9e572283f9de3975f9d285ebf382ca92b81266
|
4
|
+
data.tar.gz: bc7d66d6988545c060af3b213edf6c2b1c3c0f8f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 02102a5039d84ade5dd8984da6fbbeaf9ec4bd87fae8976b7e5a2095c21bd520531ba76935991aec4940e7f905420dffb874b69288e94bb79a9e8979ce5da0fe
|
7
|
+
data.tar.gz: ae6e942153d39b7f2ff8efdd17902ef6bc2a6d2abdf747a617d53fecae2d6cdea51bc3e31ff8b712fdbacac0e75cae02e6b29432a2411e33946e8961d7203f0f
|
data/.gitignore
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# temp playbook created by qb for ansible-playbook to consume
|
2
|
+
/.qb-playbook.yml
|
3
|
+
/dev/repos
|
4
|
+
/dev/ref/repos
|
5
|
+
/tmp
|
6
|
+
# BEGIN Global/OSX.gitignore
|
7
|
+
.DS_Store
|
8
|
+
.AppleDouble
|
9
|
+
.LSOverride
|
10
|
+
|
11
|
+
# Icon must end with two
|
12
|
+
Icon
|
13
|
+
|
14
|
+
|
15
|
+
# Thumbnails
|
16
|
+
._*
|
17
|
+
|
18
|
+
# Files that might appear in the root of a volume
|
19
|
+
.DocumentRevisions-V100
|
20
|
+
.fseventsd
|
21
|
+
.Spotlight-V100
|
22
|
+
.TemporaryItems
|
23
|
+
.Trashes
|
24
|
+
.VolumeIcon.icns
|
25
|
+
|
26
|
+
# Directories potentially created on remote AFP share
|
27
|
+
.AppleDB
|
28
|
+
.AppleDesktop
|
29
|
+
Network Trash Folder
|
30
|
+
Temporary Items
|
31
|
+
.apdisk
|
32
|
+
# END Global/OSX.gitignore
|
33
|
+
# BEGIN Ruby.gitignore
|
34
|
+
/*.gem
|
35
|
+
*.rbc
|
36
|
+
/.config
|
37
|
+
/coverage/
|
38
|
+
/InstalledFiles
|
39
|
+
/pkg/
|
40
|
+
/spec/reports/
|
41
|
+
/spec/examples.txt
|
42
|
+
/test/tmp/
|
43
|
+
/test/version_tmp/
|
44
|
+
/tmp/
|
45
|
+
|
46
|
+
## Specific to RubyMotion:
|
47
|
+
.dat*
|
48
|
+
.repl_history
|
49
|
+
build/
|
50
|
+
|
51
|
+
## Documentation cache and generated files:
|
52
|
+
/.yardoc/
|
53
|
+
/_yardoc/
|
54
|
+
/doc/
|
55
|
+
/rdoc/
|
56
|
+
|
57
|
+
## Environment normalization:
|
58
|
+
/.bundle/
|
59
|
+
/vendor/bundle
|
60
|
+
/lib/bundler/man/
|
61
|
+
|
62
|
+
# for a library or gem, you might want to ignore these files since the code is
|
63
|
+
# intended to run in multiple environments; otherwise, check them in:
|
64
|
+
# Gemfile.lock
|
65
|
+
# .ruby-version
|
66
|
+
# .ruby-gemset
|
67
|
+
|
68
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
69
|
+
.rvmrc
|
70
|
+
# END Ruby.gitignore
|
71
|
+
# BEGIN Gem.gitignore
|
72
|
+
/Gemfile.lock
|
73
|
+
/.ruby-version
|
74
|
+
/.ruby-gemset
|
75
|
+
# END Gem.gitignore
|
76
|
+
# BEGIN Python.gitignore
|
77
|
+
# Byte-compiled / optimized / DLL files
|
78
|
+
__pycache__/
|
79
|
+
*.py[cod]
|
80
|
+
*$py.class
|
81
|
+
|
82
|
+
# C extensions
|
83
|
+
*.so
|
84
|
+
|
85
|
+
# Distribution / packaging
|
86
|
+
.Python
|
87
|
+
env/
|
88
|
+
build/
|
89
|
+
develop-eggs/
|
90
|
+
dist/
|
91
|
+
downloads/
|
92
|
+
eggs/
|
93
|
+
.eggs/
|
94
|
+
|
95
|
+
# ruby uses this!
|
96
|
+
# lib/
|
97
|
+
|
98
|
+
lib64/
|
99
|
+
parts/
|
100
|
+
sdist/
|
101
|
+
var/
|
102
|
+
*.egg-info/
|
103
|
+
.installed.cfg
|
104
|
+
*.egg
|
105
|
+
|
106
|
+
# PyInstaller
|
107
|
+
# Usually these files are written by a python script from a template
|
108
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
109
|
+
*.manifest
|
110
|
+
*.spec
|
111
|
+
|
112
|
+
# Installer logs
|
113
|
+
pip-log.txt
|
114
|
+
pip-delete-this-directory.txt
|
115
|
+
|
116
|
+
# Unit test / coverage reports
|
117
|
+
htmlcov/
|
118
|
+
.tox/
|
119
|
+
.coverage
|
120
|
+
.coverage.*
|
121
|
+
.cache
|
122
|
+
nosetests.xml
|
123
|
+
coverage.xml
|
124
|
+
*,cover
|
125
|
+
.hypothesis/
|
126
|
+
|
127
|
+
# Translations
|
128
|
+
*.mo
|
129
|
+
*.pot
|
130
|
+
|
131
|
+
# Django stuff:
|
132
|
+
*.log
|
133
|
+
local_settings.py
|
134
|
+
|
135
|
+
# Sphinx documentation
|
136
|
+
docs/_build/
|
137
|
+
|
138
|
+
# PyBuilder
|
139
|
+
target/
|
140
|
+
|
141
|
+
#Ipython Notebook
|
142
|
+
.ipynb_checkpoints
|
143
|
+
|
144
|
+
# pyenv
|
145
|
+
.python-version
|
146
|
+
# END Python.gitignore
|
data/.qb-options.yml
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 nrser
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/ansible.cfg
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "qb"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/qb
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'pp'
|
5
|
+
require 'yaml'
|
6
|
+
require 'optparse'
|
7
|
+
require 'json'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
require 'cmds'
|
11
|
+
|
12
|
+
# constants
|
13
|
+
# =========
|
14
|
+
|
15
|
+
ROOT = (Pathname.new(__FILE__).dirname + '..').expand_path
|
16
|
+
ROLES_DIR = ROOT + 'roles'
|
17
|
+
ROLES = Pathname.glob(ROLES_DIR + 'qb.*').map {|path| path.basename.to_s}
|
18
|
+
DEBUG_ARGS = ['-d', '--debug']
|
19
|
+
|
20
|
+
# globals
|
21
|
+
# =======
|
22
|
+
|
23
|
+
$debug = false
|
24
|
+
|
25
|
+
# @api util
|
26
|
+
# *pure*
|
27
|
+
#
|
28
|
+
# format a debug message with optional key / values to print
|
29
|
+
#
|
30
|
+
# @param msg [String] message to print.
|
31
|
+
# @param dump [Hash] optional hash of keys and vaues to dump.
|
32
|
+
def format msg, dump = {}
|
33
|
+
unless dump.empty?
|
34
|
+
msg += "\n" + dump.map {|k, v| " #{ k }: #{ v.inspect }" }.join("\n")
|
35
|
+
end
|
36
|
+
msg
|
37
|
+
end
|
38
|
+
|
39
|
+
def debug *args
|
40
|
+
return unless $debug
|
41
|
+
|
42
|
+
msg, values = case args.length
|
43
|
+
when 0
|
44
|
+
raise ArgumentError, "debug needs at least one argument"
|
45
|
+
when 1
|
46
|
+
if args[0].is_a? Hash
|
47
|
+
['', args[0]]
|
48
|
+
else
|
49
|
+
[args[0], {}]
|
50
|
+
end
|
51
|
+
when 2
|
52
|
+
[args[0], args[1]]
|
53
|
+
else
|
54
|
+
raise ArgumentError, "debug needs at least one argument"
|
55
|
+
end
|
56
|
+
|
57
|
+
$stderr.puts("DEBUG " + format(msg, values))
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_debug! args
|
61
|
+
if DEBUG_ARGS.any? {|arg| args.include? arg}
|
62
|
+
$debug = true
|
63
|
+
debug "ON"
|
64
|
+
DEBUG_ARGS.each {|arg| args.delete arg}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse! role_arg, var_prefix, vars, defaults, args
|
69
|
+
positional = vars.select do |var|
|
70
|
+
var['positional'] == true
|
71
|
+
end
|
72
|
+
|
73
|
+
positional_banner = if positional.empty?
|
74
|
+
''
|
75
|
+
else
|
76
|
+
' ' + positional.map {|var|
|
77
|
+
var['name'].upcase
|
78
|
+
}.join(' ')
|
79
|
+
end
|
80
|
+
|
81
|
+
options = {}
|
82
|
+
|
83
|
+
opt_parser = OptionParser.new do |opts|
|
84
|
+
opts.banner = "qb #{ role_arg } [OPTIONS]#{ positional_banner }"
|
85
|
+
|
86
|
+
vars.each do |var|
|
87
|
+
arg_name = var.fetch 'name'
|
88
|
+
var_name = "#{ var_prefix }_#{ arg_name }"
|
89
|
+
required = var['required'] || false
|
90
|
+
arg_style = required ? :REQUIRED : :OPTIONAL
|
91
|
+
|
92
|
+
# on_args = [arg_style]
|
93
|
+
on_args = []
|
94
|
+
|
95
|
+
if var['type'] == 'boolean'
|
96
|
+
if var['short']
|
97
|
+
on_args << "-#{ var['short'] }"
|
98
|
+
end
|
99
|
+
|
100
|
+
on_args << "--[no-]#{ var['name'] }"
|
101
|
+
|
102
|
+
else
|
103
|
+
ruby_type = case var['type']
|
104
|
+
when 'string'
|
105
|
+
String
|
106
|
+
else
|
107
|
+
raise ArgumentError, "bad type: #{ var['type'].inspect }"
|
108
|
+
end
|
109
|
+
|
110
|
+
if var['short']
|
111
|
+
on_args << "-#{ var['short'] } #{ arg_name.upcase }"
|
112
|
+
end
|
113
|
+
|
114
|
+
on_args << "--#{ var['name'] }=#{ arg_name.upcase }"
|
115
|
+
|
116
|
+
on_args << ruby_type
|
117
|
+
end
|
118
|
+
|
119
|
+
# description
|
120
|
+
if var.key? 'description'
|
121
|
+
on_args << var['description']
|
122
|
+
else
|
123
|
+
on_args << "set the #{ var_name } variable"
|
124
|
+
end
|
125
|
+
|
126
|
+
if defaults.key? var_name
|
127
|
+
on_args << "(defaults to #{ defaults[var_name] })"
|
128
|
+
end
|
129
|
+
|
130
|
+
debug "adding option", name: arg_name, on_args: on_args
|
131
|
+
|
132
|
+
opts.on(*on_args) do |value|
|
133
|
+
options[var['name']] = value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# No argument, shows at tail. This will print an options summary.
|
138
|
+
# Try it and see!
|
139
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
140
|
+
puts opts
|
141
|
+
exit
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
opt_parser.parse! args
|
146
|
+
|
147
|
+
# args.each_with_index do |value, index|
|
148
|
+
# var_name = positional[index]['name']
|
149
|
+
# if options.key? var_name
|
150
|
+
# raise ArgumentError, "don't supply #{ var_name } as option and positionaly"
|
151
|
+
# else
|
152
|
+
# options[var_name] = value
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
|
156
|
+
options
|
157
|
+
end
|
158
|
+
|
159
|
+
def main args
|
160
|
+
set_debug! args
|
161
|
+
debug args: args
|
162
|
+
|
163
|
+
role_arg = args.shift
|
164
|
+
debug "role arg" => role_arg
|
165
|
+
|
166
|
+
matches = ROLES.select {|role|
|
167
|
+
role.include? role_arg
|
168
|
+
}
|
169
|
+
debug "role matches" => matches
|
170
|
+
|
171
|
+
role = case matches.length
|
172
|
+
when 0
|
173
|
+
raise ArgumentError, "no roles match arg #{ role_arg.inspect }"
|
174
|
+
when 1
|
175
|
+
matches[0]
|
176
|
+
else
|
177
|
+
raise ArgumentError, "multiple role matches: #{ matches.inpsect }"
|
178
|
+
end
|
179
|
+
debug role: role
|
180
|
+
|
181
|
+
role_dir = ROLES_DIR + role
|
182
|
+
|
183
|
+
defaults = YAML.load (role_dir + 'defaults' + 'main.yml').read
|
184
|
+
meta = YAML.load (role_dir + 'meta' + 'main.yml').read
|
185
|
+
|
186
|
+
qb_info = meta['qb_info'] || {}
|
187
|
+
vars = qb_info['vars'] || []
|
188
|
+
var_prefix = qb_info['var_prefix'] || role.split('.').last
|
189
|
+
|
190
|
+
options = parse! role_arg, var_prefix, vars, defaults, args
|
191
|
+
|
192
|
+
debug options: options
|
193
|
+
|
194
|
+
# get the target dir
|
195
|
+
dir = case args.length
|
196
|
+
when 0
|
197
|
+
# in this case, a dir has not been provided
|
198
|
+
#
|
199
|
+
# in some cases (like projects) the dir can be figured out from other
|
200
|
+
# variables. i created a hacky-ass way of dealing with this:
|
201
|
+
#
|
202
|
+
# when the sole positional arg is missing, we look for a `qb/get_dir`
|
203
|
+
# executable in the role. if it exists, call it with the JSON encoded
|
204
|
+
# options passed over STDIN. if the execuable succeeds, the result is
|
205
|
+
# taken as dir.
|
206
|
+
#
|
207
|
+
get_dir_path = role_dir + 'qb' + 'get_dir'
|
208
|
+
|
209
|
+
unless get_dir_path.exist?
|
210
|
+
raise "no dir argument provided and no qb/get_dir exe found"
|
211
|
+
end
|
212
|
+
|
213
|
+
Cmds.chomp! get_dir_path.to_s do
|
214
|
+
JSON.dump options
|
215
|
+
end
|
216
|
+
|
217
|
+
when 1
|
218
|
+
# there is a single positional arg, which is used as dir
|
219
|
+
args[0]
|
220
|
+
|
221
|
+
else
|
222
|
+
# there are multiple positional args, which is not allowed
|
223
|
+
raise "can't supply more than one argument"
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
debug input_dir: dir
|
228
|
+
|
229
|
+
# normalize to expanded path (has no trailing slash)
|
230
|
+
dir = File.expand_path dir
|
231
|
+
|
232
|
+
debug normalized_dir: dir
|
233
|
+
|
234
|
+
# create the dir if it doesn't exist (so don't have to cover this in
|
235
|
+
# every role)
|
236
|
+
FileUtils.mkdir_p dir unless File.exists? dir
|
237
|
+
|
238
|
+
saved_options_path = Pathname.new(dir) + '.qb-options.yml'
|
239
|
+
|
240
|
+
saved_options = if saved_options_path.exist?
|
241
|
+
YAML.load saved_options_path.read
|
242
|
+
else
|
243
|
+
{}
|
244
|
+
end
|
245
|
+
|
246
|
+
if saved_options.key? role
|
247
|
+
options = saved_options[role].merge options
|
248
|
+
end
|
249
|
+
|
250
|
+
playbook_role = {'role' => role}
|
251
|
+
options.each do |arg_name, arg_value|
|
252
|
+
playbook_role["#{ var_prefix }_#{ arg_name }"] = arg_value
|
253
|
+
end
|
254
|
+
|
255
|
+
playbook_role['dir'] = dir
|
256
|
+
|
257
|
+
playbook = [
|
258
|
+
{
|
259
|
+
'hosts' => 'localhost',
|
260
|
+
'pre_tasks' => [
|
261
|
+
{'qb_facts' => nil},
|
262
|
+
],
|
263
|
+
'roles' => [
|
264
|
+
'yaegashi.blockinfile',
|
265
|
+
playbook_role
|
266
|
+
],
|
267
|
+
}
|
268
|
+
]
|
269
|
+
|
270
|
+
debug playbook: playbook
|
271
|
+
|
272
|
+
File.open './.qb-playbook.yml', 'w' do |f|
|
273
|
+
f.write YAML.dump(playbook)
|
274
|
+
end
|
275
|
+
|
276
|
+
unless options.empty?
|
277
|
+
saved_options[role] = options
|
278
|
+
FileUtils.mkdir_p saved_options_path.dirname unless saved_options_path.dirname.exist?
|
279
|
+
saved_options_path.open('w') do |f|
|
280
|
+
f.write YAML.dump(saved_options)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
exec "ansible-playbook ./.qb-playbook.yml"
|
285
|
+
end
|
286
|
+
|
287
|
+
main(ARGV) if __FILE__ == $0
|