burner 1.0.0.pre.alpha.5 → 1.0.0.pre.alpha.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/README.md +52 -48
  4. data/burner.gemspec +1 -1
  5. data/exe/burner +2 -3
  6. data/lib/burner.rb +2 -0
  7. data/lib/burner/cli.rb +2 -0
  8. data/lib/burner/job.rb +27 -9
  9. data/lib/burner/job_with_register.rb +24 -0
  10. data/lib/burner/jobs.rb +28 -41
  11. data/lib/burner/library.rb +32 -0
  12. data/lib/burner/library/collection/arrays_to_objects.rb +75 -0
  13. data/lib/burner/{jobs → library}/collection/graph.rb +7 -8
  14. data/lib/burner/library/collection/objects_to_arrays.rb +88 -0
  15. data/lib/burner/{jobs → library}/collection/shift.rb +8 -9
  16. data/lib/burner/{jobs → library}/collection/transform.rb +17 -15
  17. data/lib/burner/{jobs → library}/collection/unpivot.rb +17 -9
  18. data/lib/burner/library/collection/validate.rb +89 -0
  19. data/lib/burner/{jobs → library}/collection/values.rb +9 -10
  20. data/lib/burner/{jobs → library}/deserialize/csv.rb +4 -5
  21. data/lib/burner/{jobs → library}/deserialize/json.rb +6 -5
  22. data/lib/burner/{jobs → library}/deserialize/yaml.rb +13 -7
  23. data/lib/burner/{jobs → library}/dummy.rb +4 -4
  24. data/lib/burner/{jobs → library}/echo.rb +3 -3
  25. data/lib/burner/{jobs → library}/io/base.rb +4 -4
  26. data/lib/burner/{jobs → library}/io/exist.rb +11 -9
  27. data/lib/burner/{jobs → library}/io/read.rb +7 -6
  28. data/lib/burner/{jobs → library}/io/write.rb +9 -6
  29. data/lib/burner/{jobs → library}/serialize/csv.rb +5 -6
  30. data/lib/burner/{jobs → library}/serialize/json.rb +6 -5
  31. data/lib/burner/{jobs → library}/serialize/yaml.rb +6 -5
  32. data/lib/burner/{jobs/set.rb → library/set_value.rb} +8 -7
  33. data/lib/burner/{jobs → library}/sleep.rb +3 -3
  34. data/lib/burner/modeling.rb +3 -0
  35. data/lib/burner/modeling/attribute.rb +29 -0
  36. data/lib/burner/modeling/attribute_renderer.rb +32 -0
  37. data/lib/burner/modeling/validations.rb +23 -0
  38. data/lib/burner/modeling/validations/base.rb +35 -0
  39. data/lib/burner/modeling/validations/blank.rb +31 -0
  40. data/lib/burner/modeling/validations/present.rb +31 -0
  41. data/lib/burner/payload.rb +52 -15
  42. data/lib/burner/pipeline.rb +23 -4
  43. data/lib/burner/side_effects.rb +10 -0
  44. data/lib/burner/side_effects/written_file.rb +28 -0
  45. data/lib/burner/step.rb +1 -5
  46. data/lib/burner/util.rb +11 -0
  47. data/lib/burner/util/arrayable.rb +30 -0
  48. data/lib/burner/util/string_template.rb +42 -0
  49. data/lib/burner/version.rb +1 -1
  50. metadata +40 -29
  51. data/lib/burner/jobs/collection/arrays_to_objects.rb +0 -43
  52. data/lib/burner/jobs/collection/objects_to_arrays.rb +0 -54
  53. data/lib/burner/jobs/collection/transform/attribute.rb +0 -33
  54. data/lib/burner/jobs/collection/transform/attribute_renderer.rb +0 -36
  55. data/lib/burner/string_template.rb +0 -40
  56. data/lib/burner/written_file.rb +0 -28
@@ -0,0 +1,32 @@
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 'job_with_register'
11
+
12
+ require_relative 'library/collection/arrays_to_objects'
13
+ require_relative 'library/collection/graph'
14
+ require_relative 'library/collection/objects_to_arrays'
15
+ require_relative 'library/collection/shift'
16
+ require_relative 'library/collection/transform'
17
+ require_relative 'library/collection/unpivot'
18
+ require_relative 'library/collection/validate'
19
+ require_relative 'library/collection/values'
20
+ require_relative 'library/deserialize/csv'
21
+ require_relative 'library/deserialize/json'
22
+ require_relative 'library/deserialize/yaml'
23
+ require_relative 'library/dummy'
24
+ require_relative 'library/echo'
25
+ require_relative 'library/io/exist'
26
+ require_relative 'library/io/read'
27
+ require_relative 'library/io/write'
28
+ require_relative 'library/serialize/csv'
29
+ require_relative 'library/serialize/json'
30
+ require_relative 'library/serialize/yaml'
31
+ require_relative 'library/set_value'
32
+ require_relative 'library/sleep'
@@ -0,0 +1,75 @@
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
+ # Convert an array of arrays to an array of objects. Pass in an array of
14
+ # Burner::Modeling::KeyIndexMapping instances or hashable configurations which specifies
15
+ # the index-to-key mappings to use.
16
+ #
17
+ # Expected Payload#value input: array of arrays.
18
+ # Payload#value output: An array of hashes.
19
+ #
20
+ # An example using a configuration-first pipeline:
21
+ #
22
+ # config = {
23
+ # jobs: [
24
+ # {
25
+ # name: 'set',
26
+ # type: 'b/set_value',
27
+ # value: [
28
+ # [1, 'funky']
29
+ # ]
30
+ # },
31
+ # {
32
+ # name: 'map',
33
+ # type: 'b/collection/arrays_to_objects',
34
+ # mappings: [
35
+ # { index: 0, key: 'id' },
36
+ # { index: 1, key: 'name' }
37
+ # ]
38
+ # },
39
+ # {
40
+ # name: 'output',
41
+ # type: 'b/echo',
42
+ # message: 'value is currently: {__value}'
43
+ # },
44
+ #
45
+ # ],
46
+ # steps: %w[set map output]
47
+ # }
48
+ #
49
+ # Burner::Pipeline.make(config).execute
50
+ class ArraysToObjects < JobWithRegister
51
+ attr_reader :mappings
52
+
53
+ def initialize(name:, mappings: [], register: '')
54
+ super(name: name, register: register)
55
+
56
+ @mappings = Modeling::KeyIndexMapping.array(mappings)
57
+
58
+ freeze
59
+ end
60
+
61
+ def perform(_output, payload)
62
+ payload[register] = array(payload[register]).map { |array| index_to_key_map(array) }
63
+ end
64
+
65
+ private
66
+
67
+ def index_to_key_map(array)
68
+ mappings.each_with_object({}) do |mapping, memo|
69
+ memo[mapping.key] = array[mapping.index]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -8,17 +8,18 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Collection
13
13
  # Take an array of (denormalized) objects and create an object hierarchy from them.
14
14
  # Under the hood it uses Hashematics: https://github.com/bluemarblepayroll/hashematics.
15
+ #
15
16
  # Expected Payload#value input: array of objects.
16
17
  # Payload#value output: An array of objects.
17
- class Graph < Job
18
+ class Graph < JobWithRegister
18
19
  attr_reader :key, :groups
19
20
 
20
- def initialize(name:, key:, config: Hashematics::Configuration.new)
21
- super(name: name)
21
+ def initialize(name:, key:, config: Hashematics::Configuration.new, register: '')
22
+ super(name: name, register: register)
22
23
 
23
24
  raise ArgumentError, 'key is required' if key.to_s.empty?
24
25
 
@@ -29,13 +30,11 @@ module Burner
29
30
  end
30
31
 
31
32
  def perform(output, payload)
32
- graph = Hashematics::Graph.new(groups).add(payload.value || [])
33
+ graph = Hashematics::Graph.new(groups).add(array(payload[register]))
33
34
 
34
35
  output.detail("Graphing: #{key}")
35
36
 
36
- payload.value = graph.data(key)
37
-
38
- nil
37
+ payload[register] = graph.data(key)
39
38
  end
40
39
  end
41
40
  end
@@ -0,0 +1,88 @@
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
+ # Convert an array of objects to an array of arrays. You can leverage the separator
14
+ # option to support key paths and nested objects. Pass in an array of
15
+ # Burner::Modeling::KeyIndexMapping instances or hashable configurations which specifies
16
+ # the key-to-index mappings to use.
17
+ #
18
+ # Expected Payload#value input: array of hashes.
19
+ # Payload#value output: An array of arrays.
20
+ #
21
+ # An example using a configuration-first pipeline:
22
+ #
23
+ # config = {
24
+ # jobs: [
25
+ # {
26
+ # name: 'set',
27
+ # type: 'b/set_value',
28
+ # value: [
29
+ # [1, 'funky']
30
+ # ]
31
+ # },
32
+ # {
33
+ # name: 'map',
34
+ # type: 'b/collection/objects_to_arrays',
35
+ # mappings: [
36
+ # { index: 0, key: 'id' },
37
+ # { index: 1, key: 'name' }
38
+ # ]
39
+ # },
40
+ # {
41
+ # name: 'output',
42
+ # type: 'b/echo',
43
+ # message: 'value is currently: {__value}'
44
+ # },
45
+ #
46
+ # ],
47
+ # steps: %w[set map output]
48
+ # }
49
+ #
50
+ # Burner::Pipeline.make(config).execute
51
+ class ObjectsToArrays < JobWithRegister
52
+ attr_reader :mappings
53
+
54
+ # If you wish to support nested objects you can pass in a string to use as a
55
+ # key path separator. For example: if you would like to recognize dot-notation for
56
+ # nested hashes then set separator to '.'. For more information, see the underlying
57
+ # library that supports this dot-notation concept:
58
+ # https://github.com/bluemarblepayroll/objectable
59
+ def initialize(name:, mappings: [], register: '', separator: '')
60
+ super(name: name, register: register)
61
+
62
+ @mappings = Modeling::KeyIndexMapping.array(mappings)
63
+ @resolver = Objectable.resolver(separator: separator.to_s)
64
+
65
+ freeze
66
+ end
67
+
68
+ def perform(_output, payload)
69
+ payload[register] = array(payload[register]).map { |object| key_to_index_map(object) }
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :resolver
75
+
76
+ def key_to_index_map(object)
77
+ mappings.each_with_object(prototype_array) do |mapping, memo|
78
+ memo[mapping.index] = resolver.get(object, mapping.key)
79
+ end
80
+ end
81
+
82
+ def prototype_array
83
+ Array.new(mappings.length)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -8,21 +8,23 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Collection
13
13
  # Take an array and remove the first N elements, where N is specified by the amount
14
- # attribute.
14
+ # attribute. The initial use case for this was to remove "header" rows from arrays,
15
+ # like you would expect when parsing CSV files.
16
+ #
15
17
  # Expected Payload#value input: nothing.
16
18
  # Payload#value output: An array with N beginning elements removed.
17
- class Shift < Job
19
+ class Shift < JobWithRegister
18
20
  DEFAULT_AMOUNT = 0
19
21
 
20
22
  private_constant :DEFAULT_AMOUNT
21
23
 
22
24
  attr_reader :amount
23
25
 
24
- def initialize(name:, amount: DEFAULT_AMOUNT)
25
- super(name: name)
26
+ def initialize(name:, amount: DEFAULT_AMOUNT, register: '')
27
+ super(name: name, register: register)
26
28
 
27
29
  @amount = amount.to_i
28
30
 
@@ -32,10 +34,7 @@ module Burner
32
34
  def perform(output, payload)
33
35
  output.detail("Shifting #{amount} entries.")
34
36
 
35
- payload.value ||= []
36
- payload.value.shift(amount)
37
-
38
- nil
37
+ payload[register] = array(payload[register]).slice(amount..-1)
39
38
  end
40
39
  end
41
40
  end
@@ -7,44 +7,46 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'transform/attribute'
11
- require_relative 'transform/attribute_renderer'
12
-
13
10
  module Burner
14
- class Jobs
11
+ module Library
15
12
  module Collection
16
- # Iterate over all objects and return a new set of transformed objects. Under the hood
17
- # this uses the Realize library: https://github.com/bluemarblepayroll/realize
13
+ # Iterate over all objects and return a new set of transformed objects. The object is
14
+ # transformed per the "transformers" attribute for its attributes. An attribute defines
15
+ # the ultimate key to place the value in and then the transformer pipeline to use to
16
+ # derive the value. Under the hood this uses the Realize library:
17
+ # https://github.com/bluemarblepayroll/realize
18
+ # For more information on the specific contract for attributes, see the
19
+ # Burner::Modeling::Attribute class.
20
+ #
18
21
  # Expected Payload#value input: array of objects.
19
22
  # Payload#value output: An array of objects.
20
- class Transform < Job
23
+ class Transform < JobWithRegister
21
24
  BLANK = ''
22
25
 
23
26
  attr_reader :attribute_renderers,
24
27
  :exclusive,
25
28
  :resolver
26
29
 
27
- def initialize(name:, attributes: [], exclusive: false, separator: BLANK)
28
- super(name: name)
30
+ def initialize(name:, attributes: [], exclusive: false, register: '', separator: BLANK)
31
+ super(name: name, register: register)
29
32
 
30
33
  @resolver = Objectable.resolver(separator: separator)
31
34
  @exclusive = exclusive || false
32
35
 
33
- @attribute_renderers = Attribute.array(attributes)
34
- .map { |a| AttributeRenderer.new(a, resolver) }
36
+ @attribute_renderers =
37
+ Modeling::Attribute.array(attributes)
38
+ .map { |a| Modeling::AttributeRenderer.new(a, resolver) }
35
39
 
36
40
  freeze
37
41
  end
38
42
 
39
43
  def perform(output, payload)
40
- payload.value = (payload.value || []).map { |row| transform(row, payload.time) }
44
+ payload[register] = array(payload[register]).map { |row| transform(row, payload.time) }
41
45
 
42
46
  attr_count = attribute_renderers.length
43
- row_count = payload.value.length
47
+ row_count = payload[register].length
44
48
 
45
49
  output.detail("Transformed #{attr_count} attributes(s) for #{row_count} row(s)")
46
-
47
- nil
48
50
  end
49
51
 
50
52
  private
@@ -8,18 +8,19 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Collection
13
13
  # Take an array of objects and un-pivot groups of keys into rows.
14
14
  # Under the hood it uses HashMath's Unpivot class:
15
15
  # https://github.com/bluemarblepayroll/hash_math
16
+ #
16
17
  # Expected Payload#value input: array of objects.
17
18
  # Payload#value output: An array of objects.
18
- class Unpivot < Job
19
+ class Unpivot < JobWithRegister
19
20
  attr_reader :unpivot
20
21
 
21
- def initialize(name:, pivot_set: HashMath::Unpivot::PivotSet.new)
22
- super(name: name)
22
+ def initialize(name:, pivot_set: HashMath::Unpivot::PivotSet.new, register: '')
23
+ super(name: name, register: register)
23
24
 
24
25
  @unpivot = HashMath::Unpivot.new(pivot_set)
25
26
 
@@ -27,17 +28,24 @@ module Burner
27
28
  end
28
29
 
29
30
  def perform(output, payload)
30
- pivot_count = unpivot.pivot_set.pivots.length
31
- key_count = unpivot.pivot_set.pivots.map { |p| p.keys.length }.sum
32
- object_count = payload.value&.length || 0
31
+ payload[register] = array(payload[register])
32
+ object_count = payload[register].length || 0
33
33
 
34
34
  message = "#{pivot_count} Pivots, Key(s): #{key_count} key(s), #{object_count} objects(s)"
35
35
 
36
36
  output.detail(message)
37
37
 
38
- payload.value = (payload.value || []).flat_map { |object| unpivot.expand(object) }
38
+ payload[register] = payload[register].flat_map { |object| unpivot.expand(object) }
39
+ end
40
+
41
+ private
42
+
43
+ def pivot_count
44
+ unpivot.pivot_set.pivots.length
45
+ end
39
46
 
40
- nil
47
+ def key_count
48
+ unpivot.pivot_set.pivots.map { |p| p.keys.length }.sum
41
49
  end
42
50
  end
43
51
  end
@@ -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