burner 1.0.0.pre.alpha.3 → 1.0.0.pre.alpha.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca551c813e385ad98bd5856ff74be816d43a57fe60003da061536b9a0a27b2a6
4
- data.tar.gz: 484f1490830bd27d5eaeae2dd097ac8ee0a2024d0d231ae09f2607ab1275cd9e
3
+ metadata.gz: 6e96a8d656466219851ff06ebced5aacac54fe0b6a78de2e1f0e5bf76480e1ca
4
+ data.tar.gz: a4ba7e8d001f7e25ee7c404ed539089e51f295098d0062d14a3fcf473db64d7d
5
5
  SHA512:
6
- metadata.gz: ee7bdfe6e3571918730bf6ff983d88b3cbb10514c90feae0df736167dfaeee4cce41f3e307c743012a528e43b0c0251e60b07008908e5a1e400aec7b65fe4005
7
- data.tar.gz: ebd8cde5363fcc8c509d4856ec9978c14a1601d85f492a92f76e5e35dd10629fbd19566d51d13093b8216366035cbda9b4fbef610995796f17881d09e3e45d81
6
+ metadata.gz: 1cc60f86ee2931902ab8e6870f5bda78f953e6740c2a5d76dc306c12457e93511646b9cbfe4db8fa583a3242bc1d361715011ea98ea5682045356a6c6f269808
7
+ data.tar.gz: 5b9cf06f4426d1629f9c6672ee5c469052863e07bf01079b021494f5b6e0bc4ae0ef57be64e1a37d1652803039a1e11061c5415ca4a83f39672d4c55085feeac
data/README.md CHANGED
@@ -73,19 +73,21 @@ params = {
73
73
  input_file: 'input.json',
74
74
  output_file: 'output.yaml'
75
75
  }
76
+
77
+ payload = Burner::Payload.new(params: params)
76
78
  ````
77
79
 
78
80
  Assuming we are running this script from a directory where an `input.json` file exists, we can then programatically process the pipeline:
79
81
 
80
82
  ````ruby
81
- Burner::Pipeline.make(pipeline).execute(params: params)
83
+ Burner::Pipeline.make(pipeline).execute(payload: payload)
82
84
  ````
83
85
 
84
86
  We should now see a output.yaml file created.
85
87
 
86
88
  Some notes:
87
89
 
88
- * Some values are able to be string-interpolated using the provided params. This allows for the passing runtime configuration/data into pipelines/jobs.
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.
89
91
  * The job's ID can be accessed using the `__id` key.
90
92
  * The current job's payload value can be accessed using the `__value` key.
91
93
  * Jobs can be re-used (just like the output_id and output_value jobs).
@@ -116,8 +118,9 @@ end
116
118
 
117
119
  string_out = StringOut.new
118
120
  output = Burner::Output.new(outs: string_out)
121
+ payload = Burner::Payload.new(params: params)
119
122
 
120
- Burner::Pipeline.make(pipeline).execute(output: output, params: params)
123
+ Burner::Pipeline.make(pipeline).execute(output: output, payload: payload)
121
124
 
122
125
  log = string_out.read
123
126
  ````
@@ -228,15 +231,37 @@ Burner::Cli.new(args).invoke
228
231
 
229
232
  This library only ships with very basic, rudimentary jobs that are meant to just serve as a baseline:
230
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.
231
246
  * **deserialize/json** []: Treat input as a string and de-serialize it to JSON.
232
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`
233
- * **dummy** []: Do nothing
234
- * **echo** [message]: Write a message to the output. The message parameter can be interpolated using params.
235
- * **io/exist** [path, short_circuit]: Check to see if a file exists. The path parameter can be interpolated using params. If short_circuit was set to true (defaults to false) and the file does not exist then the pipeline will be short-circuited.
236
- * **io/read** [binary, path]: Read in a local file. The path parameter can be interpolated using params. If the contents are binary, pass in `binary: true` to open it up in binary+read mode.
237
- * **io/write** [binary, path]: Write to a local file. The path parameter can be interpolated using params. If the contents are binary, pass in `binary: true` to open it up in binary+write mode.
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.
238
258
  * **serialize/json** []: Convert value to JSON.
239
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`.
240
265
  * **set** [value]: Set the value to any arbitrary value.
241
266
  * **sleep** [seconds]: Sleep the thread for X number of seconds.
242
267
 
@@ -249,7 +274,7 @@ Let's say we would like to register a job to parse a CSV:
249
274
 
250
275
  ````ruby
251
276
  class ParseCsv < Burner::Job
252
- def perform(output, payload, params)
277
+ def perform(output, payload)
253
278
  payload.value = CSV.parse(payload.value, headers: true).map(&:to_h)
254
279
 
255
280
  nil
@@ -309,7 +334,9 @@ params = {
309
334
  output_file: File.join(TEMP_DIR, "#{SecureRandom.uuid}.yaml")
310
335
  }
311
336
 
312
- Burner::Pipeline.make(pipeline).execute(output: output, params: params)
337
+ payload = Burner::Payload.new(params: params)
338
+
339
+ Burner::Pipeline.make(pipeline).execute(output: output, payload: payload)
313
340
  ````
314
341
 
315
342
  ## Contributing
@@ -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')
@@ -9,12 +9,21 @@
9
9
 
10
10
  require 'acts_as_hashable'
11
11
  require 'benchmark'
12
+ require 'csv'
12
13
  require 'forwardable'
14
+ require 'hash_math'
15
+ require 'hashematics'
13
16
  require 'json'
14
17
  require 'objectable'
18
+ require 'realize'
15
19
  require 'securerandom'
16
20
  require 'singleton'
17
21
  require 'stringento'
22
+ require 'time'
18
23
  require 'yaml'
19
24
 
25
+ # Common/Shared
26
+ require_relative 'burner/modeling'
27
+
28
+ # Main Entrypoint(s)
20
29
  require_relative 'burner/cli'
@@ -12,20 +12,18 @@ require_relative 'pipeline'
12
12
  module Burner
13
13
  # Process a single string as a Pipeline. This is mainly to back the command-line interface.
14
14
  class Cli
15
- attr_reader :params, :pipeline
15
+ attr_reader :payload, :pipeline
16
16
 
17
17
  def initialize(args)
18
- path = args.first
19
- cli_params = extract_cli_params(args)
20
- config = read_yaml(path)
21
- @pipeline = Burner::Pipeline.make(jobs: config['jobs'], steps: config['steps'])
22
- @params = (config['params'] || {}).merge(cli_params)
18
+ path = args.first
19
+ params = extract_cli_params(args)
20
+ config = read_yaml(path)
21
+ @pipeline = Burner::Pipeline.make(jobs: config['jobs'], steps: config['steps'])
22
+ @payload = Payload.new(params: params)
23
23
  end
24
24
 
25
- def execute(output: Output.new, params: {}, payload: Payload.new)
26
- final_params = @params.merge(params || {})
27
-
28
- pipeline.execute(output: output, params: final_params, payload: payload)
25
+ def execute
26
+ pipeline.execute(payload: payload)
29
27
  end
30
28
 
31
29
  private
@@ -28,8 +28,10 @@ module Burner
28
28
 
29
29
  private
30
30
 
31
- def eval_string_template(expression, input)
32
- string_template.evaluate(expression, input)
31
+ def job_string_template(expression, output, payload)
32
+ templatable_params = payload.params.merge(__id: output.id, __value: payload.value)
33
+
34
+ string_template.evaluate(expression, templatable_params)
33
35
  end
34
36
  end
35
37
  end
@@ -8,6 +8,13 @@
8
8
  #
9
9
 
10
10
  require_relative 'job'
11
+ require_relative 'jobs/collection/arrays_to_objects'
12
+ require_relative 'jobs/collection/graph'
13
+ require_relative 'jobs/collection/objects_to_arrays'
14
+ require_relative 'jobs/collection/shift'
15
+ require_relative 'jobs/collection/transform'
16
+ require_relative 'jobs/collection/unpivot'
17
+ require_relative 'jobs/deserialize/csv'
11
18
  require_relative 'jobs/deserialize/json'
12
19
  require_relative 'jobs/deserialize/yaml'
13
20
  require_relative 'jobs/dummy'
@@ -15,6 +22,7 @@ require_relative 'jobs/echo'
15
22
  require_relative 'jobs/io/exist'
16
23
  require_relative 'jobs/io/read'
17
24
  require_relative 'jobs/io/write'
25
+ require_relative 'jobs/serialize/csv'
18
26
  require_relative 'jobs/serialize/json'
19
27
  require_relative 'jobs/serialize/yaml'
20
28
  require_relative 'jobs/set'
@@ -27,16 +35,24 @@ module Burner
27
35
  class Jobs
28
36
  acts_as_hashable_factory
29
37
 
30
- register 'deserialize/json', Deserialize::Json
31
- register 'deserialize/yaml', Deserialize::Yaml
32
- register 'dummy', '', Dummy
33
- register 'echo', Echo
34
- register 'io/exist', IO::Exist
35
- register 'io/read', IO::Read
36
- register 'io/write', IO::Write
37
- register 'serialize/json', Serialize::Json
38
- register 'serialize/yaml', Serialize::Yaml
39
- register 'set', Set
40
- register 'sleep', Sleep
38
+ register 'collection/arrays_to_objects', Collection::ArraysToObjects
39
+ register 'collection/graph', Collection::Graph
40
+ register 'collection/objects_to_arrays', Collection::ObjectsToArrays
41
+ register 'collection/shift', Collection::Shift
42
+ register 'collection/transform', Collection::Transform
43
+ register 'collection/unpivot', Collection::Unpivot
44
+ register 'deserialize/csv', Deserialize::Csv
45
+ register 'deserialize/json', Deserialize::Json
46
+ register 'deserialize/yaml', Deserialize::Yaml
47
+ register 'dummy', '', Dummy
48
+ register 'echo', Echo
49
+ register 'io/exist', IO::Exist
50
+ register 'io/read', IO::Read
51
+ register 'io/write', IO::Write
52
+ register 'serialize/csv', Serialize::Csv
53
+ register 'serialize/json', Serialize::Json
54
+ register 'serialize/yaml', Serialize::Yaml
55
+ register 'set', Set
56
+ register 'sleep', Sleep
41
57
  end
42
58
  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
+ module Burner
11
+ class Jobs
12
+ module Collection
13
+ # Convert an array of arrays to an array of objects.
14
+ # Expected Payload#value input: array of arrays.
15
+ # Payload#value output: An array of hashes.
16
+ class ArraysToObjects < Job
17
+ attr_reader :mappings
18
+
19
+ def initialize(name:, mappings: [])
20
+ super(name: name)
21
+
22
+ @mappings = Modeling::KeyIndexMapping.array(mappings)
23
+
24
+ freeze
25
+ end
26
+
27
+ def perform(_output, payload)
28
+ payload.value = (payload.value || []).map { |array| index_to_key_map(array) }
29
+
30
+ nil
31
+ end
32
+
33
+ private
34
+
35
+ def index_to_key_map(array)
36
+ mappings.each_with_object({}) do |mapping, memo|
37
+ memo[mapping.key] = array[mapping.index]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ 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
+ module Burner
11
+ class Jobs
12
+ module Collection
13
+ # Take an array of (denormalized) objects and create an object hierarchy from them.
14
+ # Under the hood it uses Hashematics: https://github.com/bluemarblepayroll/hashematics.
15
+ # Expected Payload#value input: array of objects.
16
+ # Payload#value output: An array of objects.
17
+ class Graph < Job
18
+ attr_reader :key, :groups
19
+
20
+ def initialize(name:, key:, config: Hashematics::Configuration.new)
21
+ super(name: name)
22
+
23
+ raise ArgumentError, 'key is required' if key.to_s.empty?
24
+
25
+ @groups = Hashematics::Configuration.new(config).groups
26
+ @key = key.to_s
27
+
28
+ freeze
29
+ end
30
+
31
+ def perform(output, payload)
32
+ graph = Hashematics::Graph.new(groups).add(payload.value || [])
33
+
34
+ output.detail("Graphing: #{key}")
35
+
36
+ payload.value = graph.data(key)
37
+
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,54 @@
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 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.
15
+ # Expected Payload#value input: array of hashes.
16
+ # Payload#value output: An array of arrays.
17
+ class ObjectsToArrays < Job
18
+ attr_reader :mappings
19
+
20
+ # If you wish to support nested objects you can pass in a string to use as a
21
+ # key path separator. For example: if you would like to recognize dot-notation for
22
+ # nested hashes then set separator to '.'.
23
+ def initialize(name:, mappings: [], separator: '')
24
+ super(name: name)
25
+
26
+ @mappings = Modeling::KeyIndexMapping.array(mappings)
27
+ @resolver = Objectable.resolver(separator: separator.to_s)
28
+
29
+ freeze
30
+ end
31
+
32
+ def perform(_output, payload)
33
+ payload.value = (payload.value || []).map { |object| key_to_index_map(object) }
34
+
35
+ nil
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :resolver
41
+
42
+ def key_to_index_map(object)
43
+ mappings.each_with_object(prototype_array) do |mapping, memo|
44
+ memo[mapping.index] = resolver.get(object, mapping.key)
45
+ end
46
+ end
47
+
48
+ def prototype_array
49
+ Array.new(mappings.length)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ 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
+ module Burner
11
+ class Jobs
12
+ module Collection
13
+ # Take an array and remove the first N elements, where N is specified by the amount
14
+ # attribute.
15
+ # Expected Payload#value input: nothing.
16
+ # Payload#value output: An array with N beginning elements removed.
17
+ class Shift < Job
18
+ DEFAULT_AMOUNT = 0
19
+
20
+ private_constant :DEFAULT_AMOUNT
21
+
22
+ attr_reader :amount
23
+
24
+ def initialize(name:, amount: DEFAULT_AMOUNT)
25
+ super(name: name)
26
+
27
+ @amount = amount.to_i
28
+
29
+ freeze
30
+ end
31
+
32
+ def perform(output, payload)
33
+ output.detail("Shifting #{amount} entries.")
34
+
35
+ payload.value ||= []
36
+ payload.value.shift(amount)
37
+
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,64 @@
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 'transform/attribute'
11
+ require_relative 'transform/attribute_renderer'
12
+
13
+ module Burner
14
+ class Jobs
15
+ 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
18
+ # Expected Payload#value input: array of objects.
19
+ # Payload#value output: An array of objects.
20
+ class Transform < Job
21
+ BLANK = ''
22
+
23
+ attr_reader :attribute_renderers,
24
+ :exclusive,
25
+ :resolver
26
+
27
+ def initialize(name:, attributes: [], exclusive: false, separator: BLANK)
28
+ super(name: name)
29
+
30
+ @resolver = Objectable.resolver(separator: separator)
31
+ @exclusive = exclusive || false
32
+
33
+ @attribute_renderers = Attribute.array(attributes)
34
+ .map { |a| AttributeRenderer.new(a, resolver) }
35
+
36
+ freeze
37
+ end
38
+
39
+ def perform(output, payload)
40
+ payload.value = (payload.value || []).map { |row| transform(row, payload.time) }
41
+
42
+ attr_count = attribute_renderers.length
43
+ row_count = payload.value.length
44
+
45
+ output.detail("Transformed #{attr_count} attributes(s) for #{row_count} row(s)")
46
+
47
+ nil
48
+ end
49
+
50
+ private
51
+
52
+ def transform(row, time)
53
+ outgoing_row = exclusive ? {} : row
54
+
55
+ attribute_renderers.each_with_object(outgoing_row) do |attribute_renderer, memo|
56
+ value = attribute_renderer.transform(row, time)
57
+
58
+ resolver.set(memo, attribute_renderer.key, value)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
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 Collection
13
+ class Transform < Job
14
+ # Defines a top-level key and the associated transformers for deriving the final value
15
+ # to set the key to.
16
+ class Attribute
17
+ acts_as_hashable
18
+
19
+ attr_reader :key, :transformers
20
+
21
+ def initialize(key:, transformers: [])
22
+ raise ArgumentError, 'key is required' if key.to_s.empty?
23
+
24
+ @key = key.to_s
25
+ @transformers = Realize::Transformers.array(transformers)
26
+
27
+ freeze
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
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 Collection
13
+ class Transform < Job
14
+ # Composed of an Attribute instance and a Pipeline instance. It knows how to
15
+ # render/transform an Attribute. Since this library is data-first, these intermediary
16
+ # objects are necessary for non-data-centric modeling.
17
+ class AttributeRenderer
18
+ extend Forwardable
19
+
20
+ attr_reader :attribute, :pipeline
21
+
22
+ def_delegators :attribute, :key
23
+
24
+ def_delegators :pipeline, :transform
25
+
26
+ def initialize(attribute, resolver)
27
+ @attribute = attribute
28
+ @pipeline = Realize::Pipeline.new(attribute.transformers, resolver: resolver)
29
+
30
+ freeze
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
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 Collection
13
+ # Take an array of objects and un-pivot groups of keys into rows.
14
+ # Under the hood it uses HashMath's Unpivot class:
15
+ # https://github.com/bluemarblepayroll/hash_math
16
+ # Expected Payload#value input: array of objects.
17
+ # Payload#value output: An array of objects.
18
+ class Unpivot < Job
19
+ attr_reader :unpivot
20
+
21
+ def initialize(name:, pivot_set: HashMath::Unpivot::PivotSet.new)
22
+ super(name: name)
23
+
24
+ @unpivot = HashMath::Unpivot.new(pivot_set)
25
+
26
+ freeze
27
+ end
28
+
29
+ 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
33
+
34
+ message = "#{pivot_count} Pivots, Key(s): #{key_count} key(s), #{object_count} objects(s)"
35
+
36
+ output.detail(message)
37
+
38
+ payload.value = (payload.value || []).flat_map { |object| unpivot.expand(object) }
39
+
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -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
+ class Jobs
12
+ module Deserialize
13
+ # Take a CSV string and de-serialize into object(s).
14
+ # Expected Payload#value input: nothing.
15
+ # Payload#value output: an array of arrays. Each inner array represents one data row.
16
+ class Csv < Job
17
+ # This currently only supports returning an array of arrays, including the header row.
18
+ # In the future this could be extended to offer more customizable options, such as
19
+ # making it return an array of hashes with the columns mapped, etc.)
20
+ def perform(_output, payload)
21
+ payload.value = CSV.new(payload.value, headers: false).to_a
22
+
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -12,7 +12,7 @@ module Burner
12
12
  module Deserialize
13
13
  # Take a JSON string and deserialize into object(s).
14
14
  class Json < Job
15
- def perform(_output, payload, _params)
15
+ def perform(_output, payload)
16
16
  payload.value = JSON.parse(payload.value)
17
17
 
18
18
  nil
@@ -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, _params)
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)
@@ -11,7 +11,7 @@ module Burner
11
11
  class Jobs
12
12
  # Do nothing.
13
13
  class Dummy < Job
14
- def perform(_output, _payload, _params)
14
+ def perform(_output, _payload)
15
15
  nil
16
16
  end
17
17
  end
@@ -21,8 +21,8 @@ module Burner
21
21
  freeze
22
22
  end
23
23
 
24
- def perform(output, _payload, params)
25
- compiled_message = eval_string_template(message, params)
24
+ def perform(output, payload)
25
+ compiled_message = job_string_template(message, output, payload)
26
26
 
27
27
  output.detail(compiled_message)
28
28
 
@@ -21,12 +21,6 @@ module Burner
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
@@ -25,8 +25,8 @@ module Burner
25
25
  freeze
26
26
  end
27
27
 
28
- def perform(output, _payload, params)
29
- compiled_path = compile_path(params)
28
+ def perform(output, payload)
29
+ compiled_path = job_string_template(path, output, payload)
30
30
 
31
31
  exists = File.exist?(compiled_path)
32
32
  verb = exists ? 'does' : 'does not'
@@ -24,8 +24,8 @@ module Burner
24
24
  freeze
25
25
  end
26
26
 
27
- def perform(output, payload, params)
28
- compiled_path = compile_path(params)
27
+ def perform(output, payload)
28
+ compiled_path = job_string_template(path, output, payload)
29
29
 
30
30
  output.detail("Reading: #{compiled_path}")
31
31
 
@@ -24,8 +24,8 @@ module Burner
24
24
  freeze
25
25
  end
26
26
 
27
- def perform(output, payload, params)
28
- compiled_path = compile_path(params)
27
+ def perform(output, payload)
28
+ compiled_path = job_string_template(path, output, payload)
29
29
 
30
30
  ensure_directory_exists(output, compiled_path)
31
31
 
@@ -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
@@ -12,7 +12,7 @@ module Burner
12
12
  module Serialize
13
13
  # Treat value like a Ruby object and serialize it using JSON.
14
14
  class Json < Job
15
- def perform(_output, payload, _params)
15
+ def perform(_output, payload)
16
16
  payload.value = payload.value.to_json
17
17
 
18
18
  nil
@@ -12,7 +12,7 @@ module Burner
12
12
  module Serialize
13
13
  # Treat value like a Ruby object and serialize it using YAML.
14
14
  class Yaml < Job
15
- def perform(_output, payload, _params)
15
+ def perform(_output, payload)
16
16
  payload.value = payload.value.to_yaml
17
17
 
18
18
  nil
@@ -21,7 +21,7 @@ module Burner
21
21
  freeze
22
22
  end
23
23
 
24
- def perform(_output, payload, _params)
24
+ def perform(_output, payload)
25
25
  payload.value = value
26
26
 
27
27
  nil
@@ -21,7 +21,7 @@ module Burner
21
21
  freeze
22
22
  end
23
23
 
24
- def perform(output, _payload, _params)
24
+ def perform(output, _payload)
25
25
  output.detail("Going to sleep for #{seconds} second(s)")
26
26
 
27
27
  Kernel.sleep(seconds)
@@ -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
@@ -18,10 +18,18 @@ module Burner
18
18
  class Payload
19
19
  attr_accessor :value
20
20
 
21
- attr_reader :context, :written_files
21
+ attr_reader :params,
22
+ :time,
23
+ :written_files
22
24
 
23
- def initialize(context: {}, value: nil, written_files: [])
24
- @context = context || {}
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
25
33
  @value = value
26
34
  @written_files = written_files || []
27
35
  end
@@ -35,15 +35,15 @@ module Burner
35
35
  end
36
36
 
37
37
  # The main entry-point for kicking off a pipeline.
38
- def execute(output: Output.new, params: {}, payload: Payload.new)
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
45
  steps.each do |step|
46
- return_value = step.perform(output, payload, params)
46
+ return_value = step.perform(output, payload)
47
47
 
48
48
  if return_value.is_a?(FalseClass)
49
49
  output.detail('Job returned false, ending pipeline.')
@@ -28,15 +28,13 @@ module Burner
28
28
  freeze
29
29
  end
30
30
 
31
- def perform(output, payload, params)
31
+ def perform(output, payload)
32
32
  return_value = nil
33
33
 
34
34
  output.title("#{job.class.name}#{SEPARATOR}#{job.name}")
35
35
 
36
36
  time_in_seconds = Benchmark.measure do
37
- job_params = (params || {}).merge(__id: output.id, __value: payload.value)
38
-
39
- return_value = job.perform(output, payload, job_params)
37
+ return_value = job.perform(output, payload)
40
38
  end.real.round(3)
41
39
 
42
40
  output.complete(time_in_seconds)
@@ -8,5 +8,5 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- VERSION = '1.0.0-alpha.3'
11
+ VERSION = '1.0.0-alpha.4'
12
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: burner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha.3
4
+ version: 1.0.0.pre.alpha.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Ruggio
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-15 00:00:00.000000000 Z
11
+ date: 2020-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts_as_hashable
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hashematics
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hash_math
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: objectable
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +66,20 @@ dependencies:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
68
  version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: realize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: stringento
43
85
  requirement: !ruby/object:Gem::Requirement
@@ -179,6 +221,15 @@ files:
179
221
  - lib/burner/cli.rb
180
222
  - lib/burner/job.rb
181
223
  - lib/burner/jobs.rb
224
+ - lib/burner/jobs/collection/arrays_to_objects.rb
225
+ - lib/burner/jobs/collection/graph.rb
226
+ - lib/burner/jobs/collection/objects_to_arrays.rb
227
+ - lib/burner/jobs/collection/shift.rb
228
+ - lib/burner/jobs/collection/transform.rb
229
+ - lib/burner/jobs/collection/transform/attribute.rb
230
+ - lib/burner/jobs/collection/transform/attribute_renderer.rb
231
+ - lib/burner/jobs/collection/unpivot.rb
232
+ - lib/burner/jobs/deserialize/csv.rb
182
233
  - lib/burner/jobs/deserialize/json.rb
183
234
  - lib/burner/jobs/deserialize/yaml.rb
184
235
  - lib/burner/jobs/dummy.rb
@@ -187,10 +238,13 @@ files:
187
238
  - lib/burner/jobs/io/exist.rb
188
239
  - lib/burner/jobs/io/read.rb
189
240
  - lib/burner/jobs/io/write.rb
241
+ - lib/burner/jobs/serialize/csv.rb
190
242
  - lib/burner/jobs/serialize/json.rb
191
243
  - lib/burner/jobs/serialize/yaml.rb
192
244
  - lib/burner/jobs/set.rb
193
245
  - lib/burner/jobs/sleep.rb
246
+ - lib/burner/modeling.rb
247
+ - lib/burner/modeling/key_index_mapping.rb
194
248
  - lib/burner/output.rb
195
249
  - lib/burner/payload.rb
196
250
  - lib/burner/pipeline.rb