burner 1.0.0.pre.alpha.6 → 1.0.0.pre.alpha.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/README.md +22 -18
  4. data/lib/burner/job.rb +4 -6
  5. data/lib/burner/job_with_register.rb +24 -0
  6. data/lib/burner/library.rb +3 -1
  7. data/lib/burner/library/collection/arrays_to_objects.rb +4 -6
  8. data/lib/burner/library/collection/graph.rb +5 -7
  9. data/lib/burner/library/collection/objects_to_arrays.rb +4 -6
  10. data/lib/burner/library/collection/shift.rb +4 -6
  11. data/lib/burner/library/collection/transform.rb +5 -7
  12. data/lib/burner/library/collection/unpivot.rb +15 -9
  13. data/lib/burner/library/collection/validate.rb +89 -0
  14. data/lib/burner/library/collection/values.rb +7 -9
  15. data/lib/burner/library/deserialize/csv.rb +2 -4
  16. data/lib/burner/library/deserialize/json.rb +2 -4
  17. data/lib/burner/library/deserialize/yaml.rb +5 -5
  18. data/lib/burner/library/dummy.rb +1 -3
  19. data/lib/burner/library/echo.rb +0 -2
  20. data/lib/burner/library/io/base.rb +3 -3
  21. data/lib/burner/library/io/exist.rb +8 -8
  22. data/lib/burner/library/io/read.rb +3 -5
  23. data/lib/burner/library/io/write.rb +3 -5
  24. data/lib/burner/library/serialize/csv.rb +3 -5
  25. data/lib/burner/library/serialize/json.rb +2 -4
  26. data/lib/burner/library/serialize/yaml.rb +2 -4
  27. data/lib/burner/library/set_value.rb +4 -6
  28. data/lib/burner/library/sleep.rb +0 -2
  29. data/lib/burner/modeling.rb +1 -0
  30. data/lib/burner/modeling/validations.rb +23 -0
  31. data/lib/burner/modeling/validations/base.rb +35 -0
  32. data/lib/burner/modeling/validations/blank.rb +31 -0
  33. data/lib/burner/modeling/validations/present.rb +31 -0
  34. data/lib/burner/payload.rb +39 -5
  35. data/lib/burner/pipeline.rb +3 -3
  36. data/lib/burner/step.rb +1 -5
  37. data/lib/burner/util.rb +1 -0
  38. data/lib/burner/util/string_template.rb +42 -0
  39. data/lib/burner/version.rb +1 -1
  40. metadata +9 -3
  41. data/lib/burner/string_template.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73263c161bce669b99286160acb7129d33c529162da964b72fd34321cc7e3724
4
- data.tar.gz: 18fd59799a73d9a8a3bf691a9dd3f8a004d442b37862bf83cbe347fbddd7e49a
3
+ metadata.gz: 8cb851b5c317d3566c1b3b77c9c904e26970de8e0ce4329e2c8b83336c372b02
4
+ data.tar.gz: 0ad28e56c8b52ceede4ed321812e45edfaef2bcd4000428738c348e5e849092a
5
5
  SHA512:
6
- metadata.gz: 7db0c2be13a882885b97681ec7c9fd60b6702368d673d97ba0aa484b446db0c77c9f823adf03c8d9e970b2078b43a99d2d159ba97df345124a461497718b7046
7
- data.tar.gz: 509382aa7fb48e116b8b3eb145a8b8226c1e4d4282eec1aabb10dcc9f7b333b906a7887d97cee4a96ae730dd7c02a0b0d4cd4a7b06dbfcc90349cfd82767e048
6
+ metadata.gz: cf5bc810ffa5dba106e6538dbab2f44e6763f5ad073da2b645e46eff3e976e61f9a0d551ae4d5ad9998c5c1d87b5e2913b902901020ad77f20b4c29800321df5
7
+ data.tar.gz: ef83de8173fbad28c2cc07c44f101175ebe9c60d5a9275d2e7280ba1b621a8c51082504ae5d5c917916d398a7a89d06ca7416dd08ba7148ebebb87cd42dd7b05
@@ -31,3 +31,5 @@ Style/TrailingCommaInHashLiteral:
31
31
  Style/TrailingCommaInArrayLiteral:
32
32
  Enabled: false
33
33
 
34
+ Metrics/ParameterLists:
35
+ CountKeywordArgs: false
data/README.md CHANGED
@@ -233,39 +233,43 @@ This library only ships with very basic, rudimentary jobs that are meant to just
233
233
 
234
234
  #### Collection
235
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
- * **collection/values** [include_keys]: Take an array of objects and call `#values` on each object. If include_keys is true (it is false by default), then call `#keys` on the first object and inject that as a "header" object.
236
+ * **collection/arrays_to_objects** [mappings, register]: Convert an array of arrays to an array of objects.
237
+ * **collection/graph** [config, key, register]: 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, register]: Convert an array of objects to an array of arrays.
239
+ * **collection/shift** [amount, register]: Remove the first N number of elements from an array.
240
+ * **collection/transform** [attributes, exclusive, separator, register]: 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, register]: 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
+ * **collection/validate** [invalid_register, join_char, message_key, register, separator, validations]: Take an array of objects, run it through each declared validator, and split the objects into two registers. The valid objects will be split into the current register while the invalid ones will go into the invalid_register as declared. Optional arguments, join_char and message_key, help determine the compiled error messages. The separator option can be utilized to use dot-notation for validating keys. See each validation's options by viewing their classes within the `lib/modeling/validations` directory.
243
+ * **collection/values** [include_keys, register]: Take an array of objects and call `#values` on each object. If include_keys is true (it is false by default), then call `#keys` on the first object and inject that as a "header" object.
243
244
 
244
245
  #### De-serialization
245
246
 
246
- * **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.
247
- * **deserialize/json** []: Treat input as a string and de-serialize it to JSON.
248
- * **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`
247
+ * **deserialize/csv** [register]: 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.
248
+ * **deserialize/json** [register]: Treat input as a string and de-serialize it to JSON.
249
+ * **deserialize/yaml** [register, 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`
249
250
 
250
251
  #### IO
251
252
 
252
253
  * **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.
253
- * **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.
254
- * **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
+ * **io/read** [binary, path, register]: 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.
255
+ * **io/write** [binary, path, register]: 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.
255
256
 
256
257
  #### Serialization
257
258
 
258
- * **serialize/csv** []: Take an array of arrays and create a CSV.
259
- * **serialize/json** []: Convert value to JSON.
260
- * **serialize/yaml** []: Convert value to YAML.
259
+ * **serialize/csv** [register]: Take an array of arrays and create a CSV.
260
+ * **serialize/json** [register]: Convert value to JSON.
261
+ * **serialize/yaml** [register]: Convert value to YAML.
261
262
 
262
263
  #### General
263
264
 
264
265
  * **dummy** []: Do nothing
265
266
  * **echo** [message]: Write a message to the output. The message parameter can be interpolated using `Payload#params`.
266
- * **set** [value]: Set the value to any arbitrary value.
267
+ * **set** [register, value]: Set the value to any arbitrary value.
267
268
  * **sleep** [seconds]: Sleep the thread for X number of seconds.
268
269
 
270
+ Notes:
271
+
272
+ * If you see that a job accepts a 'register' attribute/argument, that indicates a job will access and/or mutate the payload. The register indicates which part of the payload the job will interact with. This allows jobs to be placed into 'lanes'. If register is not specified, then the default register is used.
269
273
 
270
274
  ### Adding & Registering Jobs
271
275
 
@@ -274,9 +278,9 @@ Where this library shines is when additional jobs are plugged in. Burner uses i
274
278
  Let's say we would like to register a job to parse a CSV:
275
279
 
276
280
  ````ruby
277
- class ParseCsv < Burner::Job
281
+ class ParseCsv < Burner::JobWithRegister
278
282
  def perform(output, payload)
279
- payload.value = CSV.parse(payload.value, headers: true).map(&:to_h)
283
+ payload[register] = CSV.parse(payload[register], headers: true).map(&:to_h)
280
284
 
281
285
  nil
282
286
  end
@@ -7,11 +7,9 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'string_template'
11
-
12
10
  module Burner
13
11
  # Abstract base class for all job subclasses. The only public method a subclass needs to
14
- # implement #perform(params, payload, reporter) and then you can register it for use using
12
+ # implement #perform(output, payload) and then you can register it for use using
15
13
  # the Burner::Jobs factory class method #register. An example of a registration:
16
14
  # Burner::Jobs.register('your_class', YourClass)
17
15
  class Job
@@ -26,7 +24,7 @@ module Burner
26
24
  @name = name.to_s
27
25
  end
28
26
 
29
- # There are only two requirements to be considered a valid Burner Job:
27
+ # There are only a few requirements to be considered a valid Burner Job:
30
28
  # 1. The class responds to #name
31
29
  # 2. The class responds to #perform(output, payload)
32
30
  #
@@ -49,9 +47,9 @@ module Burner
49
47
  protected
50
48
 
51
49
  def job_string_template(expression, output, payload)
52
- templatable_params = payload.params.merge(__id: output.id, __value: payload.value)
50
+ templatable_params = payload.params.merge(__id: output.id, __value: payload[''])
53
51
 
54
- StringTemplate.instance.evaluate(expression, templatable_params)
52
+ Util::StringTemplate.instance.evaluate(expression, templatable_params)
55
53
  end
56
54
  end
57
55
  end
@@ -0,0 +1,24 @@
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'
11
+
12
+ module Burner
13
+ # Add on a register attribute to the configuration for a job. This indicates that a job
14
+ # either accesses and/or mutates the payload's registers.
15
+ class JobWithRegister < Job
16
+ attr_reader :register
17
+
18
+ def initialize(name:, register: '')
19
+ super(name: name)
20
+
21
+ @register = register.to_s
22
+ end
23
+ end
24
+ end
@@ -7,13 +7,15 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'job'
10
+ require_relative 'job_with_register'
11
+
11
12
  require_relative 'library/collection/arrays_to_objects'
12
13
  require_relative 'library/collection/graph'
13
14
  require_relative 'library/collection/objects_to_arrays'
14
15
  require_relative 'library/collection/shift'
15
16
  require_relative 'library/collection/transform'
16
17
  require_relative 'library/collection/unpivot'
18
+ require_relative 'library/collection/validate'
17
19
  require_relative 'library/collection/values'
18
20
  require_relative 'library/deserialize/csv'
19
21
  require_relative 'library/deserialize/json'
@@ -47,11 +47,11 @@ module Burner
47
47
  # }
48
48
  #
49
49
  # Burner::Pipeline.make(config).execute
50
- class ArraysToObjects < Job
50
+ class ArraysToObjects < JobWithRegister
51
51
  attr_reader :mappings
52
52
 
53
- def initialize(name:, mappings: [])
54
- super(name: name)
53
+ def initialize(name:, mappings: [], register: '')
54
+ super(name: name, register: register)
55
55
 
56
56
  @mappings = Modeling::KeyIndexMapping.array(mappings)
57
57
 
@@ -59,9 +59,7 @@ module Burner
59
59
  end
60
60
 
61
61
  def perform(_output, payload)
62
- payload.value = array(payload.value).map { |array| index_to_key_map(array) }
63
-
64
- nil
62
+ payload[register] = array(payload[register]).map { |array| index_to_key_map(array) }
65
63
  end
66
64
 
67
65
  private
@@ -15,11 +15,11 @@ module Burner
15
15
  #
16
16
  # Expected Payload#value input: array of objects.
17
17
  # Payload#value output: An array of objects.
18
- class Graph < Job
18
+ class Graph < JobWithRegister
19
19
  attr_reader :key, :groups
20
20
 
21
- def initialize(name:, key:, config: Hashematics::Configuration.new)
22
- super(name: name)
21
+ def initialize(name:, key:, config: Hashematics::Configuration.new, register: '')
22
+ super(name: name, register: register)
23
23
 
24
24
  raise ArgumentError, 'key is required' if key.to_s.empty?
25
25
 
@@ -30,13 +30,11 @@ module Burner
30
30
  end
31
31
 
32
32
  def perform(output, payload)
33
- graph = Hashematics::Graph.new(groups).add(array(payload.value))
33
+ graph = Hashematics::Graph.new(groups).add(array(payload[register]))
34
34
 
35
35
  output.detail("Graphing: #{key}")
36
36
 
37
- payload.value = graph.data(key)
38
-
39
- nil
37
+ payload[register] = graph.data(key)
40
38
  end
41
39
  end
42
40
  end
@@ -48,7 +48,7 @@ module Burner
48
48
  # }
49
49
  #
50
50
  # Burner::Pipeline.make(config).execute
51
- class ObjectsToArrays < Job
51
+ class ObjectsToArrays < JobWithRegister
52
52
  attr_reader :mappings
53
53
 
54
54
  # If you wish to support nested objects you can pass in a string to use as a
@@ -56,8 +56,8 @@ module Burner
56
56
  # nested hashes then set separator to '.'. For more information, see the underlying
57
57
  # library that supports this dot-notation concept:
58
58
  # https://github.com/bluemarblepayroll/objectable
59
- def initialize(name:, mappings: [], separator: '')
60
- super(name: name)
59
+ def initialize(name:, mappings: [], register: '', separator: '')
60
+ super(name: name, register: register)
61
61
 
62
62
  @mappings = Modeling::KeyIndexMapping.array(mappings)
63
63
  @resolver = Objectable.resolver(separator: separator.to_s)
@@ -66,9 +66,7 @@ module Burner
66
66
  end
67
67
 
68
68
  def perform(_output, payload)
69
- payload.value = array(payload.value).map { |object| key_to_index_map(object) }
70
-
71
- nil
69
+ payload[register] = array(payload[register]).map { |object| key_to_index_map(object) }
72
70
  end
73
71
 
74
72
  private
@@ -16,15 +16,15 @@ module Burner
16
16
  #
17
17
  # Expected Payload#value input: nothing.
18
18
  # Payload#value output: An array with N beginning elements removed.
19
- class Shift < Job
19
+ class Shift < JobWithRegister
20
20
  DEFAULT_AMOUNT = 0
21
21
 
22
22
  private_constant :DEFAULT_AMOUNT
23
23
 
24
24
  attr_reader :amount
25
25
 
26
- def initialize(name:, amount: DEFAULT_AMOUNT)
27
- super(name: name)
26
+ def initialize(name:, amount: DEFAULT_AMOUNT, register: '')
27
+ super(name: name, register: register)
28
28
 
29
29
  @amount = amount.to_i
30
30
 
@@ -34,9 +34,7 @@ module Burner
34
34
  def perform(output, payload)
35
35
  output.detail("Shifting #{amount} entries.")
36
36
 
37
- payload.value = array(payload.value).slice(amount..-1)
38
-
39
- nil
37
+ payload[register] = array(payload[register]).slice(amount..-1)
40
38
  end
41
39
  end
42
40
  end
@@ -20,15 +20,15 @@ module Burner
20
20
  #
21
21
  # Expected Payload#value input: array of objects.
22
22
  # Payload#value output: An array of objects.
23
- class Transform < Job
23
+ class Transform < JobWithRegister
24
24
  BLANK = ''
25
25
 
26
26
  attr_reader :attribute_renderers,
27
27
  :exclusive,
28
28
  :resolver
29
29
 
30
- def initialize(name:, attributes: [], exclusive: false, separator: BLANK)
31
- super(name: name)
30
+ def initialize(name:, attributes: [], exclusive: false, register: '', separator: BLANK)
31
+ super(name: name, register: register)
32
32
 
33
33
  @resolver = Objectable.resolver(separator: separator)
34
34
  @exclusive = exclusive || false
@@ -41,14 +41,12 @@ module Burner
41
41
  end
42
42
 
43
43
  def perform(output, payload)
44
- payload.value = array(payload.value).map { |row| transform(row, payload.time) }
44
+ payload[register] = array(payload[register]).map { |row| transform(row, payload.time) }
45
45
 
46
46
  attr_count = attribute_renderers.length
47
- row_count = payload.value.length
47
+ row_count = payload[register].length
48
48
 
49
49
  output.detail("Transformed #{attr_count} attributes(s) for #{row_count} row(s)")
50
-
51
- nil
52
50
  end
53
51
 
54
52
  private
@@ -16,11 +16,11 @@ module Burner
16
16
  #
17
17
  # Expected Payload#value input: array of objects.
18
18
  # Payload#value output: An array of objects.
19
- class Unpivot < Job
19
+ class Unpivot < JobWithRegister
20
20
  attr_reader :unpivot
21
21
 
22
- def initialize(name:, pivot_set: HashMath::Unpivot::PivotSet.new)
23
- super(name: name)
22
+ def initialize(name:, pivot_set: HashMath::Unpivot::PivotSet.new, register: '')
23
+ super(name: name, register: register)
24
24
 
25
25
  @unpivot = HashMath::Unpivot.new(pivot_set)
26
26
 
@@ -28,18 +28,24 @@ module Burner
28
28
  end
29
29
 
30
30
  def perform(output, payload)
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
31
+ payload[register] = array(payload[register])
32
+ object_count = payload[register].length || 0
35
33
 
36
34
  message = "#{pivot_count} Pivots, Key(s): #{key_count} key(s), #{object_count} objects(s)"
37
35
 
38
36
  output.detail(message)
39
37
 
40
- 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
41
46
 
42
- nil
47
+ def key_count
48
+ unpivot.pivot_set.pivots.map { |p| p.keys.length }.sum
43
49
  end
44
50
  end
45
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
@@ -16,11 +16,11 @@ module Burner
16
16
  #
17
17
  # Expected Payload#value input: array of objects.
18
18
  # Payload#value output: An array of arrays.
19
- class Values < Job
19
+ class Values < JobWithRegister
20
20
  attr_reader :include_keys
21
21
 
22
- def initialize(name:, include_keys: false)
23
- super(name: name)
22
+ def initialize(name:, include_keys: false, register: '')
23
+ super(name: name, register: register)
24
24
 
25
25
  @include_keys = include_keys || false
26
26
 
@@ -28,12 +28,10 @@ module Burner
28
28
  end
29
29
 
30
30
  def perform(_output, payload)
31
- payload.value = array(payload.value)
32
- keys = include_keys ? [keys(payload.value.first)] : []
33
- values = payload.value.map { |object| values(object) }
34
- payload.value = keys + values
35
-
36
- nil
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
37
35
  end
38
36
 
39
37
  private
@@ -14,14 +14,12 @@ module Burner
14
14
  #
15
15
  # Expected Payload#value input: nothing.
16
16
  # Payload#value output: an array of arrays. Each inner array represents one data row.
17
- class Csv < Job
17
+ class Csv < JobWithRegister
18
18
  # This currently only supports returning an array of arrays, including the header row.
19
19
  # In the future this could be extended to offer more customizable options, such as
20
20
  # making it return an array of hashes with the columns mapped, etc.)
21
21
  def perform(_output, payload)
22
- payload.value = CSV.new(payload.value, headers: false).to_a
23
-
24
- nil
22
+ payload[register] = CSV.new(payload[register], headers: false).to_a
25
23
  end
26
24
  end
27
25
  end
@@ -14,11 +14,9 @@ module Burner
14
14
  #
15
15
  # Expected Payload#value input: string of JSON data.
16
16
  # Payload#value output: anything, as specified by the JSON de-serializer.
17
- class Json < Job
17
+ class Json < JobWithRegister
18
18
  def perform(_output, payload)
19
- payload.value = JSON.parse(payload.value)
20
-
21
- nil
19
+ payload[register] = JSON.parse(payload[register])
22
20
  end
23
21
  end
24
22
  end
@@ -17,11 +17,11 @@ module Burner
17
17
  #
18
18
  # Expected Payload#value input: string of YAML data.
19
19
  # Payload#value output: anything as specified by the YAML de-serializer.
20
- class Yaml < Job
20
+ class Yaml < JobWithRegister
21
21
  attr_reader :safe
22
22
 
23
- def initialize(name:, safe: true)
24
- super(name: name)
23
+ def initialize(name:, register: '', safe: true)
24
+ super(name: name, register: register)
25
25
 
26
26
  @safe = safe
27
27
 
@@ -36,9 +36,9 @@ module Burner
36
36
  def perform(output, payload)
37
37
  output.detail('Warning: loading YAML not using safe_load.') unless safe
38
38
 
39
- payload.value = safe ? YAML.safe_load(payload.value) : YAML.load(payload.value)
39
+ value = payload[register]
40
40
 
41
- nil
41
+ payload[register] = safe ? YAML.safe_load(value) : YAML.load(value)
42
42
  end
43
43
  # rubocop:enable Security/YAMLLoad
44
44
  end
@@ -13,9 +13,7 @@ module Burner
13
13
  #
14
14
  # Note: this does not use Payload#value.
15
15
  class Dummy < Job
16
- def perform(_output, _payload)
17
- nil
18
- end
16
+ def perform(_output, _payload); end
19
17
  end
20
18
  end
21
19
  end
@@ -27,8 +27,6 @@ module Burner
27
27
  compiled_message = job_string_template(message, output, payload)
28
28
 
29
29
  output.detail(compiled_message)
30
-
31
- nil
32
30
  end
33
31
  end
34
32
  end
@@ -11,11 +11,11 @@ module Burner
11
11
  module Library
12
12
  module IO
13
13
  # Common configuration/code for all IO Job subclasses.
14
- class Base < Job
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
 
@@ -16,15 +16,16 @@ module Burner
16
16
  # does not exist then the job will return false and short circuit the pipeline.
17
17
  #
18
18
  # Note: this does not use Payload#value.
19
- class Exist < Base
20
- attr_reader :short_circuit
19
+ class Exist < Job
20
+ attr_reader :path, :short_circuit
21
21
 
22
22
  def initialize(name:, path:, short_circuit: false)
23
- super(name: name, path: path)
23
+ super(name: name)
24
24
 
25
- @short_circuit = short_circuit || false
25
+ raise ArgumentError, 'path is required' if path.to_s.empty?
26
26
 
27
- freeze
27
+ @path = path.to_s
28
+ @short_circuit = short_circuit || false
28
29
  end
29
30
 
30
31
  def perform(output, payload)
@@ -35,9 +36,8 @@ module Burner
35
36
 
36
37
  output.detail("The path: #{compiled_path} #{verb} exist")
37
38
 
38
- # if anything but false is returned then the pipeline will not short circuit. So
39
- # we need to make sure we explicitly return false.
40
- 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
41
41
  end
42
42
  end
43
43
  end
@@ -19,8 +19,8 @@ module Burner
19
19
  class Read < Base
20
20
  attr_reader :binary
21
21
 
22
- def initialize(name:, path:, binary: false)
23
- super(name: name, path: path)
22
+ def initialize(name:, path:, binary: false, register: '')
23
+ super(name: name, path: path, register: register)
24
24
 
25
25
  @binary = binary || false
26
26
 
@@ -32,9 +32,7 @@ module Burner
32
32
 
33
33
  output.detail("Reading: #{compiled_path}")
34
34
 
35
- payload.value = File.open(compiled_path, mode, &:read)
36
-
37
- nil
35
+ payload[register] = File.open(compiled_path, mode, &:read)
38
36
  end
39
37
 
40
38
  private
@@ -19,8 +19,8 @@ module Burner
19
19
  class Write < Base
20
20
  attr_reader :binary
21
21
 
22
- def initialize(name:, path:, binary: false)
23
- super(name: name, path: path)
22
+ def initialize(name:, path:, binary: false, register: '')
23
+ super(name: name, path: path, register: register)
24
24
 
25
25
  @binary = binary || false
26
26
 
@@ -35,7 +35,7 @@ module Burner
35
35
  output.detail("Writing: #{compiled_path}")
36
36
 
37
37
  time_in_seconds = Benchmark.measure do
38
- File.open(compiled_path, mode) { |io| io.write(payload.value) }
38
+ File.open(compiled_path, mode) { |io| io.write(payload[register]) }
39
39
  end.real
40
40
 
41
41
  side_effect = SideEffects::WrittenFile.new(
@@ -45,8 +45,6 @@ module Burner
45
45
  )
46
46
 
47
47
  payload.add_side_effect(side_effect)
48
-
49
- nil
50
48
  end
51
49
 
52
50
  private
@@ -14,15 +14,13 @@ module Burner
14
14
  #
15
15
  # Expected Payload#value input: array of arrays.
16
16
  # Payload#value output: a serialized CSV string.
17
- class Csv < Job
17
+ class Csv < JobWithRegister
18
18
  def perform(_output, payload)
19
- payload.value = CSV.generate(options) do |csv|
20
- array(payload.value).each do |row|
19
+ payload[register] = CSV.generate(options) do |csv|
20
+ array(payload[register]).each do |row|
21
21
  csv << row
22
22
  end
23
23
  end
24
-
25
- nil
26
24
  end
27
25
 
28
26
  private
@@ -14,11 +14,9 @@ module Burner
14
14
  #
15
15
  # Expected Payload#value input: anything.
16
16
  # Payload#value output: string representing the output of the JSON serializer.
17
- class Json < Job
17
+ class Json < JobWithRegister
18
18
  def perform(_output, payload)
19
- payload.value = payload.value.to_json
20
-
21
- nil
19
+ payload[register] = payload[register].to_json
22
20
  end
23
21
  end
24
22
  end
@@ -14,11 +14,9 @@ module Burner
14
14
  #
15
15
  # Expected Payload#value input: anything.
16
16
  # Payload#value output: string representing the output of the YAML serializer.
17
- class Yaml < Job
17
+ class Yaml < JobWithRegister
18
18
  def perform(_output, payload)
19
- payload.value = payload.value.to_yaml
20
-
21
- nil
19
+ payload[register] = payload[register].to_yaml
22
20
  end
23
21
  end
24
22
  end
@@ -13,11 +13,11 @@ module Burner
13
13
  #
14
14
  # Expected Payload#value input: anything.
15
15
  # Payload#value output: whatever value was specified in this job.
16
- class SetValue < Job
16
+ class SetValue < JobWithRegister
17
17
  attr_reader :value
18
18
 
19
- def initialize(name:, value: nil)
20
- super(name: name)
19
+ def initialize(name:, register: '', value: nil)
20
+ super(name: name, register: register)
21
21
 
22
22
  @value = value
23
23
 
@@ -25,9 +25,7 @@ module Burner
25
25
  end
26
26
 
27
27
  def perform(_output, payload)
28
- payload.value = value
29
-
30
- nil
28
+ payload[register] = value
31
29
  end
32
30
  end
33
31
  end
@@ -27,8 +27,6 @@ module Burner
27
27
  output.detail("Going to sleep for #{seconds} second(s)")
28
28
 
29
29
  Kernel.sleep(seconds)
30
-
31
- nil
32
30
  end
33
31
  end
34
32
  end
@@ -10,3 +10,4 @@
10
10
  require_relative 'modeling/attribute'
11
11
  require_relative 'modeling/attribute_renderer'
12
12
  require_relative 'modeling/key_index_mapping'
13
+ require_relative 'modeling/validations'
@@ -0,0 +1,23 @@
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 'validations/blank'
11
+ require_relative 'validations/present'
12
+
13
+ module Burner
14
+ module Modeling
15
+ # Factory for building sub-classes that can validate an individual object and field value.
16
+ class Validations
17
+ acts_as_hashable_factory
18
+
19
+ register 'blank', Validations::Blank
20
+ register 'present', Validations::Present
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
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
+ class Validations
13
+ # Common logic shared among all Validation subclasses.
14
+ # This class is an abstract class, make sure to implement:
15
+ # - #valid?(object, resolver)
16
+ # - #default_message
17
+ class Base
18
+ acts_as_hashable
19
+
20
+ attr_reader :key
21
+
22
+ def initialize(key:, message: '')
23
+ raise ArgumentError, 'key is required' if key.to_s.empty?
24
+
25
+ @key = key.to_s
26
+ @message = message.to_s
27
+ end
28
+
29
+ def message
30
+ @message.to_s.empty? ? "#{key}#{default_message}" : @message.to_s
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
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
+ module Modeling
14
+ class Validations
15
+ # Check if a value is blank, if it is not blank then it is not valid.
16
+ class Blank < Base
17
+ acts_as_hashable
18
+
19
+ def valid?(object, resolver)
20
+ resolver.get(object, key).to_s.empty?
21
+ end
22
+
23
+ private
24
+
25
+ def default_message
26
+ ' must be blank'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
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
+ module Modeling
14
+ class Validations
15
+ # Check if a value is present. If it is blank (null or empty) then it is invalid.
16
+ class Present < Base
17
+ acts_as_hashable
18
+
19
+ def valid?(object_value, resolver)
20
+ !resolver.get(object_value, key).to_s.empty?
21
+ end
22
+
23
+ private
24
+
25
+ def default_message
26
+ ' is required'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -19,26 +19,60 @@ module Burner
19
19
  # report back the files it has written in a more structured data way (as opposed to simply
20
20
  # writing some information to the output.)
21
21
  class Payload
22
- attr_accessor :value
23
-
24
22
  attr_reader :params,
23
+ :registers,
25
24
  :side_effects,
26
25
  :time
27
26
 
28
27
  def initialize(
29
28
  params: {},
29
+ registers: {},
30
30
  side_effects: [],
31
- time: Time.now.utc,
32
- value: nil
31
+ time: Time.now.utc
33
32
  )
34
33
  @params = params || {}
34
+ @registers = {}
35
35
  @side_effects = side_effects || []
36
36
  @time = time || Time.now.utc
37
- @value = value
37
+
38
+ add_registers(registers)
38
39
  end
39
40
 
41
+ # Add a side effect of a job. This helps to keep track of things jobs do outside of its
42
+ # register mutations.
40
43
  def add_side_effect(side_effect)
41
44
  tap { side_effects << side_effect }
42
45
  end
46
+
47
+ # Set a register's value.
48
+ def []=(key, value)
49
+ set(key, value)
50
+ end
51
+
52
+ # Retrieve a register's value.
53
+ def [](key)
54
+ registers[key.to_s]
55
+ end
56
+
57
+ # Set halt_pipeline to true. This will indicate to the pipeline to stop all
58
+ # subsequent processing.
59
+ def halt_pipeline
60
+ @halt_pipeline = true
61
+ end
62
+
63
+ # Check and see if halt_pipeline was called.
64
+ def halt_pipeline?
65
+ @halt_pipeline || false
66
+ end
67
+
68
+ private
69
+
70
+ def set(key, value)
71
+ registers[key.to_s] = value
72
+ end
73
+
74
+ def add_registers(registers)
75
+ (registers || {}).each { |k, v| set(k, v) }
76
+ end
43
77
  end
44
78
  end
@@ -48,10 +48,10 @@ module Burner
48
48
 
49
49
  time_in_seconds = Benchmark.measure do
50
50
  steps.each do |step|
51
- return_value = step.perform(output, payload)
51
+ step.perform(output, payload)
52
52
 
53
- if return_value.is_a?(FalseClass)
54
- output.detail('Job returned false, ending pipeline.')
53
+ if payload.halt_pipeline?
54
+ output.detail('Payload was halted, ending pipeline.')
55
55
  break
56
56
  end
57
57
  end
@@ -29,17 +29,13 @@ module Burner
29
29
  end
30
30
 
31
31
  def perform(output, payload)
32
- return_value = nil
33
-
34
32
  output.title("#{job.class.name}#{SEPARATOR}#{job.name}")
35
33
 
36
34
  time_in_seconds = Benchmark.measure do
37
- return_value = job.perform(output, payload)
35
+ job.perform(output, payload)
38
36
  end.real.round(3)
39
37
 
40
38
  output.complete(time_in_seconds)
41
-
42
- return_value
43
39
  end
44
40
  end
45
41
  end
@@ -8,3 +8,4 @@
8
8
  #
9
9
 
10
10
  require_relative 'util/arrayable'
11
+ require_relative 'util/string_template'
@@ -0,0 +1,42 @@
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 Util
12
+ # Can take in a string and an object and use the object for formatting string interpolations
13
+ # using tokens of form: {attribute_name}. This templating class does not understand nested
14
+ # structures, so input should be a flat object/hash in the form of key-value pairs.
15
+ # A benefit of using Objectable for resolution is that it can understand almost any type of
16
+ # object: Hash, Struct, OpenStruct, custom objects, etc.
17
+ # For more information see underlying libraries:
18
+ # * Stringento: https://github.com/bluemarblepayroll/stringento
19
+ # * Objectable: https://github.com/bluemarblepayroll/objectable
20
+ class StringTemplate
21
+ include Singleton
22
+
23
+ attr_reader :resolver
24
+
25
+ def initialize
26
+ @resolver = Objectable.resolver(separator: '')
27
+
28
+ freeze
29
+ end
30
+
31
+ # For general consumption
32
+ def evaluate(expression, input)
33
+ Stringento.evaluate(expression, input, resolver: self)
34
+ end
35
+
36
+ # For Stringento consumption
37
+ def resolve(value, input)
38
+ resolver.get(input, value)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -8,5 +8,5 @@
8
8
  #
9
9
 
10
10
  module Burner
11
- VERSION = '1.0.0-alpha.6'
11
+ VERSION = '1.0.0-alpha.7'
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.6
4
+ version: 1.0.0.pre.alpha.7
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-22 00:00:00.000000000 Z
11
+ date: 2020-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts_as_hashable
@@ -220,6 +220,7 @@ files:
220
220
  - lib/burner.rb
221
221
  - lib/burner/cli.rb
222
222
  - lib/burner/job.rb
223
+ - lib/burner/job_with_register.rb
223
224
  - lib/burner/jobs.rb
224
225
  - lib/burner/library.rb
225
226
  - lib/burner/library/collection/arrays_to_objects.rb
@@ -228,6 +229,7 @@ files:
228
229
  - lib/burner/library/collection/shift.rb
229
230
  - lib/burner/library/collection/transform.rb
230
231
  - lib/burner/library/collection/unpivot.rb
232
+ - lib/burner/library/collection/validate.rb
231
233
  - lib/burner/library/collection/values.rb
232
234
  - lib/burner/library/deserialize/csv.rb
233
235
  - lib/burner/library/deserialize/json.rb
@@ -247,15 +249,19 @@ files:
247
249
  - lib/burner/modeling/attribute.rb
248
250
  - lib/burner/modeling/attribute_renderer.rb
249
251
  - lib/burner/modeling/key_index_mapping.rb
252
+ - lib/burner/modeling/validations.rb
253
+ - lib/burner/modeling/validations/base.rb
254
+ - lib/burner/modeling/validations/blank.rb
255
+ - lib/burner/modeling/validations/present.rb
250
256
  - lib/burner/output.rb
251
257
  - lib/burner/payload.rb
252
258
  - lib/burner/pipeline.rb
253
259
  - lib/burner/side_effects.rb
254
260
  - lib/burner/side_effects/written_file.rb
255
261
  - lib/burner/step.rb
256
- - lib/burner/string_template.rb
257
262
  - lib/burner/util.rb
258
263
  - lib/burner/util/arrayable.rb
264
+ - lib/burner/util/string_template.rb
259
265
  - lib/burner/version.rb
260
266
  homepage: https://github.com/bluemarblepayroll/burner
261
267
  licenses:
@@ -1,40 +0,0 @@
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
- # Can take in a string and an object and use the object for formatting string interpolations
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:
17
- # * Stringento: https://github.com/bluemarblepayroll/stringento
18
- # * Objectable: https://github.com/bluemarblepayroll/objectable
19
- class StringTemplate
20
- include Singleton
21
-
22
- attr_reader :resolver
23
-
24
- def initialize
25
- @resolver = Objectable.resolver(separator: '')
26
-
27
- freeze
28
- end
29
-
30
- # For general consumption
31
- def evaluate(expression, input)
32
- Stringento.evaluate(expression, input, resolver: self)
33
- end
34
-
35
- # For Stringento consumption
36
- def resolve(value, input)
37
- resolver.get(input, value)
38
- end
39
- end
40
- end