burner 1.0.0.pre.alpha → 1.0.0.pre.alpha.5
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 +4 -4
- data/README.md +320 -2
- data/burner.gemspec +3 -0
- data/lib/burner.rb +10 -0
- data/lib/burner/cli.rb +7 -7
- data/lib/burner/job.rb +4 -2
- data/lib/burner/jobs.rb +30 -10
- data/lib/burner/jobs/collection/arrays_to_objects.rb +43 -0
- data/lib/burner/jobs/collection/graph.rb +43 -0
- data/lib/burner/jobs/collection/objects_to_arrays.rb +54 -0
- data/lib/burner/jobs/collection/shift.rb +43 -0
- data/lib/burner/jobs/collection/transform.rb +64 -0
- data/lib/burner/jobs/collection/transform/attribute.rb +33 -0
- data/lib/burner/jobs/collection/transform/attribute_renderer.rb +36 -0
- data/lib/burner/jobs/collection/unpivot.rb +45 -0
- data/lib/burner/jobs/collection/values.rb +50 -0
- data/lib/burner/jobs/deserialize/csv.rb +28 -0
- data/lib/burner/jobs/deserialize/json.rb +1 -1
- data/lib/burner/jobs/deserialize/yaml.rb +1 -1
- data/lib/burner/jobs/dummy.rb +1 -1
- data/lib/burner/jobs/echo.rb +2 -2
- data/lib/burner/jobs/io/base.rb +3 -16
- data/lib/burner/jobs/io/exist.rb +43 -0
- data/lib/burner/jobs/io/read.rb +12 -2
- data/lib/burner/jobs/io/write.rb +25 -3
- data/lib/burner/jobs/serialize/csv.rb +38 -0
- data/lib/burner/jobs/serialize/json.rb +1 -1
- data/lib/burner/jobs/serialize/yaml.rb +1 -1
- data/lib/burner/jobs/set.rb +1 -1
- data/lib/burner/jobs/sleep.rb +1 -1
- data/lib/burner/modeling.rb +10 -0
- data/lib/burner/modeling/key_index_mapping.rb +29 -0
- data/lib/burner/payload.rb +19 -4
- data/lib/burner/pipeline.rb +10 -3
- data/lib/burner/step.rb +5 -3
- data/lib/burner/string_template.rb +6 -5
- data/lib/burner/version.rb +1 -1
- data/lib/burner/written_file.rb +28 -0
- metadata +59 -2
@@ -27,7 +27,7 @@ module Burner
|
|
27
27
|
# in a sandbox. By default, though, we will try and drive them towards using it
|
28
28
|
# in the safer alternative.
|
29
29
|
# rubocop:disable Security/YAMLLoad
|
30
|
-
def perform(output, payload
|
30
|
+
def perform(output, payload)
|
31
31
|
output.detail('Warning: loading YAML not using safe_load.') unless safe
|
32
32
|
|
33
33
|
payload.value = safe ? YAML.safe_load(payload.value) : YAML.load(payload.value)
|
data/lib/burner/jobs/dummy.rb
CHANGED
data/lib/burner/jobs/echo.rb
CHANGED
@@ -21,8 +21,8 @@ module Burner
|
|
21
21
|
freeze
|
22
22
|
end
|
23
23
|
|
24
|
-
def perform(output,
|
25
|
-
compiled_message =
|
24
|
+
def perform(output, payload)
|
25
|
+
compiled_message = job_string_template(message, output, payload)
|
26
26
|
|
27
27
|
output.detail(compiled_message)
|
28
28
|
|
data/lib/burner/jobs/io/base.rb
CHANGED
@@ -12,27 +12,14 @@ module Burner
|
|
12
12
|
module IO
|
13
13
|
# Common configuration/code for all IO Job subclasses.
|
14
14
|
class Base < Job
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :path
|
16
16
|
|
17
|
-
def initialize(name:, path
|
17
|
+
def initialize(name:, path:)
|
18
18
|
super(name: name)
|
19
19
|
|
20
20
|
raise ArgumentError, 'path is required' if path.to_s.empty?
|
21
21
|
|
22
|
-
@path
|
23
|
-
@binary = binary || false
|
24
|
-
|
25
|
-
freeze
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def compile_path(params)
|
31
|
-
eval_string_template(path, params)
|
32
|
-
end
|
33
|
-
|
34
|
-
def mode
|
35
|
-
binary ? 'wb' : 'w'
|
22
|
+
@path = path.to_s
|
36
23
|
end
|
37
24
|
end
|
38
25
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'base'
|
11
|
+
|
12
|
+
module Burner
|
13
|
+
class Jobs
|
14
|
+
module IO
|
15
|
+
# Check to see if a file exists. If short_circuit is set to true and the file
|
16
|
+
# does not exist then the job will return false and short circuit the pipeline.
|
17
|
+
class Exist < Base
|
18
|
+
attr_reader :short_circuit
|
19
|
+
|
20
|
+
def initialize(name:, path:, short_circuit: false)
|
21
|
+
super(name: name, path: path)
|
22
|
+
|
23
|
+
@short_circuit = short_circuit || false
|
24
|
+
|
25
|
+
freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform(output, payload)
|
29
|
+
compiled_path = job_string_template(path, output, payload)
|
30
|
+
|
31
|
+
exists = File.exist?(compiled_path)
|
32
|
+
verb = exists ? 'does' : 'does not'
|
33
|
+
|
34
|
+
output.detail("The path: #{compiled_path} #{verb} exist")
|
35
|
+
|
36
|
+
# if anything but false is returned then the pipeline will not short circuit. So
|
37
|
+
# we need to make sure we explicitly return false.
|
38
|
+
short_circuit && !exists ? false : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/burner/jobs/io/read.rb
CHANGED
@@ -14,8 +14,18 @@ module Burner
|
|
14
14
|
module IO
|
15
15
|
# Read value from disk.
|
16
16
|
class Read < Base
|
17
|
-
|
18
|
-
|
17
|
+
attr_reader :binary
|
18
|
+
|
19
|
+
def initialize(name:, path:, binary: false)
|
20
|
+
super(name: name, path: path)
|
21
|
+
|
22
|
+
@binary = binary || false
|
23
|
+
|
24
|
+
freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform(output, payload)
|
28
|
+
compiled_path = job_string_template(path, output, payload)
|
19
29
|
|
20
30
|
output.detail("Reading: #{compiled_path}")
|
21
31
|
|
data/lib/burner/jobs/io/write.rb
CHANGED
@@ -14,14 +14,32 @@ module Burner
|
|
14
14
|
module IO
|
15
15
|
# Write value to disk.
|
16
16
|
class Write < Base
|
17
|
-
|
18
|
-
|
17
|
+
attr_reader :binary
|
18
|
+
|
19
|
+
def initialize(name:, path:, binary: false)
|
20
|
+
super(name: name, path: path)
|
21
|
+
|
22
|
+
@binary = binary || false
|
23
|
+
|
24
|
+
freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform(output, payload)
|
28
|
+
compiled_path = job_string_template(path, output, payload)
|
19
29
|
|
20
30
|
ensure_directory_exists(output, compiled_path)
|
21
31
|
|
22
32
|
output.detail("Writing: #{compiled_path}")
|
23
33
|
|
24
|
-
|
34
|
+
time_in_seconds = Benchmark.measure do
|
35
|
+
File.open(compiled_path, mode) { |io| io.write(payload.value) }
|
36
|
+
end.real
|
37
|
+
|
38
|
+
payload.add_written_file(
|
39
|
+
logical_filename: compiled_path,
|
40
|
+
physical_filename: compiled_path,
|
41
|
+
time_in_seconds: time_in_seconds
|
42
|
+
)
|
25
43
|
|
26
44
|
nil
|
27
45
|
end
|
@@ -39,6 +57,10 @@ module Burner
|
|
39
57
|
|
40
58
|
nil
|
41
59
|
end
|
60
|
+
|
61
|
+
def mode
|
62
|
+
binary ? 'wb' : 'w'
|
63
|
+
end
|
42
64
|
end
|
43
65
|
end
|
44
66
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Burner
|
11
|
+
class Jobs
|
12
|
+
module Serialize
|
13
|
+
# Take an array of arrays and create a CSV.
|
14
|
+
# Expected Payload#value input: array of arrays.
|
15
|
+
# Payload#value output: a serialized CSV string.
|
16
|
+
class Csv < Job
|
17
|
+
def perform(_output, payload)
|
18
|
+
payload.value = CSV.generate(options) do |csv|
|
19
|
+
(payload.value || []).each do |row|
|
20
|
+
csv << row
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def options
|
30
|
+
{
|
31
|
+
headers: false,
|
32
|
+
write_headers: false
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/burner/jobs/set.rb
CHANGED
data/lib/burner/jobs/sleep.rb
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'modeling/key_index_mapping'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Burner
|
11
|
+
module Modeling
|
12
|
+
# Generic relationship between a numeric index and a key.
|
13
|
+
class KeyIndexMapping
|
14
|
+
acts_as_hashable
|
15
|
+
|
16
|
+
attr_reader :index, :key
|
17
|
+
|
18
|
+
def initialize(index:, key:)
|
19
|
+
raise ArgumentError, 'index is required' if index.to_s.empty?
|
20
|
+
raise ArgumentError, 'key is required' if key.to_s.empty?
|
21
|
+
|
22
|
+
@index = index.to_i
|
23
|
+
@key = key.to_s
|
24
|
+
|
25
|
+
freeze
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/burner/payload.rb
CHANGED
@@ -7,6 +7,8 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
+
require_relative 'written_file'
|
11
|
+
|
10
12
|
module Burner
|
11
13
|
# The input for all Job#perform methods. The main notion of this object is its "value"
|
12
14
|
# attribute. This is dynamic and weak on purpose and is subject to whatever the Job#perform
|
@@ -16,11 +18,24 @@ module Burner
|
|
16
18
|
class Payload
|
17
19
|
attr_accessor :value
|
18
20
|
|
19
|
-
attr_reader :
|
21
|
+
attr_reader :params,
|
22
|
+
:time,
|
23
|
+
:written_files
|
24
|
+
|
25
|
+
def initialize(
|
26
|
+
params: {},
|
27
|
+
time: Time.now.utc,
|
28
|
+
value: nil,
|
29
|
+
written_files: []
|
30
|
+
)
|
31
|
+
@params = params || {}
|
32
|
+
@time = time || Time.now.utc
|
33
|
+
@value = value
|
34
|
+
@written_files = written_files || []
|
35
|
+
end
|
20
36
|
|
21
|
-
def
|
22
|
-
|
23
|
-
@value = value
|
37
|
+
def add_written_file(written_file)
|
38
|
+
tap { written_files << WrittenFile.make(written_file) }
|
24
39
|
end
|
25
40
|
end
|
26
41
|
end
|
data/lib/burner/pipeline.rb
CHANGED
@@ -35,14 +35,21 @@ module Burner
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# The main entry-point for kicking off a pipeline.
|
38
|
-
def execute(
|
38
|
+
def execute(output: Output.new, payload: Payload.new)
|
39
39
|
output.write("Pipeline started with #{steps.length} step(s)")
|
40
40
|
|
41
|
-
output_params(params, output)
|
41
|
+
output_params(payload.params, output)
|
42
42
|
output.ruler
|
43
43
|
|
44
44
|
time_in_seconds = Benchmark.measure do
|
45
|
-
steps.each
|
45
|
+
steps.each do |step|
|
46
|
+
return_value = step.perform(output, payload)
|
47
|
+
|
48
|
+
if return_value.is_a?(FalseClass)
|
49
|
+
output.detail('Job returned false, ending pipeline.')
|
50
|
+
break
|
51
|
+
end
|
52
|
+
end
|
46
53
|
end.real.round(3)
|
47
54
|
|
48
55
|
output.ruler
|
data/lib/burner/step.rb
CHANGED
@@ -28,16 +28,18 @@ module Burner
|
|
28
28
|
freeze
|
29
29
|
end
|
30
30
|
|
31
|
-
def perform(output, payload
|
31
|
+
def perform(output, payload)
|
32
|
+
return_value = nil
|
33
|
+
|
32
34
|
output.title("#{job.class.name}#{SEPARATOR}#{job.name}")
|
33
35
|
|
34
36
|
time_in_seconds = Benchmark.measure do
|
35
|
-
job.perform(output, payload
|
37
|
+
return_value = job.perform(output, payload)
|
36
38
|
end.real.round(3)
|
37
39
|
|
38
40
|
output.complete(time_in_seconds)
|
39
41
|
|
40
|
-
|
42
|
+
return_value
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
@@ -9,10 +9,11 @@
|
|
9
9
|
|
10
10
|
module Burner
|
11
11
|
# Can take in a string and an object and use the object for formatting string interpolations
|
12
|
-
# using tokens of form: {attribute_name}.
|
13
|
-
#
|
14
|
-
# is that it can understand almost any type of
|
15
|
-
#
|
12
|
+
# using tokens of form: {attribute_name}. This templating class does not understand nested
|
13
|
+
# structures, so input should be a flat object/hash in the form of key-value pairs. A benefit of
|
14
|
+
# using Objectable for resolution is that it can understand almost any type of
|
15
|
+
# object: Hash, Struct, OpenStruct, custom objects, etc.
|
16
|
+
# For more information see underlying libraries:
|
16
17
|
# * Stringento: https://github.com/bluemarblepayroll/stringento
|
17
18
|
# * Objectable: https://github.com/bluemarblepayroll/objectable
|
18
19
|
class StringTemplate
|
@@ -21,7 +22,7 @@ module Burner
|
|
21
22
|
attr_reader :resolver
|
22
23
|
|
23
24
|
def initialize
|
24
|
-
@resolver = Objectable.resolver
|
25
|
+
@resolver = Objectable.resolver(separator: '')
|
25
26
|
|
26
27
|
freeze
|
27
28
|
end
|
data/lib/burner/version.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Burner
|
11
|
+
# Describes a file that was generated by a Job. If a Job emits a file, it should also add the
|
12
|
+
# file details to the Payload#written_files array using the Payload#add_written_file method.
|
13
|
+
class WrittenFile
|
14
|
+
acts_as_hashable
|
15
|
+
|
16
|
+
attr_reader :logical_filename,
|
17
|
+
:physical_filename,
|
18
|
+
:time_in_seconds
|
19
|
+
|
20
|
+
def initialize(logical_filename:, physical_filename:, time_in_seconds:)
|
21
|
+
@logical_filename = logical_filename.to_s
|
22
|
+
@physical_filename = physical_filename.to_s
|
23
|
+
@time_in_seconds = time_in_seconds.to_f
|
24
|
+
|
25
|
+
freeze
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|