exel 1.2.1 → 1.3.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -4
- data/.rubocop_todo.yml +1 -13
- data/Gemfile +1 -0
- data/Guardfile +1 -0
- data/README.md +95 -30
- data/Rakefile +1 -0
- data/exel.gemspec +2 -2
- data/lib/exel.rb +6 -1
- data/lib/exel/ast_node.rb +4 -9
- data/lib/exel/context.rb +1 -0
- data/lib/exel/deferred_context_value.rb +1 -0
- data/lib/exel/error/job_termination.rb +9 -0
- data/lib/exel/events.rb +1 -0
- data/lib/exel/instruction.rb +3 -1
- data/lib/exel/instruction_node.rb +1 -0
- data/lib/exel/job.rb +1 -0
- data/lib/exel/listen_instruction.rb +1 -0
- data/lib/exel/logging.rb +22 -1
- data/lib/exel/logging/logger_wrapper.rb +28 -0
- data/lib/exel/logging_helper.rb +35 -0
- data/lib/exel/middleware/chain.rb +66 -0
- data/lib/exel/middleware/logging.rb +30 -0
- data/lib/exel/null_instruction.rb +1 -0
- data/lib/exel/processor_helper.rb +7 -0
- data/lib/exel/processors/async_processor.rb +2 -8
- data/lib/exel/processors/run_processor.rb +2 -7
- data/lib/exel/processors/split_processor.rb +5 -8
- data/lib/exel/providers/local_file_provider.rb +1 -0
- data/lib/exel/providers/threaded_async_provider.rb +1 -0
- data/lib/exel/sequence_node.rb +1 -0
- data/lib/exel/value.rb +1 -0
- data/lib/exel/version.rb +2 -1
- data/spec/exel/ast_node_spec.rb +24 -3
- data/spec/exel/context_spec.rb +1 -0
- data/spec/exel/deferred_context_value_spec.rb +1 -0
- data/spec/exel/events_spec.rb +1 -0
- data/spec/exel/instruction_node_spec.rb +1 -0
- data/spec/exel/instruction_spec.rb +6 -0
- data/spec/exel/job_spec.rb +1 -0
- data/spec/exel/listen_instruction_spec.rb +1 -0
- data/spec/exel/logging/logger_wrapper_spec.rb +95 -0
- data/spec/exel/logging_helper_spec.rb +25 -0
- data/spec/exel/logging_spec.rb +36 -3
- data/spec/exel/middleware/chain_spec.rb +67 -0
- data/spec/exel/middleware/logging_spec.rb +33 -0
- data/spec/exel/middleware_spec.rb +69 -0
- data/spec/exel/null_instruction_spec.rb +1 -0
- data/spec/exel/processors/async_processor_spec.rb +1 -0
- data/spec/exel/processors/run_processor_spec.rb +1 -0
- data/spec/exel/processors/split_processor_spec.rb +2 -7
- data/spec/exel/providers/local_file_provider_spec.rb +1 -0
- data/spec/exel/providers/threaded_async_provider_spec.rb +1 -0
- data/spec/exel/sequence_node_spec.rb +1 -0
- data/spec/exel/value_spec.rb +1 -0
- data/spec/exel_spec.rb +1 -0
- data/spec/integration/integration_spec.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/integration_test_classes.rb +1 -0
- metadata +18 -18
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
# Logging related helper methods for processors
|
4
|
+
module LoggingHelper
|
5
|
+
# @return [Logger] Returns the EXEL logger
|
6
|
+
def logger
|
7
|
+
EXEL.logger
|
8
|
+
end
|
9
|
+
|
10
|
+
# Logs a message with DEBUG severity
|
11
|
+
def log_debug(message)
|
12
|
+
logger.debug(message)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Logs a message with INFO severity
|
16
|
+
def log_info(message)
|
17
|
+
logger.info(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Logs a message with WARN severity
|
21
|
+
def log_warn(message)
|
22
|
+
logger.warn(message)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Logs a message with ERROR severity
|
26
|
+
def log_error(message)
|
27
|
+
logger.error(message)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Logs a message with FATAL severity
|
31
|
+
def log_fatal(message)
|
32
|
+
logger.fatal(message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
# Middleware is code configured to run around each processor execution. Custom middleware can be added as follows:
|
4
|
+
#
|
5
|
+
# EXEL.configure do |config|
|
6
|
+
# config.middleware.add(MyMiddleware)
|
7
|
+
# config.middleware.add(AnotherMiddleware, 'constructor arg')
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# Middleware can be any class that implements a +call+ method that includes a call to +yield+:
|
11
|
+
#
|
12
|
+
# class MyMiddleware
|
13
|
+
# def call(processor, context, args)
|
14
|
+
# puts 'before process'
|
15
|
+
# yield
|
16
|
+
# puts 'after process'
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# The +call+ method will be passed the class of the processor that will be executed, the current context, and any args
|
21
|
+
# that were passed to the processor in the job definition.
|
22
|
+
module Middleware
|
23
|
+
# Chain of middleware to be invoked in sequence around each processor execution.
|
24
|
+
class Chain
|
25
|
+
attr_reader :entries
|
26
|
+
|
27
|
+
Entry = Struct.new(:klass, :args)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@entries = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds a middleware class to the chain. If it is already in the chain it will be removed and added to the end.
|
34
|
+
# Any additional arguments will be passed to +new+ when the middleware is created.
|
35
|
+
def add(klass, *args)
|
36
|
+
remove(klass)
|
37
|
+
@entries << Entry.new(klass, args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes a middleware class from the chain.
|
41
|
+
def remove(klass)
|
42
|
+
@entries.delete_if { |entry| entry.klass == klass }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns true if the given class is in the chain.
|
46
|
+
def include?(klass)
|
47
|
+
@entries.any? { |entry| entry.klass == klass }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Calls each middleware in the chain.
|
51
|
+
def invoke(*args)
|
52
|
+
chain = @entries.map { |entry| entry.klass.new(*entry.args) }
|
53
|
+
|
54
|
+
traverse_chain = lambda do
|
55
|
+
if chain.empty?
|
56
|
+
yield
|
57
|
+
else
|
58
|
+
chain.shift.call(*args, &traverse_chain)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
traverse_chain.call
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
module Middleware
|
4
|
+
# Middleware to add a prefix to all messages logged during processor execution. The prefix is specified by the
|
5
|
+
# +:log_prefix+ key in the context. Also logs start, finish, and failure of the processor execution.
|
6
|
+
class Logging
|
7
|
+
def call(processor_class, context, _args, &block)
|
8
|
+
EXEL::Logging.with_prefix("#{context[:log_prefix]}[#{processor_class}] ") { log_process(&block) }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def log_process
|
14
|
+
start_time = Time.now
|
15
|
+
EXEL.logger.info 'Starting'
|
16
|
+
|
17
|
+
yield
|
18
|
+
|
19
|
+
EXEL.logger.info "Finished in #{duration(start_time)} seconds"
|
20
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
21
|
+
EXEL.logger.info "Failed in #{duration(start_time)} seconds"
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
|
25
|
+
def duration(start_time)
|
26
|
+
(Time.now - start_time).round(3)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,6 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module EXEL
|
2
3
|
# Helper methods useful to processors
|
4
|
+
# @deprecated Most functionality replaced by {EXEL::Middleware::Logging} middleware.
|
3
5
|
module ProcessorHelper
|
6
|
+
def self.included(other)
|
7
|
+
warn "DEPRECATION WARNING: [#{other}] EXEL::ProcessorHelper will be removed. For process logging, please use "\
|
8
|
+
'EXEL::Middleware::Logging instead'
|
9
|
+
end
|
10
|
+
|
4
11
|
def tag(*tags)
|
5
12
|
tags.map { |t| "[#{t}]" }.join('')
|
6
13
|
end
|
@@ -1,24 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module EXEL
|
4
4
|
module Processors
|
5
5
|
# Implements the +async+ instruction by using the configured async provider to run a block asynchronously.
|
6
6
|
class AsyncProcessor
|
7
|
-
include EXEL::ProcessorHelper
|
8
7
|
attr_reader :provider
|
9
8
|
|
10
9
|
def initialize(context)
|
11
10
|
@context = context
|
12
11
|
@provider = EXEL.async_provider.new(context)
|
13
|
-
|
14
|
-
log_prefix_with '[AsyncProcessor]'
|
15
12
|
end
|
16
13
|
|
17
14
|
def process(block)
|
18
|
-
|
19
|
-
@provider.do_async(block)
|
20
|
-
log_info 'call to async completed'
|
21
|
-
end
|
15
|
+
@provider.do_async(block)
|
22
16
|
end
|
23
17
|
end
|
24
18
|
end
|
@@ -1,11 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
module EXEL
|
4
3
|
module Processors
|
5
4
|
# Implements the +run+ instruction.
|
6
5
|
class RunProcessor
|
7
|
-
include EXEL::ProcessorHelper
|
8
|
-
|
9
6
|
# Requires +context[:job]+ to contain the name of the job to be run.
|
10
7
|
def initialize(context)
|
11
8
|
@context = context
|
@@ -13,9 +10,7 @@ module EXEL
|
|
13
10
|
|
14
11
|
# Runs the specified job with the current context
|
15
12
|
def process(_block = nil)
|
16
|
-
|
17
|
-
EXEL::Job.run(@context[:job], @context)
|
18
|
-
end
|
13
|
+
EXEL::Job.run(@context[:job], @context)
|
19
14
|
end
|
20
15
|
end
|
21
16
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'csv'
|
2
3
|
require 'tempfile'
|
3
|
-
require_relative '../
|
4
|
+
require_relative '../logging_helper'
|
4
5
|
|
5
6
|
module EXEL
|
6
7
|
module Processors
|
@@ -12,7 +13,7 @@ module EXEL
|
|
12
13
|
# be deleted when splitting is complete
|
13
14
|
# * +:chunk_size+ Set to specify the number of lines that each chunk should contain
|
14
15
|
class SplitProcessor
|
15
|
-
include EXEL::
|
16
|
+
include EXEL::LoggingHelper
|
16
17
|
|
17
18
|
attr_accessor :file_name, :block
|
18
19
|
|
@@ -26,15 +27,11 @@ module EXEL
|
|
26
27
|
@context = context
|
27
28
|
@file = context[:resource]
|
28
29
|
@context[:delete_resource] = true if @context[:delete_resource].nil?
|
29
|
-
|
30
|
-
log_prefix_with '[SplitProcessor]'
|
31
30
|
end
|
32
31
|
|
33
32
|
def process(callback)
|
34
|
-
|
35
|
-
|
36
|
-
finish(callback)
|
37
|
-
end
|
33
|
+
process_file(callback)
|
34
|
+
finish(callback)
|
38
35
|
end
|
39
36
|
|
40
37
|
def process_line(line, callback)
|
data/lib/exel/sequence_node.rb
CHANGED
data/lib/exel/value.rb
CHANGED
data/lib/exel/version.rb
CHANGED
data/spec/exel/ast_node_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module EXEL
|
2
3
|
describe ASTNode do
|
3
4
|
let(:context) { instance_double(EXEL::Context) }
|
@@ -9,13 +10,33 @@ module EXEL
|
|
9
10
|
TestNode = Class.new(ASTNode)
|
10
11
|
|
11
12
|
describe '#start' do
|
12
|
-
context 'when
|
13
|
-
|
14
|
-
|
13
|
+
context 'when a JobTermination error bubbles up' do
|
14
|
+
let(:node) { TestNode.new(instruction) }
|
15
|
+
|
16
|
+
before do
|
15
17
|
allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'ensures the process fails silently' do
|
16
21
|
expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
|
17
22
|
expect { node.start(context) }.not_to raise_error
|
18
23
|
end
|
24
|
+
|
25
|
+
it 'logs the error by default' do
|
26
|
+
expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
|
27
|
+
node.start(context)
|
28
|
+
end
|
29
|
+
|
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
|
34
|
+
|
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
|
39
|
+
end
|
19
40
|
end
|
20
41
|
end
|
21
42
|
|
data/spec/exel/context_spec.rb
CHANGED
data/spec/exel/events_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module EXEL
|
2
3
|
describe Instruction do
|
3
4
|
subject(:instruction) { EXEL::Instruction.new(processor_class, args) }
|
@@ -14,6 +15,11 @@ module EXEL
|
|
14
15
|
instruction.execute(context)
|
15
16
|
end
|
16
17
|
|
18
|
+
it 'invokes the middleware chain' do
|
19
|
+
expect(EXEL.middleware).to receive(:invoke).with(processor_class, context, args)
|
20
|
+
instruction.execute(context)
|
21
|
+
end
|
22
|
+
|
17
23
|
it 'does not pass a copy of the context' do
|
18
24
|
allow(processor_class).to receive(:new) do |context_arg|
|
19
25
|
expect(context_arg).to be(context)
|
data/spec/exel/job_spec.rb
CHANGED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
module Logging
|
4
|
+
describe LoggerWrapper do
|
5
|
+
subject(:wrapper) { LoggerWrapper.new(logger) }
|
6
|
+
let(:logger) { instance_double(Logger) }
|
7
|
+
|
8
|
+
it { is_expected.to be_a(SimpleDelegator) }
|
9
|
+
|
10
|
+
LOG_LEVELS = %i(debug info warn error fatal unknown).freeze
|
11
|
+
|
12
|
+
context 'without a Logging prefix' do
|
13
|
+
LOG_LEVELS.each do |level|
|
14
|
+
describe "##{level}" do
|
15
|
+
context 'when passed a message string' do
|
16
|
+
it 'passes the message to its wrapped logger' do
|
17
|
+
expect(logger).to receive(level).with('message')
|
18
|
+
wrapper.send(level, 'message')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when passed a message block' do
|
23
|
+
it 'passes the block to its wrapped logger' do
|
24
|
+
block = proc {}
|
25
|
+
expect(logger).to receive(level).with(nil, &block)
|
26
|
+
|
27
|
+
wrapper.send(level, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'and a progname' do
|
31
|
+
it 'passes the block and progname to its wrapped logger' do
|
32
|
+
block = proc {}
|
33
|
+
expect(logger).to receive(level).with('test', &block)
|
34
|
+
|
35
|
+
wrapper.send(level, 'test', &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#add' do
|
43
|
+
it 'passes the message to its wrapped logger' do
|
44
|
+
expect(logger).to receive(:add).with(Logger::FATAL, 'message', 'progname')
|
45
|
+
wrapper.add(Logger::FATAL, 'message', 'progname')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with a Logging prefix' do
|
51
|
+
before { allow(Logging).to receive(:prefix).and_return('prefix: ') }
|
52
|
+
|
53
|
+
LOG_LEVELS.each do |level|
|
54
|
+
describe "##{level}" do
|
55
|
+
context 'when passed a message string' do
|
56
|
+
it 'passes the prefixed message to its wrapped logger' do
|
57
|
+
expect(logger).to receive(level).with('prefix: message')
|
58
|
+
wrapper.send(level, 'message')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when passed a message block' do
|
63
|
+
it 'passes the prefixed block to its wrapped logger' do
|
64
|
+
expect(logger).to receive(level) do |progname, &block|
|
65
|
+
expect(progname).to be_nil
|
66
|
+
expect(block.call).to eq('prefix: message')
|
67
|
+
end
|
68
|
+
|
69
|
+
wrapper.send(level) { 'message' }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'and a progname' do
|
73
|
+
it 'passes the prefixed block and progname to its wrapped logger' do
|
74
|
+
expect(logger).to receive(level) do |progname, &block|
|
75
|
+
expect(progname).to eq('test')
|
76
|
+
expect(block.call).to eq('prefix: message')
|
77
|
+
end
|
78
|
+
|
79
|
+
wrapper.send(level, 'test') { 'message' }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#add' do
|
87
|
+
it 'passes the prefixed message to its wrapped logger' do
|
88
|
+
expect(logger).to receive(:add).with(Logger::FATAL, 'prefix: message', 'progname')
|
89
|
+
wrapper.add(Logger::FATAL, 'message', 'progname')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|