burner 1.0.0.pre.alpha.5 → 1.0.0.pre.alpha.6

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -12
  3. data/exe/burner +2 -3
  4. data/lib/burner.rb +2 -0
  5. data/lib/burner/cli.rb +2 -0
  6. data/lib/burner/job.rb +25 -5
  7. data/lib/burner/jobs.rb +21 -41
  8. data/lib/burner/library.rb +30 -0
  9. data/lib/burner/library/collection/arrays_to_objects.rb +77 -0
  10. data/lib/burner/{jobs → library}/collection/graph.rb +3 -2
  11. data/lib/burner/{jobs → library}/collection/objects_to_arrays.rb +40 -4
  12. data/lib/burner/{jobs → library}/collection/shift.rb +5 -4
  13. data/lib/burner/{jobs → library}/collection/transform.rb +13 -9
  14. data/lib/burner/{jobs → library}/collection/unpivot.rb +7 -5
  15. data/lib/burner/{jobs → library}/collection/values.rb +5 -4
  16. data/lib/burner/{jobs → library}/deserialize/csv.rb +2 -1
  17. data/lib/burner/{jobs → library}/deserialize/json.rb +4 -1
  18. data/lib/burner/{jobs → library}/deserialize/yaml.rb +8 -2
  19. data/lib/burner/{jobs → library}/dummy.rb +3 -1
  20. data/lib/burner/{jobs → library}/echo.rb +3 -1
  21. data/lib/burner/{jobs → library}/io/base.rb +1 -1
  22. data/lib/burner/{jobs → library}/io/exist.rb +3 -1
  23. data/lib/burner/{jobs → library}/io/read.rb +4 -1
  24. data/lib/burner/{jobs → library}/io/write.rb +7 -2
  25. data/lib/burner/{jobs → library}/serialize/csv.rb +3 -2
  26. data/lib/burner/{jobs → library}/serialize/json.rb +4 -1
  27. data/lib/burner/{jobs → library}/serialize/yaml.rb +4 -1
  28. data/lib/burner/{jobs/set.rb → library/set_value.rb} +5 -2
  29. data/lib/burner/{jobs → library}/sleep.rb +3 -1
  30. data/lib/burner/modeling.rb +2 -0
  31. data/lib/burner/modeling/attribute.rb +29 -0
  32. data/lib/burner/modeling/attribute_renderer.rb +32 -0
  33. data/lib/burner/payload.rb +15 -12
  34. data/lib/burner/pipeline.rb +20 -1
  35. data/lib/burner/side_effects.rb +10 -0
  36. data/lib/burner/side_effects/written_file.rb +28 -0
  37. data/lib/burner/util.rb +10 -0
  38. data/lib/burner/util/arrayable.rb +30 -0
  39. data/lib/burner/version.rb +1 -1
  40. metadata +30 -26
  41. data/lib/burner/jobs/collection/arrays_to_objects.rb +0 -43
  42. data/lib/burner/jobs/collection/transform/attribute.rb +0 -33
  43. data/lib/burner/jobs/collection/transform/attribute_renderer.rb +0 -36
  44. data/lib/burner/written_file.rb +0 -28
@@ -7,14 +7,17 @@
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
23
  class Transform < Job
@@ -30,14 +33,15 @@ module Burner
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.value = array(payload.value).map { |row| transform(row, payload.time) }
41
45
 
42
46
  attr_count = attribute_renderers.length
43
47
  row_count = payload.value.length
@@ -8,11 +8,12 @@
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
19
  class Unpivot < Job
@@ -27,15 +28,16 @@ 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
+ pivot_count = unpivot.pivot_set.pivots.length
32
+ key_count = unpivot.pivot_set.pivots.map { |p| p.keys.length }.sum
33
+ payload.value = array(payload.value)
34
+ object_count = payload.value.length || 0
33
35
 
34
36
  message = "#{pivot_count} Pivots, Key(s): #{key_count} key(s), #{object_count} objects(s)"
35
37
 
36
38
  output.detail(message)
37
39
 
38
- payload.value = (payload.value || []).flat_map { |object| unpivot.expand(object) }
40
+ payload.value = payload.value.flat_map { |object| unpivot.expand(object) }
39
41
 
40
42
  nil
41
43
  end
@@ -8,11 +8,12 @@
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 call #values on each object.
14
14
  # If include_keys is true (it is false by default), then call #keys on the first
15
15
  # object and inject that as a "header" object.
16
+ #
16
17
  # Expected Payload#value input: array of objects.
17
18
  # Payload#value output: An array of arrays.
18
19
  class Values < Job
@@ -27,9 +28,9 @@ module Burner
27
28
  end
28
29
 
29
30
  def perform(_output, payload)
30
- keys = include_keys ? [keys(payload.value&.first)] : []
31
- values = (payload.value || []).map { |object| values(object) }
32
-
31
+ payload.value = array(payload.value)
32
+ keys = include_keys ? [keys(payload.value.first)] : []
33
+ values = payload.value.map { |object| values(object) }
33
34
  payload.value = keys + values
34
35
 
35
36
  nil
@@ -8,9 +8,10 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Deserialize
13
13
  # Take a CSV string and de-serialize into object(s).
14
+ #
14
15
  # Expected Payload#value input: nothing.
15
16
  # Payload#value output: an array of arrays. Each inner array represents one data row.
16
17
  class Csv < Job
@@ -8,9 +8,12 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Deserialize
13
13
  # Take a JSON string and deserialize into object(s).
14
+ #
15
+ # Expected Payload#value input: string of JSON data.
16
+ # Payload#value output: anything, as specified by the JSON de-serializer.
14
17
  class Json < Job
15
18
  def perform(_output, payload)
16
19
  payload.value = JSON.parse(payload.value)
@@ -8,9 +8,15 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Deserialize
13
- # Take a YAML string and deserialize into object(s).
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.
14
20
  class Yaml < Job
15
21
  attr_reader :safe
16
22
 
@@ -8,8 +8,10 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  # Do nothing.
13
+ #
14
+ # Note: this does not use Payload#value.
13
15
  class Dummy < Job
14
16
  def perform(_output, _payload)
15
17
  nil
@@ -8,8 +8,10 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
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
 
@@ -8,7 +8,7 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module IO
13
13
  # Common configuration/code for all IO Job subclasses.
14
14
  class Base < Job
@@ -10,10 +10,12 @@
10
10
  require_relative 'base'
11
11
 
12
12
  module Burner
13
- class Jobs
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
+ # Note: this does not use Payload#value.
17
19
  class Exist < Base
18
20
  attr_reader :short_circuit
19
21
 
@@ -10,9 +10,12 @@
10
10
  require_relative 'base'
11
11
 
12
12
  module Burner
13
- class Jobs
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
 
@@ -10,9 +10,12 @@
10
10
  require_relative 'base'
11
11
 
12
12
  module Burner
13
- class Jobs
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
 
@@ -35,12 +38,14 @@ module Burner
35
38
  File.open(compiled_path, mode) { |io| io.write(payload.value) }
36
39
  end.real
37
40
 
38
- payload.add_written_file(
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
 
47
+ payload.add_side_effect(side_effect)
48
+
44
49
  nil
45
50
  end
46
51
 
@@ -8,15 +8,16 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Serialize
13
13
  # Take an array of arrays and create a CSV.
14
+ #
14
15
  # Expected Payload#value input: array of arrays.
15
16
  # Payload#value output: a serialized CSV string.
16
17
  class Csv < Job
17
18
  def perform(_output, payload)
18
19
  payload.value = CSV.generate(options) do |csv|
19
- (payload.value || []).each do |row|
20
+ array(payload.value).each do |row|
20
21
  csv << row
21
22
  end
22
23
  end
@@ -8,9 +8,12 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Serialize
13
13
  # Treat value like a Ruby object and serialize it using JSON.
14
+ #
15
+ # Expected Payload#value input: anything.
16
+ # Payload#value output: string representing the output of the JSON serializer.
14
17
  class Json < Job
15
18
  def perform(_output, payload)
16
19
  payload.value = payload.value.to_json
@@ -8,9 +8,12 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  module Serialize
13
13
  # Treat value like a Ruby object and serialize it using YAML.
14
+ #
15
+ # Expected Payload#value input: anything.
16
+ # Payload#value output: string representing the output of the YAML serializer.
14
17
  class Yaml < Job
15
18
  def perform(_output, payload)
16
19
  payload.value = payload.value.to_yaml
@@ -8,9 +8,12 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  # Arbitrarily set value
13
- class Set < Job
13
+ #
14
+ # Expected Payload#value input: anything.
15
+ # Payload#value output: whatever value was specified in this job.
16
+ class SetValue < Job
14
17
  attr_reader :value
15
18
 
16
19
  def initialize(name:, value: nil)
@@ -8,8 +8,10 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- class Jobs
11
+ module Library
12
12
  # Arbitrarily put thread to sleep for X number of seconds
13
+ #
14
+ # Payload#value output: whatever value was specified in this job.
13
15
  class Sleep < Job
14
16
  attr_reader :seconds
15
17
 
@@ -7,4 +7,6 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
+ require_relative 'modeling/attribute'
11
+ require_relative 'modeling/attribute_renderer'
10
12
  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
+ # Defines a top-level key and the associated transformers for deriving the final value
13
+ # to set the key to.
14
+ class Attribute
15
+ acts_as_hashable
16
+
17
+ attr_reader :key, :transformers
18
+
19
+ def initialize(key:, transformers: [])
20
+ raise ArgumentError, 'key is required' if key.to_s.empty?
21
+
22
+ @key = key.to_s
23
+ @transformers = Realize::Transformers.array(transformers)
24
+
25
+ freeze
26
+ end
27
+ end
28
+ end
29
+ end
@@ -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
+ module Burner
11
+ module Modeling
12
+ # Composed of an Attribute instance and a Pipeline instance. It knows how to
13
+ # render/transform an Attribute. Since this library is data-first, these intermediary
14
+ # objects are necessary for non-data-centric modeling.
15
+ class AttributeRenderer
16
+ extend Forwardable
17
+
18
+ attr_reader :attribute, :pipeline
19
+
20
+ def_delegators :attribute, :key
21
+
22
+ def_delegators :pipeline, :transform
23
+
24
+ def initialize(attribute, resolver)
25
+ @attribute = attribute
26
+ @pipeline = Realize::Pipeline.new(attribute.transformers, resolver: resolver)
27
+
28
+ freeze
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,35 +7,38 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'written_file'
11
-
12
10
  module Burner
13
11
  # The input for all Job#perform methods. The main notion of this object is its "value"
14
12
  # attribute. This is dynamic and weak on purpose and is subject to whatever the Job#perform
15
13
  # methods decides it is. This definitely adds an order-of-magnitude complexity to this whole
16
14
  # library and lifecycle, but I am not sure there is any other way around it: trying to build
17
15
  # a generic, open-ended object pipeline to serve almost any use case.
16
+ #
17
+ # The side_effects attribute can also be utilized as a way for jobs to emit any data in a more
18
+ # structured/additive manner. The initial use case for this was for Burner's core IO jobs to
19
+ # report back the files it has written in a more structured data way (as opposed to simply
20
+ # writing some information to the output.)
18
21
  class Payload
19
22
  attr_accessor :value
20
23
 
21
24
  attr_reader :params,
22
- :time,
23
- :written_files
25
+ :side_effects,
26
+ :time
24
27
 
25
28
  def initialize(
26
29
  params: {},
30
+ side_effects: [],
27
31
  time: Time.now.utc,
28
- value: nil,
29
- written_files: []
32
+ value: nil
30
33
  )
31
- @params = params || {}
32
- @time = time || Time.now.utc
33
- @value = value
34
- @written_files = written_files || []
34
+ @params = params || {}
35
+ @side_effects = side_effects || []
36
+ @time = time || Time.now.utc
37
+ @value = value
35
38
  end
36
39
 
37
- def add_written_file(written_file)
38
- tap { written_files << WrittenFile.make(written_file) }
40
+ def add_side_effect(side_effect)
41
+ tap { side_effects << side_effect }
39
42
  end
40
43
  end
41
44
  end