burner 1.0.0.pre.alpha.3 → 1.0.0.pre.alpha.8
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/.rubocop.yml +2 -0
- data/README.md +76 -44
- data/burner.gemspec +4 -1
- data/exe/burner +2 -3
- data/lib/burner.rb +11 -0
- data/lib/burner/cli.rb +10 -10
- data/lib/burner/job.rb +29 -9
- data/lib/burner/job_with_register.rb +24 -0
- data/lib/burner/jobs.rb +27 -23
- data/lib/burner/library.rb +32 -0
- data/lib/burner/library/collection/arrays_to_objects.rb +75 -0
- data/lib/burner/library/collection/graph.rb +42 -0
- data/lib/burner/library/collection/objects_to_arrays.rb +88 -0
- data/lib/burner/library/collection/shift.rb +42 -0
- data/lib/burner/library/collection/transform.rb +66 -0
- data/lib/burner/library/collection/unpivot.rb +53 -0
- data/lib/burner/library/collection/validate.rb +89 -0
- data/lib/burner/library/collection/values.rb +49 -0
- data/lib/burner/library/deserialize/csv.rb +27 -0
- data/lib/burner/{jobs → library}/deserialize/json.rb +7 -6
- data/lib/burner/{jobs → library}/deserialize/yaml.rb +14 -8
- data/lib/burner/{jobs → library}/dummy.rb +4 -4
- data/lib/burner/{jobs → library}/echo.rb +5 -5
- data/lib/burner/{jobs → library}/io/base.rb +4 -10
- data/lib/burner/{jobs → library}/io/exist.rb +13 -11
- data/lib/burner/{jobs → library}/io/read.rb +9 -8
- data/lib/burner/{jobs → library}/io/write.rb +11 -8
- data/lib/burner/library/serialize/csv.rb +37 -0
- data/lib/burner/{jobs → library}/serialize/json.rb +7 -6
- data/lib/burner/{jobs → library}/serialize/yaml.rb +7 -6
- data/lib/burner/{jobs/set.rb → library/set_value.rb} +9 -8
- data/lib/burner/{jobs → library}/sleep.rb +4 -4
- data/lib/burner/modeling.rb +13 -0
- data/lib/burner/modeling/attribute.rb +29 -0
- data/lib/burner/modeling/attribute_renderer.rb +32 -0
- data/lib/burner/modeling/key_index_mapping.rb +29 -0
- data/lib/burner/modeling/validations.rb +23 -0
- data/lib/burner/modeling/validations/base.rb +35 -0
- data/lib/burner/modeling/validations/blank.rb +31 -0
- data/lib/burner/modeling/validations/present.rb +31 -0
- data/lib/burner/payload.rb +55 -10
- data/lib/burner/pipeline.rb +25 -6
- data/lib/burner/side_effects.rb +10 -0
- data/lib/burner/side_effects/written_file.rb +28 -0
- data/lib/burner/step.rb +2 -8
- data/lib/burner/util.rb +11 -0
- data/lib/burner/util/arrayable.rb +30 -0
- data/lib/burner/util/string_template.rb +42 -0
- data/lib/burner/version.rb +1 -1
- metadata +84 -18
- data/lib/burner/string_template.rb +0 -40
- data/lib/burner/written_file.rb +0 -28
@@ -0,0 +1,89 @@
|
|
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 Library
|
12
|
+
module Collection
|
13
|
+
# Process each object in an array and see if its attribute values match a given set
|
14
|
+
# of validations. The main register will include the valid objects and the invalid_register
|
15
|
+
# will contain the invalid objects.
|
16
|
+
#
|
17
|
+
# Expected Payload#value input: array of objects.
|
18
|
+
# Payload#value output: An array of objects.
|
19
|
+
class Validate < JobWithRegister
|
20
|
+
DEFAULT_INVALID_REGISTER = 'invalid'
|
21
|
+
DEFAULT_JOIN_CHAR = ', '
|
22
|
+
DEFAULT_MESSAGE_KEY = 'errors'
|
23
|
+
|
24
|
+
attr_reader :invalid_register,
|
25
|
+
:join_char,
|
26
|
+
:message_key,
|
27
|
+
:resolver,
|
28
|
+
:validations
|
29
|
+
|
30
|
+
def initialize(
|
31
|
+
name:,
|
32
|
+
invalid_register: DEFAULT_INVALID_REGISTER,
|
33
|
+
join_char: DEFAULT_JOIN_CHAR,
|
34
|
+
message_key: DEFAULT_MESSAGE_KEY,
|
35
|
+
register: '',
|
36
|
+
separator: '',
|
37
|
+
validations: []
|
38
|
+
)
|
39
|
+
super(name: name, register: register)
|
40
|
+
|
41
|
+
@invalid_register = invalid_register.to_s
|
42
|
+
@join_char = join_char.to_s
|
43
|
+
@message_key = message_key.to_s
|
44
|
+
@resolver = Objectable.resolver(separator: separator)
|
45
|
+
@validations = Modeling::Validations.array(validations)
|
46
|
+
|
47
|
+
freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
def perform(output, payload)
|
51
|
+
valid = []
|
52
|
+
invalid = []
|
53
|
+
|
54
|
+
(payload[register] || []).each do |object|
|
55
|
+
errors = validate(object)
|
56
|
+
|
57
|
+
if errors.empty?
|
58
|
+
valid << object
|
59
|
+
else
|
60
|
+
invalid << make_in_error(object, errors)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
output.detail("Valid count: #{valid.length}")
|
65
|
+
output.detail("Invalid count: #{invalid.length}")
|
66
|
+
|
67
|
+
payload[register] = valid
|
68
|
+
payload[invalid_register] = invalid
|
69
|
+
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def validate(object)
|
76
|
+
validations.each_with_object([]) do |validation, memo|
|
77
|
+
next if validation.valid?(object, resolver)
|
78
|
+
|
79
|
+
memo << validation.message
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def make_in_error(object, errors)
|
84
|
+
resolver.set(object, message_key, errors.join(join_char))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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 Library
|
12
|
+
module Collection
|
13
|
+
# Take an array of objects and call #values on each object.
|
14
|
+
# If include_keys is true (it is false by default), then call #keys on the first
|
15
|
+
# object and inject that as a "header" object.
|
16
|
+
#
|
17
|
+
# Expected Payload#value input: array of objects.
|
18
|
+
# Payload#value output: An array of arrays.
|
19
|
+
class Values < JobWithRegister
|
20
|
+
attr_reader :include_keys
|
21
|
+
|
22
|
+
def initialize(name:, include_keys: false, register: '')
|
23
|
+
super(name: name, register: register)
|
24
|
+
|
25
|
+
@include_keys = include_keys || false
|
26
|
+
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def perform(_output, payload)
|
31
|
+
payload[register] = array(payload[register])
|
32
|
+
keys = include_keys ? [keys(payload[register].first)] : []
|
33
|
+
values = payload[register].map { |object| values(object) }
|
34
|
+
payload[register] = keys + values
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def keys(object)
|
40
|
+
object.respond_to?(:keys) ? object.keys : []
|
41
|
+
end
|
42
|
+
|
43
|
+
def values(object)
|
44
|
+
object.respond_to?(:values) ? object.values : []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 Library
|
12
|
+
module Deserialize
|
13
|
+
# Take a CSV string and de-serialize into object(s).
|
14
|
+
#
|
15
|
+
# Expected Payload#value input: nothing.
|
16
|
+
# Payload#value output: an array of arrays. Each inner array represents one data row.
|
17
|
+
class Csv < JobWithRegister
|
18
|
+
# This currently only supports returning an array of arrays, including the header row.
|
19
|
+
# In the future this could be extended to offer more customizable options, such as
|
20
|
+
# making it return an array of hashes with the columns mapped, etc.)
|
21
|
+
def perform(_output, payload)
|
22
|
+
payload[register] = CSV.new(payload[register], headers: false).to_a
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -8,14 +8,15 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Burner
|
11
|
-
|
11
|
+
module Library
|
12
12
|
module Deserialize
|
13
13
|
# Take a JSON string and deserialize into object(s).
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
#
|
15
|
+
# Expected Payload#value input: string of JSON data.
|
16
|
+
# Payload#value output: anything, as specified by the JSON de-serializer.
|
17
|
+
class Json < JobWithRegister
|
18
|
+
def perform(_output, payload)
|
19
|
+
payload[register] = JSON.parse(payload[register])
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
@@ -8,14 +8,20 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Burner
|
11
|
-
|
11
|
+
module Library
|
12
12
|
module Deserialize
|
13
|
-
# Take a YAML string and deserialize into object(s).
|
14
|
-
|
13
|
+
# Take a YAML string and deserialize into object(s). It uses YAML#safe_load by default,
|
14
|
+
# which ensures only a limited number of Ruby object constants can be hydrated by the
|
15
|
+
# YAML. If you wish to ease this restriction, for example if you have custom serialization
|
16
|
+
# for custom classes, then you can pass in safe: false.
|
17
|
+
#
|
18
|
+
# Expected Payload#value input: string of YAML data.
|
19
|
+
# Payload#value output: anything as specified by the YAML de-serializer.
|
20
|
+
class Yaml < JobWithRegister
|
15
21
|
attr_reader :safe
|
16
22
|
|
17
|
-
def initialize(name:, safe: true)
|
18
|
-
super(name: name)
|
23
|
+
def initialize(name:, register: '', safe: true)
|
24
|
+
super(name: name, register: register)
|
19
25
|
|
20
26
|
@safe = safe
|
21
27
|
|
@@ -27,12 +33,12 @@ module Burner
|
|
27
33
|
# in a sandbox. By default, though, we will try and drive them towards using it
|
28
34
|
# in the safer alternative.
|
29
35
|
# rubocop:disable Security/YAMLLoad
|
30
|
-
def perform(output, payload
|
36
|
+
def perform(output, payload)
|
31
37
|
output.detail('Warning: loading YAML not using safe_load.') unless safe
|
32
38
|
|
33
|
-
|
39
|
+
value = payload[register]
|
34
40
|
|
35
|
-
|
41
|
+
payload[register] = safe ? YAML.safe_load(value) : YAML.load(value)
|
36
42
|
end
|
37
43
|
# rubocop:enable Security/YAMLLoad
|
38
44
|
end
|
@@ -8,12 +8,12 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Burner
|
11
|
-
|
11
|
+
module Library
|
12
12
|
# Do nothing.
|
13
|
+
#
|
14
|
+
# Note: this does not use Payload#value.
|
13
15
|
class Dummy < Job
|
14
|
-
def perform(_output, _payload
|
15
|
-
nil
|
16
|
-
end
|
16
|
+
def perform(_output, _payload); end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -8,8 +8,10 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Burner
|
11
|
-
|
11
|
+
module Library
|
12
12
|
# Output a simple message to the output.
|
13
|
+
#
|
14
|
+
# Note: this does not use Payload#value.
|
13
15
|
class Echo < Job
|
14
16
|
attr_reader :message
|
15
17
|
|
@@ -21,12 +23,10 @@ module Burner
|
|
21
23
|
freeze
|
22
24
|
end
|
23
25
|
|
24
|
-
def perform(output,
|
25
|
-
compiled_message =
|
26
|
+
def perform(output, payload)
|
27
|
+
compiled_message = job_string_template(message, output, payload)
|
26
28
|
|
27
29
|
output.detail(compiled_message)
|
28
|
-
|
29
|
-
nil
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -8,25 +8,19 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Burner
|
11
|
-
|
11
|
+
module Library
|
12
12
|
module IO
|
13
13
|
# Common configuration/code for all IO Job subclasses.
|
14
|
-
class Base <
|
14
|
+
class Base < JobWithRegister
|
15
15
|
attr_reader :path
|
16
16
|
|
17
|
-
def initialize(name:, path:)
|
18
|
-
super(name: name)
|
17
|
+
def initialize(name:, path:, register: '')
|
18
|
+
super(name: name, register: register)
|
19
19
|
|
20
20
|
raise ArgumentError, 'path is required' if path.to_s.empty?
|
21
21
|
|
22
22
|
@path = path.to_s
|
23
23
|
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def compile_path(params)
|
28
|
-
eval_string_template(path, params)
|
29
|
-
end
|
30
24
|
end
|
31
25
|
end
|
32
26
|
end
|
@@ -10,32 +10,34 @@
|
|
10
10
|
require_relative 'base'
|
11
11
|
|
12
12
|
module Burner
|
13
|
-
|
13
|
+
module Library
|
14
14
|
module IO
|
15
15
|
# Check to see if a file exists. If short_circuit is set to true and the file
|
16
16
|
# does not exist then the job will return false and short circuit the pipeline.
|
17
|
-
|
18
|
-
|
17
|
+
#
|
18
|
+
# Note: this does not use Payload#value.
|
19
|
+
class Exist < Job
|
20
|
+
attr_reader :path, :short_circuit
|
19
21
|
|
20
22
|
def initialize(name:, path:, short_circuit: false)
|
21
|
-
super(name: name
|
23
|
+
super(name: name)
|
22
24
|
|
23
|
-
|
25
|
+
raise ArgumentError, 'path is required' if path.to_s.empty?
|
24
26
|
|
25
|
-
|
27
|
+
@path = path.to_s
|
28
|
+
@short_circuit = short_circuit || false
|
26
29
|
end
|
27
30
|
|
28
|
-
def perform(output,
|
29
|
-
compiled_path =
|
31
|
+
def perform(output, payload)
|
32
|
+
compiled_path = job_string_template(path, output, payload)
|
30
33
|
|
31
34
|
exists = File.exist?(compiled_path)
|
32
35
|
verb = exists ? 'does' : 'does not'
|
33
36
|
|
34
37
|
output.detail("The path: #{compiled_path} #{verb} exist")
|
35
38
|
|
36
|
-
# if anything but false is returned then the pipeline will not short circuit.
|
37
|
-
|
38
|
-
short_circuit && !exists ? false : nil
|
39
|
+
# if anything but false is returned then the pipeline will not short circuit.
|
40
|
+
payload.halt_pipeline if short_circuit && !exists
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
@@ -10,28 +10,29 @@
|
|
10
10
|
require_relative 'base'
|
11
11
|
|
12
12
|
module Burner
|
13
|
-
|
13
|
+
module Library
|
14
14
|
module IO
|
15
15
|
# Read value from disk.
|
16
|
+
#
|
17
|
+
# Expected Payload#value input: nothing.
|
18
|
+
# Payload#value output: contents of the specified file.
|
16
19
|
class Read < Base
|
17
20
|
attr_reader :binary
|
18
21
|
|
19
|
-
def initialize(name:, path:, binary: false)
|
20
|
-
super(name: name, path: path)
|
22
|
+
def initialize(name:, path:, binary: false, register: '')
|
23
|
+
super(name: name, path: path, register: register)
|
21
24
|
|
22
25
|
@binary = binary || false
|
23
26
|
|
24
27
|
freeze
|
25
28
|
end
|
26
29
|
|
27
|
-
def perform(output, payload
|
28
|
-
compiled_path =
|
30
|
+
def perform(output, payload)
|
31
|
+
compiled_path = job_string_template(path, output, payload)
|
29
32
|
|
30
33
|
output.detail("Reading: #{compiled_path}")
|
31
34
|
|
32
|
-
payload
|
33
|
-
|
34
|
-
nil
|
35
|
+
payload[register] = File.open(compiled_path, mode, &:read)
|
35
36
|
end
|
36
37
|
|
37
38
|
private
|
@@ -10,38 +10,41 @@
|
|
10
10
|
require_relative 'base'
|
11
11
|
|
12
12
|
module Burner
|
13
|
-
|
13
|
+
module Library
|
14
14
|
module IO
|
15
15
|
# Write value to disk.
|
16
|
+
#
|
17
|
+
# Expected Payload#value input: anything.
|
18
|
+
# Payload#value output: whatever was passed in.
|
16
19
|
class Write < Base
|
17
20
|
attr_reader :binary
|
18
21
|
|
19
|
-
def initialize(name:, path:, binary: false)
|
20
|
-
super(name: name, path: path)
|
22
|
+
def initialize(name:, path:, binary: false, register: '')
|
23
|
+
super(name: name, path: path, register: register)
|
21
24
|
|
22
25
|
@binary = binary || false
|
23
26
|
|
24
27
|
freeze
|
25
28
|
end
|
26
29
|
|
27
|
-
def perform(output, payload
|
28
|
-
compiled_path =
|
30
|
+
def perform(output, payload)
|
31
|
+
compiled_path = job_string_template(path, output, payload)
|
29
32
|
|
30
33
|
ensure_directory_exists(output, compiled_path)
|
31
34
|
|
32
35
|
output.detail("Writing: #{compiled_path}")
|
33
36
|
|
34
37
|
time_in_seconds = Benchmark.measure do
|
35
|
-
File.open(compiled_path, mode) { |io| io.write(payload
|
38
|
+
File.open(compiled_path, mode) { |io| io.write(payload[register]) }
|
36
39
|
end.real
|
37
40
|
|
38
|
-
|
41
|
+
side_effect = SideEffects::WrittenFile.new(
|
39
42
|
logical_filename: compiled_path,
|
40
43
|
physical_filename: compiled_path,
|
41
44
|
time_in_seconds: time_in_seconds
|
42
45
|
)
|
43
46
|
|
44
|
-
|
47
|
+
payload.add_side_effect(side_effect)
|
45
48
|
end
|
46
49
|
|
47
50
|
private
|
@@ -0,0 +1,37 @@
|
|
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 Library
|
12
|
+
module Serialize
|
13
|
+
# Take an array of arrays and create a CSV.
|
14
|
+
#
|
15
|
+
# Expected Payload#value input: array of arrays.
|
16
|
+
# Payload#value output: a serialized CSV string.
|
17
|
+
class Csv < JobWithRegister
|
18
|
+
def perform(_output, payload)
|
19
|
+
payload[register] = CSV.generate(options) do |csv|
|
20
|
+
array(payload[register]).each do |row|
|
21
|
+
csv << row
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def options
|
29
|
+
{
|
30
|
+
headers: false,
|
31
|
+
write_headers: false
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|