exel 1.4.0 → 1.5.1

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 (61) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +4 -4
  3. data/.rubocop.yml +20 -12
  4. data/.rubocop_airbnb.yml +2 -0
  5. data/.travis.yml +17 -4
  6. data/Gemfile +1 -2
  7. data/Gemfile.lock +64 -60
  8. data/README.md +1 -1
  9. data/Rakefile +1 -0
  10. data/exel.gemspec +4 -4
  11. data/lib/exel.rb +1 -0
  12. data/lib/exel/ast_node.rb +2 -1
  13. data/lib/exel/context.rb +2 -1
  14. data/lib/exel/deferred_context_value.rb +1 -0
  15. data/lib/exel/error/job_termination.rb +1 -0
  16. data/lib/exel/events.rb +1 -0
  17. data/lib/exel/instruction.rb +2 -1
  18. data/lib/exel/instruction_node.rb +1 -0
  19. data/lib/exel/job.rb +6 -4
  20. data/lib/exel/listen_instruction.rb +1 -0
  21. data/lib/exel/logging.rb +1 -0
  22. data/lib/exel/logging/logger_wrapper.rb +4 -1
  23. data/lib/exel/logging_helper.rb +1 -0
  24. data/lib/exel/middleware/chain.rb +1 -0
  25. data/lib/exel/middleware/logging.rb +1 -1
  26. data/lib/exel/null_instruction.rb +1 -0
  27. data/lib/exel/processor_helper.rb +1 -0
  28. data/lib/exel/processors/run_processor.rb +1 -0
  29. data/lib/exel/processors/split_processor.rb +2 -1
  30. data/lib/exel/providers/local_file_provider.rb +2 -1
  31. data/lib/exel/providers/threaded_async_provider.rb +1 -0
  32. data/lib/exel/sequence_node.rb +1 -0
  33. data/lib/exel/value.rb +1 -0
  34. data/lib/exel/version.rb +2 -1
  35. data/spec/exel/ast_node_spec.rb +42 -42
  36. data/spec/exel/context_spec.rb +76 -77
  37. data/spec/exel/deferred_context_value_spec.rb +41 -42
  38. data/spec/exel/events_spec.rb +65 -65
  39. data/spec/exel/instruction_node_spec.rb +16 -16
  40. data/spec/exel/instruction_spec.rb +46 -45
  41. data/spec/exel/job_spec.rb +94 -91
  42. data/spec/exel/listen_instruction_spec.rb +10 -10
  43. data/spec/exel/logging/logger_wrapper_spec.rb +67 -69
  44. data/spec/exel/logging_helper_spec.rb +15 -16
  45. data/spec/exel/logging_spec.rb +56 -56
  46. data/spec/exel/middleware/chain_spec.rb +51 -53
  47. data/spec/exel/middleware/logging_spec.rb +21 -23
  48. data/spec/exel/middleware_spec.rb +49 -50
  49. data/spec/exel/null_instruction_spec.rb +3 -4
  50. data/spec/exel/processors/async_processor_spec.rb +16 -18
  51. data/spec/exel/processors/run_processor_spec.rb +9 -11
  52. data/spec/exel/processors/split_processor_spec.rb +91 -93
  53. data/spec/exel/providers/local_file_provider_spec.rb +25 -28
  54. data/spec/exel/providers/threaded_async_provider_spec.rb +36 -38
  55. data/spec/exel/sequence_node_spec.rb +11 -11
  56. data/spec/exel/value_spec.rb +32 -33
  57. data/spec/exel_spec.rb +8 -7
  58. data/spec/integration/integration_spec.rb +2 -1
  59. data/spec/spec_helper.rb +3 -2
  60. data/spec/support/integration_test_classes.rb +3 -1
  61. metadata +16 -30
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Logging
4
5
  # Wraps calls to a logger to add {Logging} prefix to log messages
@@ -20,7 +21,9 @@ module EXEL
20
21
  end
21
22
 
22
23
  def add(severity, message = nil, progname = nil)
23
- message = yield if message.nil? && block_given?
24
+ if message.nil? && block_given?
25
+ message = yield
26
+ end
24
27
  __getobj__.add(severity, "#{Logging.prefix}#{message}", progname)
25
28
  end
26
29
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Logging related helper methods for processors
4
5
  module LoggingHelper
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Middleware is code configured to run around each processor execution. Custom middleware can be added as follows:
4
5
  #
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Middleware
4
5
  # Middleware to add a prefix to all messages logged during processor execution. The prefix is specified by the
@@ -12,7 +13,6 @@ module EXEL
12
13
 
13
14
  def log_process
14
15
  start_time = Time.now
15
- EXEL.logger.info 'Starting'
16
16
 
17
17
  yield
18
18
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # An {Instruction} that does nothing when executed
4
5
  class NullInstruction
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Helper methods useful to processors
4
5
  # @deprecated Most functionality replaced by {EXEL::Middleware::Logging} middleware.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Processors
4
5
  # Implements the +run+ instruction.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'csv'
3
4
  require 'tempfile'
4
5
  require_relative '../logging_helper'
@@ -65,7 +66,7 @@ module EXEL
65
66
  def process_file(callback)
66
67
  csv_options = @context[:csv_options] || {col_sep: ','}
67
68
 
68
- CSV.foreach(@file.path, csv_options) do |line|
69
+ CSV.foreach(@file.path, **csv_options) do |line|
69
70
  process_line(line, callback)
70
71
 
71
72
  break if @tempfile_count == @max_chunks
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Providers
4
5
  # The default remote provider. Doesn't actually upload and download files to and from remote storage, but rather
@@ -14,7 +15,7 @@ module EXEL
14
15
  end
15
16
 
16
17
  def self.remote?(uri)
17
- uri =~ %r{file://}
18
+ uri.to_s =~ %r{file://}
18
19
  end
19
20
  end
20
21
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Providers
4
5
  # The default remote provider. Provides async execution by running the given EXEL block in a new Thread
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative './ast_node'
3
4
 
4
5
  module EXEL
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Contains methods to handle remote and local values. Used for {Context} serialization
4
5
  module Value
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
- VERSION = '1.4.0'
4
+ VERSION = '1.5.1'
4
5
  end
@@ -1,61 +1,61 @@
1
1
  # frozen_string_literal: true
2
- module EXEL
3
- describe ASTNode do
4
- let(:context) { instance_double(EXEL::Context) }
5
2
 
6
- def instruction
7
- instance_double(Instruction, execute: nil)
8
- end
3
+ describe EXEL::ASTNode do
4
+ let(:context) { instance_double(EXEL::Context) }
9
5
 
10
- TestNode = Class.new(ASTNode)
6
+ def instruction
7
+ instance_double(EXEL::Instruction, execute: nil)
8
+ end
11
9
 
12
- describe '#start' do
13
- context 'when a JobTermination error bubbles up' do
14
- let(:node) { TestNode.new(instruction) }
10
+ class TestNode < EXEL::ASTNode
11
+ end
15
12
 
16
- before do
17
- allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
18
- end
13
+ describe '#start' do
14
+ context 'when a JobTermination error bubbles up' do
15
+ let(:node) { TestNode.new(instruction) }
19
16
 
20
- it 'ensures the process fails silently' do
21
- expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
22
- expect { node.start(context) }.not_to raise_error
23
- end
17
+ before do
18
+ allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
19
+ end
24
20
 
25
- it 'logs the error by default' do
26
- expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
27
- node.start(context)
28
- end
21
+ it 'ensures the process fails silently' do
22
+ expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
23
+ expect { node.start(context) }.not_to raise_error
24
+ end
29
25
 
30
- context 'given a log instruction' do
31
- before do
32
- allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination.new('Error', :warn))
33
- end
26
+ it 'logs the error by default' do
27
+ expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
28
+ node.start(context)
29
+ end
34
30
 
35
- it 'logs the error with the given cmd' do
36
- expect(EXEL.logger).to receive(:warn).with('JobTerminationError: Error')
37
- node.start(context)
38
- end
31
+ context 'given a log instruction' do
32
+ before do
33
+ allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination.new('Error', :warn))
34
+ end
35
+
36
+ it 'logs the error with the given cmd' do
37
+ expect(EXEL.logger).to receive(:warn).with('JobTerminationError: Error')
38
+ node.start(context)
39
39
  end
40
40
  end
41
41
  end
42
+ end
42
43
 
43
- describe '#run' do
44
- it 'raises an error if not implemented' do
45
- expect { TestNode.new(instruction).run(context) }.to raise_error 'EXEL::TestNode does not implement #process'
46
- end
44
+ describe '#run' do
45
+ it 'raises an error if not implemented' do
46
+ expect { TestNode.new(instruction).run(context) }.to raise_error 'TestNode does not implement #process'
47
47
  end
48
+ end
48
49
 
49
- describe '#add_child' do
50
- it 'adds the given node to its children' do
51
- root = ASTNode.new(instruction)
52
- child_node = ASTNode.new(instruction)
53
- child_node2 = ASTNode.new(instruction)
54
- root.add_child(child_node)
55
- root.add_child(child_node2)
50
+ describe '#add_child' do
51
+ it 'adds the given node to its children' do
52
+ root = EXEL::ASTNode.new(instruction)
53
+ child_node = EXEL::ASTNode.new(instruction)
54
+ child_node2 = EXEL::ASTNode.new(instruction)
55
+ root.add_child(child_node)
56
+ root.add_child(child_node2)
56
57
 
57
- expect(root.children).to eq([child_node, child_node2])
58
- end
58
+ expect(root.children).to eq([child_node, child_node2])
59
59
  end
60
60
  end
61
61
  end
@@ -1,109 +1,108 @@
1
1
  # frozen_string_literal: true
2
- module EXEL
3
- describe Context do
4
- subject(:context) { EXEL::Context.new(key1: '1', key2: 2) }
5
2
 
6
- it { is_expected.to be_a(Hash) }
3
+ describe EXEL::Context do
4
+ subject(:context) { EXEL::Context.new(key1: '1', key2: 2) }
7
5
 
8
- describe '#initialize' do
9
- it 'initializes with a hash' do
10
- expect(context[:key1]).to eq('1')
11
- expect(context[:key2]).to eq(2)
12
- expect(context[:key3]).to be_nil
13
- end
6
+ it { is_expected.to be_a(Hash) }
7
+
8
+ describe '#initialize' do
9
+ it 'initializes with a hash' do
10
+ expect(context[:key1]).to eq('1')
11
+ expect(context[:key2]).to eq(2)
12
+ expect(context[:key3]).to be_nil
14
13
  end
14
+ end
15
15
 
16
- describe '#deep_dup' do
17
- it 'returns a deep copy of itself' do
18
- context[:a] = {nested: []}
16
+ describe '#deep_dup' do
17
+ it 'returns a deep copy of itself' do
18
+ context[:a] = {nested: []}
19
19
 
20
- dup = context.deep_dup
21
- expect(context).to eq(dup)
22
- expect(context).not_to be_equal(dup)
20
+ dup = context.deep_dup
21
+ expect(context).to eq(dup)
22
+ expect(context).not_to be_equal(dup)
23
23
 
24
- dup[:a][:nested] << 1
25
- expect(context[:a][:nested]).to be_empty
26
- end
24
+ dup[:a][:nested] << 1
25
+ expect(context[:a][:nested]).to be_empty
27
26
  end
27
+ end
28
28
 
29
- describe '#serialize' do
30
- before { allow(Value).to receive(:upload) }
31
-
32
- it 'writes the serialized context to a file and upload it' do
33
- expect(Value).to receive(:remotize).with(context[:key1]).and_return('remote_value1')
34
- expect(Value).to receive(:remotize).with(context[:key2]).and_return('remote_value2')
29
+ describe '#serialize' do
30
+ before { allow(EXEL::Value).to receive(:upload) }
35
31
 
36
- expect(SecureRandom).to receive(:uuid).and_return('uuid')
32
+ it 'writes the serialized context to a file and upload it' do
33
+ expect(EXEL::Value).to receive(:remotize).with(context[:key1]).and_return('remote_value1')
34
+ expect(EXEL::Value).to receive(:remotize).with(context[:key2]).and_return('remote_value2')
37
35
 
38
- expect(Value).to receive(:remotize) do |file|
39
- expect(file.read).to eq(Marshal.dump(Context.new(key1: 'remote_value1', key2: 'remote_value2')))
40
- expect(file.path).to include('uuid')
41
- 'file_uri'
42
- end
36
+ expect(SecureRandom).to receive(:uuid).and_return('uuid')
43
37
 
44
- expect(context.serialize).to eq('file_uri')
38
+ expect(EXEL::Value).to receive(:remotize) do |file|
39
+ expect(file.read).to eq(Marshal.dump(EXEL::Context.new(key1: 'remote_value1', key2: 'remote_value2')))
40
+ expect(file.path).to include('uuid')
41
+ 'file_uri'
45
42
  end
46
43
 
47
- it 'does not mutate the current context' do
48
- allow(Value).to receive(:remotize).and_return('remote_value')
49
- original_table = context.dup
50
- context.serialize
51
- expect(context).to eq(original_table)
52
- end
44
+ expect(context.serialize).to eq('file_uri')
53
45
  end
54
46
 
55
- describe '.deserialize' do
56
- it 'deserializes a given uri' do
57
- file = StringIO.new(Marshal.dump(context))
58
- expect(Value).to receive(:localize).with('uri').and_return(file)
47
+ it 'does not mutate the current context' do
48
+ allow(EXEL::Value).to receive(:remotize).and_return('remote_value')
49
+ original_table = context.dup
50
+ context.serialize
51
+ expect(context).to eq(original_table)
52
+ end
53
+ end
59
54
 
60
- expect(Context.deserialize('uri')).to eq(context)
55
+ describe '.deserialize' do
56
+ it 'deserializes a given uri' do
57
+ file = StringIO.new(Marshal.dump(context))
58
+ expect(EXEL::Value).to receive(:localize).with('uri').and_return(file)
61
59
 
62
- expect(file).to be_closed
63
- end
60
+ expect(EXEL::Context.deserialize('uri')).to eq(context)
61
+
62
+ expect(file).to be_closed
64
63
  end
64
+ end
65
65
 
66
- shared_examples 'a reader method' do
67
- subject(:context) { EXEL::Context.new(key: 'value') }
66
+ shared_examples 'a reader method' do
67
+ subject(:context) { EXEL::Context.new(key: 'value') }
68
68
 
69
- it 'returns the value' do
70
- expect(context.send(method, :key)).to eq('value')
71
- end
69
+ it 'returns the value' do
70
+ expect(context.send(method, :key)).to eq('value')
71
+ end
72
72
 
73
- it 'localizes the returned value' do
74
- expect(Value).to receive(:localize).with('value').and_return('localized')
75
- expect(context.send(method, :key)).to eq('localized')
76
- end
73
+ it 'localizes the returned value' do
74
+ expect(EXEL::Value).to receive(:localize).with('value').and_return('localized')
75
+ expect(context.send(method, :key)).to eq('localized')
76
+ end
77
77
 
78
- it 'stores the localized value' do
79
- allow(Value).to receive(:localize).with('value').and_return('localized')
80
- context.send(method, :key)
81
- allow(Value).to receive(:localize).with('localized').and_return('localized')
82
- context.send(method, :key)
83
- end
78
+ it 'stores the localized value' do
79
+ allow(EXEL::Value).to receive(:localize).with('value').and_return('localized')
80
+ context.send(method, :key)
81
+ allow(EXEL::Value).to receive(:localize).with('localized').and_return('localized')
82
+ context.send(method, :key)
83
+ end
84
84
 
85
- it 'looks up deferred values' do
86
- # eq(context) as an argument matcher is necessary to prevent RSpec from calling fetch on the context, leading to
87
- # a stack overflow
88
- expect(DeferredContextValue).to receive(:resolve).with('value', eq(context)).and_return('resolved')
89
- expect(context.send(method, :key)).to eq('resolved')
90
- end
85
+ it 'looks up deferred values' do
86
+ # eq(context) as an argument matcher is necessary to prevent RSpec from calling fetch on the context, leading to
87
+ # a stack overflow
88
+ expect(EXEL::DeferredContextValue).to receive(:resolve).with('value', eq(context)).and_return('resolved')
89
+ expect(context.send(method, :key)).to eq('resolved')
91
90
  end
91
+ end
92
92
 
93
- describe '#[]' do
94
- it_behaves_like 'a reader method' do
95
- let(:method) { :[] }
96
- end
93
+ describe '#[]' do
94
+ it_behaves_like 'a reader method' do
95
+ let(:method) { :[] }
97
96
  end
97
+ end
98
98
 
99
- describe '#fetch' do
100
- it 'raises an exception if the key is not found' do
101
- expect { context.fetch(:unknown) }.to raise_error(KeyError)
102
- end
99
+ describe '#fetch' do
100
+ it 'raises an exception if the key is not found' do
101
+ expect { context.fetch(:unknown) }.to raise_error(KeyError)
102
+ end
103
103
 
104
- it_behaves_like 'a reader method' do
105
- let(:method) { :fetch }
106
- end
104
+ it_behaves_like 'a reader method' do
105
+ let(:method) { :fetch }
107
106
  end
108
107
  end
109
108
  end
@@ -1,66 +1,65 @@
1
1
  # frozen_string_literal: true
2
- module EXEL
3
- describe DeferredContextValue do
4
- subject(:deferred_value) { DeferredContextValue.new }
5
2
 
6
- describe '.resolve' do
7
- subject(:deferred_value) { DeferredContextValue.new[:key] }
3
+ describe EXEL::DeferredContextValue do
4
+ subject(:deferred_value) { EXEL::DeferredContextValue.new }
8
5
 
9
- context 'at the top level' do
10
- let(:context) { {key: 'value', deferred_value: deferred_value} }
6
+ describe '.resolve' do
7
+ subject(:deferred_value) { EXEL::DeferredContextValue.new[:key] }
11
8
 
12
- it 'returns the lookup value from the context' do
13
- expect(DeferredContextValue.resolve(context[:deferred_value], context)).to eq(context[:key])
14
- end
9
+ context 'at the top level' do
10
+ let(:context) { {key: 'value', deferred_value: deferred_value} }
11
+
12
+ it 'returns the lookup value from the context' do
13
+ expect(EXEL::DeferredContextValue.resolve(context[:deferred_value], context)).to eq(context[:key])
15
14
  end
15
+ end
16
16
 
17
- context 'in an array' do
18
- let(:context) { {key: 'value', array: [1, 2, deferred_value]} }
17
+ context 'in an array' do
18
+ let(:context) { {key: 'value', array: [1, 2, deferred_value]} }
19
19
 
20
- it 'returns the lookup value from the context' do
21
- expect(DeferredContextValue.resolve(context[:array], context)).to eq([1, 2, context[:key]])
22
- end
20
+ it 'returns the lookup value from the context' do
21
+ expect(EXEL::DeferredContextValue.resolve(context[:array], context)).to eq([1, 2, context[:key]])
23
22
  end
23
+ end
24
24
 
25
- context 'in a hash' do
26
- let(:context) { {key: 'value', hash: {hash_key: deferred_value}} }
25
+ context 'in a hash' do
26
+ let(:context) { {key: 'value', hash: {hash_key: deferred_value}} }
27
27
 
28
- it 'returns the lookup value from the context' do
29
- expect(DeferredContextValue.resolve(context[:hash], context)).to eq(hash_key: context[:key])
30
- end
28
+ it 'returns the lookup value from the context' do
29
+ expect(EXEL::DeferredContextValue.resolve(context[:hash], context)).to eq(hash_key: context[:key])
31
30
  end
31
+ end
32
32
 
33
- context 'in a hash nested in an array' do
34
- let(:context) { {key: 'value', nested: [{}, {hash_key: deferred_value}]} }
33
+ context 'in a hash nested in an array' do
34
+ let(:context) { {key: 'value', nested: [{}, {hash_key: deferred_value}]} }
35
35
 
36
- it 'looks up a deferred context value in a hash nested in an array' do
37
- expect(DeferredContextValue.resolve(context[:nested], context)).to eq([{}, {hash_key: context[:key]}])
38
- end
36
+ it 'looks up a deferred context value in a hash nested in an array' do
37
+ expect(EXEL::DeferredContextValue.resolve(context[:nested], context)).to eq([{}, {hash_key: context[:key]}])
39
38
  end
39
+ end
40
40
 
41
- context 'in an array nested in a hash' do
42
- let(:context) { {key: 'value', nested: {hash_key: [1, deferred_value]}} }
41
+ context 'in an array nested in a hash' do
42
+ let(:context) { {key: 'value', nested: {hash_key: [1, deferred_value]}} }
43
43
 
44
- it 'looks up a deferred context value in an array nested in a hash' do
45
- expect(DeferredContextValue.resolve(context[:nested], context)).to eq(hash_key: [1, context[:key]])
46
- end
44
+ it 'looks up a deferred context value in an array nested in a hash' do
45
+ expect(EXEL::DeferredContextValue.resolve(context[:nested], context)).to eq(hash_key: [1, context[:key]])
47
46
  end
48
47
  end
48
+ end
49
49
 
50
- describe '#[]' do
51
- it 'stores the given key in the keys attribute' do
52
- deferred_value[:top_level_key]['sub_key']
53
- expect(deferred_value.keys).to eq([:top_level_key, 'sub_key'])
54
- end
50
+ describe '#[]' do
51
+ it 'stores the given key in the keys attribute' do
52
+ deferred_value[:top_level_key]['sub_key']
53
+ expect(deferred_value.keys).to eq([:top_level_key, 'sub_key'])
55
54
  end
55
+ end
56
56
 
57
- describe '#get' do
58
- it 'looks up the value of the keys attribute in the passed-in context' do
59
- allow(deferred_value).to receive(:keys).and_return([:top_level_key, 'sub_key'])
60
- value = 'example_value'
61
- context = Context.new(top_level_key: {'sub_key' => value})
62
- expect(deferred_value.get(context)).to eq(value)
63
- end
57
+ describe '#get' do
58
+ it 'looks up the value of the keys attribute in the passed-in context' do
59
+ allow(deferred_value).to receive(:keys).and_return([:top_level_key, 'sub_key'])
60
+ value = 'example_value'
61
+ context = EXEL::Context.new(top_level_key: {'sub_key' => value})
62
+ expect(deferred_value.get(context)).to eq(value)
64
63
  end
65
64
  end
66
65
  end