burner 0.0.0 → 1.0.0.pre.alpha.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/README.md +390 -2
  4. data/burner.gemspec +4 -1
  5. data/exe/burner +21 -0
  6. data/lib/burner.rb +19 -4
  7. data/lib/burner/cli.rb +45 -0
  8. data/lib/burner/job.rb +37 -0
  9. data/lib/burner/jobs.rb +58 -0
  10. data/lib/burner/jobs/collection/arrays_to_objects.rb +43 -0
  11. data/lib/burner/jobs/collection/graph.rb +43 -0
  12. data/lib/burner/jobs/collection/objects_to_arrays.rb +54 -0
  13. data/lib/burner/jobs/collection/shift.rb +43 -0
  14. data/lib/burner/jobs/collection/transform.rb +64 -0
  15. data/lib/burner/jobs/collection/transform/attribute.rb +33 -0
  16. data/lib/burner/jobs/collection/transform/attribute_renderer.rb +36 -0
  17. data/lib/burner/jobs/collection/unpivot.rb +45 -0
  18. data/lib/burner/jobs/deserialize/csv.rb +28 -0
  19. data/lib/burner/jobs/deserialize/json.rb +23 -0
  20. data/lib/burner/jobs/deserialize/yaml.rb +41 -0
  21. data/lib/burner/jobs/dummy.rb +19 -0
  22. data/lib/burner/jobs/echo.rb +33 -0
  23. data/lib/burner/jobs/io/base.rb +27 -0
  24. data/lib/burner/jobs/io/exist.rb +43 -0
  25. data/lib/burner/jobs/io/read.rb +45 -0
  26. data/lib/burner/jobs/io/write.rb +67 -0
  27. data/lib/burner/jobs/serialize/csv.rb +38 -0
  28. data/lib/burner/jobs/serialize/json.rb +23 -0
  29. data/lib/burner/jobs/serialize/yaml.rb +23 -0
  30. data/lib/burner/jobs/set.rb +31 -0
  31. data/lib/burner/jobs/sleep.rb +33 -0
  32. data/lib/burner/modeling.rb +10 -0
  33. data/lib/burner/modeling/key_index_mapping.rb +29 -0
  34. data/lib/burner/output.rb +66 -0
  35. data/lib/burner/payload.rb +41 -0
  36. data/lib/burner/pipeline.rb +73 -0
  37. data/lib/burner/step.rb +45 -0
  38. data/lib/burner/string_template.rb +40 -0
  39. data/lib/burner/version.rb +1 -1
  40. data/lib/burner/written_file.rb +28 -0
  41. metadata +82 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c01fa95eaea68e4f0e84d616dcd22ecc3ffe4b415605ba70dc3484a6ed72868f
4
- data.tar.gz: 3e73452a5757d1cdb7c4a95aee1c4aebbb987f5f97bc1060d45eb18b29d77e5c
3
+ metadata.gz: 6e96a8d656466219851ff06ebced5aacac54fe0b6a78de2e1f0e5bf76480e1ca
4
+ data.tar.gz: a4ba7e8d001f7e25ee7c404ed539089e51f295098d0062d14a3fcf473db64d7d
5
5
  SHA512:
6
- metadata.gz: f6b9e1d10e4415d5cce06f0a0ccd0797df5e95a1ff53c5beb522e704cec8c11af2f8f48d57a2fb2194a0624f870885d39b4d5777099fcf1c1a9ee2edda8843ab
7
- data.tar.gz: e714ddeb82888412ab78167dcfa4f63f0ef4e391f7d9b3dee88b06a920a3cd6916186e2e4639dd4e85d977a7816a9025bcd6f4dcd56f6ff1ce19c2a5961d44a7
6
+ metadata.gz: 1cc60f86ee2931902ab8e6870f5bda78f953e6740c2a5d76dc306c12457e93511646b9cbfe4db8fa583a3242bc1d361715011ea98ea5682045356a6c6f269808
7
+ data.tar.gz: 5b9cf06f4426d1629f9c6672ee5c469052863e07bf01079b021494f5b6e0bc4ae0ef57be64e1a37d1652803039a1e11061c5415ca4a83f39672d4c55085feeac
data/.gitignore CHANGED
@@ -4,6 +4,3 @@
4
4
  /coverage
5
5
  Gemfile.lock
6
6
  /pkg
7
-
8
- /spec/config/database.yaml
9
- /spec/db
data/README.md CHANGED
@@ -1,5 +1,393 @@
1
1
  # Burner
2
2
 
3
- ---
3
+ [![Gem Version](https://badge.fury.io/rb/burner.svg)](https://badge.fury.io/rb/burner) [![Build Status](https://travis-ci.org/bluemarblepayroll/burner.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/burner) [![Maintainability](https://api.codeclimate.com/v1/badges/dbc3757929b67504f6ca/maintainability)](https://codeclimate.com/github/bluemarblepayroll/burner/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/dbc3757929b67504f6ca/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/burner/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- Under construction.
5
+ This library serves as the skeleton for a processing engine. It allows you to organize your code into Jobs, then stitch those jobs together as steps.
6
+
7
+ ## Installation
8
+
9
+ To install through Rubygems:
10
+
11
+ ````bash
12
+ gem install burner
13
+ ````
14
+
15
+ You can also add this to your Gemfile:
16
+
17
+ ````bash
18
+ bundle add burner
19
+ ````
20
+
21
+ ## Examples
22
+
23
+ The purpose of this library is to provide a framework for creating highly de-coupled functions (known as jobs), and then allow for the stitching of them back together in any arbitrary order (know as steps.) Although our example will be somewhat specific and contrived, the only limit to what the jobs and order of jobs are is up to your imagination.
24
+
25
+ ### JSON-to-YAML File Converter
26
+
27
+ All the jobs for this example are shipped with this library. In this example, we will write a pipeline that can read a JSON file and convert it to YAML. Pipelines are data-first so we can represent a pipeline using a hash:
28
+
29
+ ````ruby
30
+ pipeline = {
31
+ jobs: [
32
+ {
33
+ name: :read,
34
+ type: 'io/read',
35
+ path: '{input_file}'
36
+ },
37
+ {
38
+ name: :output_id,
39
+ type: :echo,
40
+ message: 'The job id is: {__id}'
41
+ },
42
+ {
43
+ name: :output_value,
44
+ type: :echo,
45
+ message: 'The current value is: {__value}'
46
+ },
47
+ {
48
+ name: :parse,
49
+ type: 'deserialize/json'
50
+ },
51
+ {
52
+ name: :convert,
53
+ type: 'serialize/yaml'
54
+ },
55
+ {
56
+ name: :write,
57
+ type: 'io/write',
58
+ path: '{output_file}'
59
+ }
60
+ ],
61
+ steps: %i[
62
+ read
63
+ output_id
64
+ output_value
65
+ parse
66
+ convert
67
+ output_value
68
+ write
69
+ ]
70
+ }
71
+
72
+ params = {
73
+ input_file: 'input.json',
74
+ output_file: 'output.yaml'
75
+ }
76
+
77
+ payload = Burner::Payload.new(params: params)
78
+ ````
79
+
80
+ Assuming we are running this script from a directory where an `input.json` file exists, we can then programatically process the pipeline:
81
+
82
+ ````ruby
83
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
84
+ ````
85
+
86
+ We should now see a output.yaml file created.
87
+
88
+ Some notes:
89
+
90
+ * Some values are able to be string-interpolated using the provided Payload#params. This allows for the passing runtime configuration/data into pipelines/jobs.
91
+ * The job's ID can be accessed using the `__id` key.
92
+ * The current job's payload value can be accessed using the `__value` key.
93
+ * Jobs can be re-used (just like the output_id and output_value jobs).
94
+
95
+ ### Capturing Feedback / Output
96
+
97
+ By default, output will be emitted to `$stdout`. You can add or change listeners by passing in optional values into Pipeline#execute. For example, say we wanted to capture the output from our json-to-yaml example:
98
+
99
+ ````ruby
100
+ class StringOut
101
+ def initialize
102
+ @io = StringIO.new
103
+ end
104
+
105
+ def puts(msg)
106
+ tap { io.write("#{msg}\n") }
107
+ end
108
+
109
+ def read
110
+ io.rewind
111
+ io.read
112
+ end
113
+
114
+ private
115
+
116
+ attr_reader :io
117
+ end
118
+
119
+ string_out = StringOut.new
120
+ output = Burner::Output.new(outs: string_out)
121
+ payload = Burner::Payload.new(params: params)
122
+
123
+ Burner::Pipeline.make(pipeline).execute(output: output, payload: payload)
124
+
125
+ log = string_out.read
126
+ ````
127
+
128
+ The value of `log` should now look similar to:
129
+
130
+ ````bash
131
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] Pipeline started with 7 step(s)
132
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] Parameters:
133
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - input_file: input.json
134
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - output_file: output.yaml
135
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] --------------------------------------------------------------------------------
136
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [1] Burner::Jobs::IO::Read::read
137
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Reading: spec/fixtures/input.json
138
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
139
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [2] Burner::Jobs::Echo::output_id
140
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - The job id is:
141
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
142
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [3] Burner::Jobs::Echo::output_value
143
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - The current value is:
144
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
145
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [4] Burner::Jobs::Deserialize::Json::parse
146
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
147
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [5] Burner::Jobs::Serialize::Yaml::convert
148
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
149
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [6] Burner::Jobs::Echo::output_value
150
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - The current value is:
151
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
152
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] [7] Burner::Jobs::IO::Write::write
153
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Writing: output.yaml
154
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] - Completed in: 0.0 second(s)
155
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] --------------------------------------------------------------------------------
156
+ [8bdc394e-7047-4a1a-87ed-6c54ed690ed5 | 2020-10-14 13:49:59 UTC] Pipeline ended, took 0.001 second(s) to complete
157
+ ````
158
+
159
+ Notes:
160
+
161
+ * The Job ID is specified as the leading UUID in each line.
162
+ * `outs` can be provided an array of listeners, as long as each listener responds to `puts(msg)`.
163
+
164
+ ### Command Line Pipeline Processing
165
+
166
+ This library also ships with a built-in script `exe/burner` that illustrates using the `Burner::Cli` API. This class can take in an array of arguments (similar to a command-line) and execute a pipeline. The first argument is the path to a YAML file with the pipeline's configuration and each subsequent argument is a param in `key=value` form. Here is how the json-to-yaml example can utilize this interface:
167
+
168
+ #### Create YAML Pipeline Configuration File
169
+
170
+ Write the following json_to_yaml_pipeline.yaml file to disk:
171
+
172
+ ````yaml
173
+ jobs:
174
+ - name: read
175
+ type: io/read
176
+ path: '{input_file}'
177
+
178
+ - name: output_id
179
+ type: echo
180
+ message: 'The job id is: {__id}'
181
+
182
+ - name: output_value
183
+ type: echo
184
+ message: 'The current value is: {__value}'
185
+
186
+ - name: parse
187
+ type: deserialize/json
188
+
189
+ - name: convert
190
+ type: serialize/yaml
191
+
192
+ - name: write
193
+ type: io/write
194
+ path: '{output_file}'
195
+
196
+ steps:
197
+ - read
198
+ - output_id
199
+ - output_value
200
+ - parse
201
+ - convert
202
+ - output_value
203
+ - write
204
+ ````
205
+
206
+ #### Run Using Script
207
+
208
+ From the command-line, run:
209
+
210
+ ````bash
211
+ bundle exec burner json_to_yaml_pipeline.yaml input_file=input.json output_file=output.yaml
212
+ ````
213
+
214
+ The pipeline should be processed and output.yaml should be created.
215
+
216
+ #### Run Using Programmatic API
217
+
218
+ Instead of the script, you can invoke it using code:
219
+
220
+ ````ruby
221
+ args = %w[
222
+ json_to_yaml_pipeline.yaml
223
+ input_file=input.json
224
+ output_file=output.yaml
225
+ ]
226
+
227
+ Burner::Cli.new(args).invoke
228
+ ````
229
+
230
+ ### Core Job Library
231
+
232
+ This library only ships with very basic, rudimentary jobs that are meant to just serve as a baseline:
233
+
234
+ #### Collection
235
+
236
+ * **collection/arrays_to_objects** [mappings]: Convert an array of arrays to an array of objects.
237
+ * **collection/graph** [config, key]: Use (Hashematics)[https://github.com/bluemarblepayroll/hashematics] to turn a flat array of objects into a deeply nested object tree.
238
+ * **collection/objects_to_arrays** [mappings]: Convert an array of objects to an array of arrays.
239
+ * **collection/shift** [amount]: Remove the first N number of elements from an array.
240
+ * **collection/transform** [attributes, exclusive, separator]: Iterate over all objects and transform each key per the attribute transformers specifications. If exclusive is set to false then the current object will be overridden/merged. Separator can also be set for key path support. This job uses (Realize)[https://github.com/bluemarblepayroll/realize], which provides its own extendable value-transformation pipeline.
241
+ * **collection/unpivot** [pivot_set]: Take an array of objects and unpivot specific sets of keys into rows. Under the hood it uses [HashMath's Unpivot class](https://github.com/bluemarblepayroll/hash_math#unpivot-hash-key-coalescence-and-row-extrapolation).
242
+
243
+ #### De-serialization
244
+
245
+ * **deserialize/csv** []: Take a CSV string and de-serialize into object(s). Currently it will return an array of arrays, with each nested array representing one row.
246
+ * **deserialize/json** []: Treat input as a string and de-serialize it to JSON.
247
+ * **deserialize/yaml** [safe]: Treat input as a string and de-serialize it to YAML. By default it will try and (safely de-serialize)[https://ruby-doc.org/stdlib-2.6.1/libdoc/psych/rdoc/Psych.html#method-c-safe_load] it (only using core classes). If you wish to de-serialize it to any class type, pass in `safe: false`
248
+
249
+ #### IO
250
+
251
+ * **io/exist** [path, short_circuit]: Check to see if a file exists. The path parameter can be interpolated using `Payload#params`. If short_circuit was set to true (defaults to false) and the file does not exist then the pipeline will be short-circuited.
252
+ * **io/read** [binary, path]: Read in a local file. The path parameter can be interpolated using `Payload#params`. If the contents are binary, pass in `binary: true` to open it up in binary+read mode.
253
+ * **io/write** [binary, path]: Write to a local file. The path parameter can be interpolated using `Payload#params`. If the contents are binary, pass in `binary: true` to open it up in binary+write mode.
254
+
255
+ #### Serialization
256
+
257
+ * **serialize/csv** []: Take an array of arrays and create a CSV.
258
+ * **serialize/json** []: Convert value to JSON.
259
+ * **serialize/yaml** []: Convert value to YAML.
260
+
261
+ #### General
262
+
263
+ * **dummy** []: Do nothing
264
+ * **echo** [message]: Write a message to the output. The message parameter can be interpolated using `Payload#params`.
265
+ * **set** [value]: Set the value to any arbitrary value.
266
+ * **sleep** [seconds]: Sleep the thread for X number of seconds.
267
+
268
+
269
+ ### Adding & Registering Jobs
270
+
271
+ Where this library shines is when additional jobs are plugged in. Burner uses its `Burner::Jobs` class as its class-level registry built with (acts_as_hashable)[https://github.com/bluemarblepayroll/acts_as_hashable]'s acts_as_hashable_factory directive.
272
+
273
+ Let's say we would like to register a job to parse a CSV:
274
+
275
+ ````ruby
276
+ class ParseCsv < Burner::Job
277
+ def perform(output, payload)
278
+ payload.value = CSV.parse(payload.value, headers: true).map(&:to_h)
279
+
280
+ nil
281
+ end
282
+ end
283
+
284
+ Burner::Jobs.register('parse_csv', ParseCsv)
285
+ ````
286
+
287
+ `parse_csv` is now recognized as a valid job and we can use it:
288
+
289
+ ````ruby
290
+ pipeline = {
291
+ jobs: [
292
+ {
293
+ name: :read,
294
+ type: 'io/read',
295
+ path: '{input_file}'
296
+ },
297
+ {
298
+ name: :output_id,
299
+ type: :echo,
300
+ message: 'The job id is: {__id}'
301
+ },
302
+ {
303
+ name: :output_value,
304
+ type: :echo,
305
+ message: 'The current value is: {__value}'
306
+ },
307
+ {
308
+ name: :parse,
309
+ type: :parse_csv
310
+ },
311
+ {
312
+ name: :convert,
313
+ type: 'serialize/yaml'
314
+ },
315
+ {
316
+ name: :write,
317
+ type: 'io/write',
318
+ path: '{output_file}'
319
+ }
320
+ ],
321
+ steps: %i[
322
+ read
323
+ output_id
324
+ output_value
325
+ parse
326
+ convert
327
+ output_value
328
+ write
329
+ ]
330
+ }
331
+
332
+ params = {
333
+ input_file: File.join('spec', 'fixtures', 'cars.csv'),
334
+ output_file: File.join(TEMP_DIR, "#{SecureRandom.uuid}.yaml")
335
+ }
336
+
337
+ payload = Burner::Payload.new(params: params)
338
+
339
+ Burner::Pipeline.make(pipeline).execute(output: output, payload: payload)
340
+ ````
341
+
342
+ ## Contributing
343
+
344
+ ### Development Environment Configuration
345
+
346
+ Basic steps to take to get this repository compiling:
347
+
348
+ 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check burner.gemspec for versions supported)
349
+ 2. Install bundler (gem install bundler)
350
+ 3. Clone the repository (git clone git@github.com:bluemarblepayroll/burner.git)
351
+ 4. Navigate to the root folder (cd burner)
352
+ 5. Install dependencies (bundle)
353
+
354
+ ### Running Tests
355
+
356
+ To execute the test suite run:
357
+
358
+ ````bash
359
+ bundle exec rspec spec --format documentation
360
+ ````
361
+
362
+ Alternatively, you can have Guard watch for changes:
363
+
364
+ ````bash
365
+ bundle exec guard
366
+ ````
367
+
368
+ Also, do not forget to run Rubocop:
369
+
370
+ ````bash
371
+ bundle exec rubocop
372
+ ````
373
+
374
+ ### Publishing
375
+
376
+ Note: ensure you have proper authorization before trying to publish new versions.
377
+
378
+ After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
379
+
380
+ 1. Merge Pull Request into master
381
+ 2. Update `lib/burner/version.rb` using [semantic versioning](https://semver.org/)
382
+ 3. Install dependencies: `bundle`
383
+ 4. Update `CHANGELOG.md` with release notes
384
+ 5. Commit & push master to remote and ensure CI builds master successfully
385
+ 6. Run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
386
+
387
+ ## Code of Conduct
388
+
389
+ Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bluemarblepayroll/burner/blob/master/CODE_OF_CONDUCT.md).
390
+
391
+ ## License
392
+
393
+ This project is MIT Licensed.
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.email = ['mruggio@bluemarblepayroll.com']
16
16
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  s.bindir = 'exe'
18
- s.executables = %w[]
18
+ s.executables = %w[burner]
19
19
  s.homepage = 'https://github.com/bluemarblepayroll/burner'
20
20
  s.license = 'MIT'
21
21
  s.metadata = {
@@ -29,7 +29,10 @@ Gem::Specification.new do |s|
29
29
  s.required_ruby_version = '>= 2.5'
30
30
 
31
31
  s.add_dependency('acts_as_hashable', '~>1.2')
32
+ s.add_dependency('hashematics', '~>1.1')
33
+ s.add_dependency('hash_math', '~>1.2')
32
34
  s.add_dependency('objectable', '~>1.0')
35
+ s.add_dependency('realize', '~>1.2')
33
36
  s.add_dependency('stringento', '~>2.1')
34
37
 
35
38
  s.add_development_dependency('guard-rspec', '~>4.7')
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # Copyright (c) 2020-present, Blue Marble Payroll, LLC
6
+ #
7
+ # This source code is licensed under the MIT license found in the
8
+ # LICENSE file in the root directory of this source tree.
9
+ #
10
+
11
+ require 'bundler/setup'
12
+ require 'burner'
13
+ require 'pry'
14
+
15
+ if ARGV.empty?
16
+ puts 'Usage: ./exe/burner package.yaml key=value key=value ...'
17
+ exit
18
+ end
19
+
20
+ # This should return exit code of 1 if it raises any hard errors.
21
+ Burner::Cli.new(ARGV).execute