exel 0.9.0 → 1.0.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -0
  3. data/.rubocop_todo.yml +43 -0
  4. data/Guardfile +18 -11
  5. data/README.md +3 -0
  6. data/Rakefile +0 -1
  7. data/exel.gemspec +6 -5
  8. data/lib/exel/ast_node.rb +2 -2
  9. data/lib/exel/context.rb +16 -19
  10. data/lib/exel/deferred_context_value.rb +2 -2
  11. data/lib/exel/instruction.rb +2 -2
  12. data/lib/exel/job.rb +5 -5
  13. data/lib/exel/logging.rb +3 -3
  14. data/lib/exel/null_instruction.rb +1 -1
  15. data/lib/exel/processor_helper.rb +3 -5
  16. data/lib/exel/processors/async_processor.rb +3 -3
  17. data/lib/exel/providers/local_file_provider.rb +18 -0
  18. data/lib/exel/providers/threaded_async_provider.rb +13 -0
  19. data/lib/exel/{resource.rb → value.rb} +7 -11
  20. data/lib/exel/version.rb +1 -1
  21. data/lib/exel.rb +10 -1
  22. data/spec/exel/ast_node_spec.rb +3 -15
  23. data/spec/exel/context_spec.rb +40 -22
  24. data/spec/exel/job_spec.rb +8 -8
  25. data/spec/exel/logging_spec.rb +3 -3
  26. data/spec/exel/processors/async_processor_spec.rb +12 -4
  27. data/spec/exel/processors/split_processor_spec.rb +67 -67
  28. data/spec/exel/providers/local_async_provider_spec.rb +19 -0
  29. data/spec/exel/providers/local_file_provider_spec.rb +36 -0
  30. data/spec/exel/sequence_node_spec.rb +6 -13
  31. data/spec/exel/value_spec.rb +51 -0
  32. data/spec/exel_spec.rb +41 -0
  33. data/spec/spec_helper.rb +22 -3
  34. metadata +69 -40
  35. data/lib/exel/execution_worker.rb +0 -13
  36. data/lib/exel/handlers/s3_handler.rb +0 -43
  37. data/lib/exel/handlers/sidekiq_handler.rb +0 -21
  38. data/spec/exel/execution_worker_spec.rb +0 -13
  39. data/spec/exel/handlers/s3_handler_spec.rb +0 -49
  40. data/spec/exel/handlers/sidekiq_handler_spec.rb +0 -54
  41. data/spec/exel/resource_spec.rb +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5eccac7b009cd6a4063a36cbb53b70de2efa22b
4
- data.tar.gz: f6e7dd493f08d09365158095290b0c319907215e
3
+ metadata.gz: 07a61d1ecdfd5d0d957caa0dd064631be8c3e546
4
+ data.tar.gz: 364c194ce4cb48938f1cc863b6bdf0a5b7e74e47
5
5
  SHA512:
6
- metadata.gz: 4135007934f8288843da2852a13cb269214725997069a3704b03ad7f4f8f5df103f7824156f1e959de24ca42d6b1a81e72f3fac1eb9b02e174ec9825b66f3b34
7
- data.tar.gz: d605c327290de5f1c71f4a4f552b71f7dc8d2469e146a5814e12581188011e6f462a4b08aa8434d2645e92cebaa76720bf0be14656dce1c0feb99c8e5f0e9ed6
6
+ metadata.gz: d004472bd8b4b3c6e496377c89cb261abcaa59fad5284009ecca65bee86dff428545af4ab27cd47ca74becbcdc9bb0a3d81962a67cf81f6437b5195a990cdf5f
7
+ data.tar.gz: debffc94d7f7fd53064f2b8f5317a68f3cbcaa4e961d19c6c2af630a31c39d79b4b42c111947e361a3f5a838853ff29d4a9814fb0892609da994f4c433517b85
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rubocop/rspec/focused
4
+
5
+ inherit_from: .rubocop_todo.yml
6
+
7
+ AllCops:
8
+ DisplayStyleGuide: true
9
+
10
+ Metrics/LineLength:
11
+ Max: 120
12
+
13
+ Style/SpaceInsideHashLiteralBraces:
14
+ EnforcedStyle: no_space
15
+
16
+ RSpec/DescribedClass:
17
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,43 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2015-12-13 18:06:06 -0500 using RuboCop version 0.35.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: CountComments.
11
+ Metrics/MethodLength:
12
+ Max: 11
13
+
14
+ # Offense count: 2
15
+ # Configuration parameters: CountComments.
16
+ Metrics/ModuleLength:
17
+ Max: 166
18
+
19
+ # Offense count: 65
20
+ # Configuration parameters: CustomTransform, IgnoredWords.
21
+ RSpec/ExampleWording:
22
+ Exclude:
23
+ - 'spec/exel/ast_node_spec.rb'
24
+ - 'spec/exel/context_spec.rb'
25
+ - 'spec/exel/deferred_context_value_spec.rb'
26
+ - 'spec/exel/handlers/s3_handler_spec.rb'
27
+ - 'spec/exel/instruction_node_spec.rb'
28
+ - 'spec/exel/instruction_spec.rb'
29
+ - 'spec/exel/job_spec.rb'
30
+ - 'spec/exel/logging_spec.rb'
31
+ - 'spec/exel/processors/split_processor_spec.rb'
32
+ - 'spec/exel/resource_spec.rb'
33
+ - 'spec/exel/sequence_node_spec.rb'
34
+
35
+ # Offense count: 1
36
+ RSpec/InstanceVariable:
37
+ Exclude:
38
+ - 'spec/exel/logging_spec.rb'
39
+
40
+ # Offense count: 18
41
+ # Configuration parameters: Exclude.
42
+ Style/Documentation:
43
+ Enabled: false
data/Guardfile CHANGED
@@ -1,14 +1,21 @@
1
- guard :rspec, cmd: 'bundle exec rspec' do
2
- require 'guard/rspec/dsl'
3
- dsl = Guard::RSpec::Dsl.new(self)
1
+ group :rspec_rubocop, halt_on_fail: true do
2
+ guard :rspec, cmd: 'bundle exec rspec' do
3
+ require 'guard/rspec/dsl'
4
+ dsl = Guard::RSpec::Dsl.new(self)
4
5
 
5
- # RSpec files
6
- rspec = dsl.rspec
7
- watch(rspec.spec_helper) { rspec.spec_dir }
8
- watch(rspec.spec_support) { rspec.spec_dir }
9
- watch(rspec.spec_files)
6
+ # RSpec files
7
+ rspec = dsl.rspec
8
+ watch(rspec.spec_helper) { rspec.spec_dir }
9
+ watch(rspec.spec_support) { rspec.spec_dir }
10
+ watch(rspec.spec_files)
10
11
 
11
- # Ruby files
12
- ruby = dsl.ruby
13
- dsl.watch_spec_files_for(ruby.lib_files)
12
+ # Ruby files
13
+ ruby = dsl.ruby
14
+ dsl.watch_spec_files_for(ruby.lib_files)
15
+ end
16
+
17
+ guard :rubocop do
18
+ watch(/.+\.rb$/)
19
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
20
+ end
14
21
  end
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # EXEL
2
+ [![Gem Version](https://badge.fury.io/rb/exel.svg)](https://badge.fury.io/rb/exel)
3
+ [![Code Climate](https://codeclimate.com/github/47colborne/exel/badges/gpa.svg)](https://codeclimate.com/github/47colborne/exel)
4
+ [![Build Status](https://snap-ci.com/47colborne/exel/branch/master/build_image)](https://snap-ci.com/47colborne/exel/branch/master)
2
5
 
3
6
  TODO: Write a gem description
4
7
 
data/Rakefile CHANGED
@@ -1,2 +1 @@
1
1
  require 'bundler/gem_tasks'
2
-
data/exel.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = EXEL::VERSION
9
9
  spec.authors = ['yroo']
10
10
  spec.email = ['dev@yroo.com']
11
- spec.summary = %q{EXEL, the Elastic eXEcution Language}
12
- spec.description = %q{A DSL for defining jobs that can be run in a highly scalable manner}
11
+ spec.summary = 'EXEL, the Elastic eXEcution Language'
12
+ spec.description = 'A DSL for defining jobs that can be run in a highly scalable manner'
13
13
  spec.homepage = 'https://github.com/47colborne/exel'
14
14
  spec.license = 'MIT'
15
15
 
@@ -18,14 +18,15 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'aws-sdk', '~> 2'
22
- spec.add_dependency 'sidekiq', '~> 3'
23
-
24
21
  spec.add_development_dependency 'bundler', '~> 1.6'
25
22
  spec.add_development_dependency 'rake', '~> 10'
26
23
  spec.add_development_dependency 'rspec', '~> 3'
27
24
  spec.add_development_dependency 'guard', '~> 2'
28
25
  spec.add_development_dependency 'guard-rspec', '~> 4'
26
+ spec.add_development_dependency 'guard-rubocop', '~> 1'
29
27
  spec.add_development_dependency 'terminal-notifier', '~> 1'
30
28
  spec.add_development_dependency 'terminal-notifier-guard', '~> 1'
29
+ spec.add_development_dependency 'rubocop', '~> 0'
30
+ spec.add_development_dependency 'rubocop-rspec', '~> 1'
31
+ spec.add_development_dependency 'rubocop-rspec-focused', '~> 0'
31
32
  end
data/lib/exel/ast_node.rb CHANGED
@@ -2,7 +2,7 @@ module EXEL
2
2
  class ASTNode
3
3
  attr_reader :instruction, :children
4
4
 
5
- def initialize(instruction, children=[])
5
+ def initialize(instruction, children = [])
6
6
  @instruction = instruction
7
7
  @children = children
8
8
  end
@@ -12,7 +12,7 @@ module EXEL
12
12
  end
13
13
 
14
14
  def run(_context)
15
- raise "#{self.class} does not implement #process"
15
+ fail "#{self.class} does not implement #process"
16
16
  end
17
17
 
18
18
  def add_child(node)
data/lib/exel/context.rb CHANGED
@@ -4,26 +4,24 @@ module EXEL
4
4
  class Context
5
5
  attr_reader :table
6
6
 
7
- def initialize(initial_context={})
7
+ def initialize(initial_context = {})
8
8
  @table = initial_context
9
9
  end
10
10
 
11
11
  def serialize
12
- remotized_table = @table.each_with_object({}) { |(key, value), acc| acc[key] = EXEL::Resource.remotize(value) }
13
- file = serialize_context(remotized_table)
14
- upload(file)
12
+ remotized_table = @table.each_with_object({}) { |(key, value), acc| acc[key] = EXEL::Value.remotize(value) }
13
+ EXEL::Value.remotize(serialized_context(remotized_table))
15
14
  end
16
15
 
17
16
  def self.deserialize(uri)
18
- handler = Handlers::S3Handler.new
19
- file = handler.download(uri)
17
+ file = EXEL::Value.localize(uri)
20
18
  context = Marshal.load(file.read)
21
19
  file.close
22
20
  context
23
21
  end
24
22
 
25
23
  def [](key)
26
- value = EXEL::Resource.localize(@table[key])
24
+ value = EXEL::Value.localize(@table[key])
27
25
  value = get_deferred(value)
28
26
  @table[key] = value
29
27
  value
@@ -43,37 +41,36 @@ module EXEL
43
41
  end
44
42
 
45
43
  def ==(other)
46
- other.kind_of?(EXEL::Context) && table == other.table
44
+ other.is_a?(EXEL::Context) && table == other.table
45
+ end
46
+
47
+ def include?(values)
48
+ @table.merge(values) == @table
47
49
  end
48
50
 
49
51
  private
50
52
 
51
- def serialize_context(table)
53
+ def serialized_context(table)
52
54
  file = Tempfile.new(SecureRandom.uuid, encoding: 'ascii-8bit')
53
55
  file.write(Marshal.dump(Context.new(table)))
54
56
  file.rewind
55
57
  file
56
58
  end
57
59
 
58
- def upload(file)
59
- handler = Handlers::S3Handler.new
60
- handler.upload(file)
61
- end
62
-
63
60
  def get_deferred(value)
64
- if is_deferred?(value)
61
+ if deferred?(value)
65
62
  value = value.get(self)
66
- elsif value.kind_of?(Array)
63
+ elsif value.is_a?(Array)
67
64
  value.map! { |v| get_deferred(v) }
68
- elsif value.kind_of?(Hash)
65
+ elsif value.is_a?(Hash)
69
66
  value.each { |k, v| value[k] = get_deferred(v) }
70
67
  end
71
68
 
72
69
  value
73
70
  end
74
71
 
75
- def is_deferred?(value)
76
- value.kind_of?(DeferredContextValue)
72
+ def deferred?(value)
73
+ value.is_a?(DeferredContextValue)
77
74
  end
78
75
  end
79
76
  end
@@ -12,7 +12,7 @@ module EXEL
12
12
  end
13
13
 
14
14
  def get(context)
15
- keys.reduce(context) { |acc, key| acc[key] }
15
+ keys.reduce(context) { |a, e| a[e] }
16
16
  end
17
17
  end
18
- end
18
+ end
@@ -1,8 +1,8 @@
1
1
  module EXEL
2
- class EXEL::Instruction
2
+ class Instruction
3
3
  attr_reader :name
4
4
 
5
- def initialize(name, processor_class, args, subtree=nil)
5
+ def initialize(name, processor_class, args, subtree = nil)
6
6
  @name = name
7
7
  @processor_class = processor_class
8
8
  @args = args || {}
data/lib/exel/job.rb CHANGED
@@ -2,7 +2,7 @@ module EXEL
2
2
  module Job
3
3
  class << self
4
4
  def define(job_name, &block)
5
- raise "Job #{job_name.inspect} is already defined" unless registry[job_name].nil?
5
+ fail "Job #{job_name.inspect} is already defined" unless registry[job_name].nil?
6
6
  registry[job_name] = block
7
7
  end
8
8
 
@@ -12,7 +12,7 @@ module EXEL
12
12
 
13
13
  def run(dsl_code_or_name, context = {})
14
14
  context = EXEL::Context.new(context) if context.is_a?(Hash)
15
- (ast = parse(dsl_code_or_name)) ? ast.start(context) : raise(%(Job "#{dsl_code_or_name}" not found))
15
+ (ast = parse(dsl_code_or_name)) ? ast.start(context) : fail(%(Job "#{dsl_code_or_name}" not found))
16
16
  end
17
17
 
18
18
  private
@@ -49,11 +49,11 @@ module EXEL
49
49
  add_instruction_node('process', processor_class, block, options)
50
50
  end
51
51
 
52
- def async(options={}, &block)
52
+ def async(options = {}, &block)
53
53
  add_instruction_node('async', Processors::AsyncProcessor, block, options)
54
54
  end
55
55
 
56
- def split(options={}, &block)
56
+ def split(options = {}, &block)
57
57
  add_instruction_node('split', Processors::SplitProcessor, block, options)
58
58
  end
59
59
 
@@ -63,7 +63,7 @@ module EXEL
63
63
 
64
64
  private
65
65
 
66
- def add_instruction_node(name, processor, block, args={})
66
+ def add_instruction_node(name, processor, block, args = {})
67
67
  sub_tree = block.nil? ? nil : Parser.parse(block)
68
68
  instruction = EXEL::Instruction.new(name, processor, args, sub_tree)
69
69
  node = sub_tree.nil? ? InstructionNode.new(instruction) : InstructionNode.new(instruction, [sub_tree])
data/lib/exel/logging.rb CHANGED
@@ -15,11 +15,11 @@ module EXEL
15
15
  end
16
16
 
17
17
  def self.log_filename
18
- EXEL.configuration[:log_filename] || '/dev/null'
18
+ EXEL.configuration.log_filename || '/dev/null'
19
19
  end
20
20
 
21
21
  def self.log_level
22
- level = EXEL.configuration[:log_level] || DEFAULT_LEVEL
22
+ level = EXEL.configuration.log_level || DEFAULT_LEVEL
23
23
  Logger.const_get(level.to_s.upcase)
24
24
  end
25
25
 
@@ -27,4 +27,4 @@ module EXEL
27
27
  @logger = logger || Logger.new('/dev/null')
28
28
  end
29
29
  end
30
- end
30
+ end
@@ -3,4 +3,4 @@ module EXEL
3
3
  def execute(context)
4
4
  end
5
5
  end
6
- end
6
+ end
@@ -1,6 +1,5 @@
1
1
  module EXEL
2
2
  module ProcessorHelper
3
-
4
3
  # Helper Methods
5
4
 
6
5
  def tag(*tags)
@@ -12,7 +11,7 @@ module EXEL
12
11
  end
13
12
 
14
13
  def file_size_in_mb(file)
15
- "#{'%.2f' % (file.size.to_f / 1_024_000).round(2)} MB"
14
+ format('%.2f MB', file.size.to_f / 1_024_000)
16
15
  end
17
16
 
18
17
  # Logging Helpers
@@ -38,10 +37,10 @@ module EXEL
38
37
  end
39
38
 
40
39
  def log_transaction(message = '')
41
- transaction_start_time = Time.now.to_f
40
+ transaction_start_time = Time.now
42
41
  log_info "Started at #{transaction_start_time}"
43
42
  yield(transaction_start_time)
44
- transaction_end_time = Time.now.to_f
43
+ transaction_end_time = Time.now
45
44
  log_info "Finished in #{(transaction_end_time - transaction_start_time).to_i} seconds #{message}"
46
45
  end
47
46
 
@@ -62,6 +61,5 @@ module EXEL
62
61
  time_to_sleep = duration.second.to_f - elapsed_time
63
62
  sleep(time_to_sleep) if time_to_sleep > 0
64
63
  end
65
-
66
64
  end
67
65
  end
@@ -4,18 +4,18 @@ module EXEL
4
4
  module Processors
5
5
  class AsyncProcessor
6
6
  include EXEL::ProcessorHelper
7
- attr_reader :handler
7
+ attr_reader :provider
8
8
 
9
9
  def initialize(context)
10
10
  @context = context
11
- @handler = EXEL::Handlers::SidekiqHandler.new(context)
11
+ @provider = EXEL.async_provider.new(context)
12
12
 
13
13
  log_prefix_with '[AsyncProcessor]'
14
14
  end
15
15
 
16
16
  def process(block)
17
17
  log_process do
18
- @handler.do_async(block)
18
+ @provider.do_async(block)
19
19
  log_info 'call to async completed'
20
20
  end
21
21
  end
@@ -0,0 +1,18 @@
1
+ module EXEL
2
+ module Providers
3
+ class LocalFileProvider
4
+ def upload(file)
5
+ "file://#{file.path}"
6
+ end
7
+
8
+ def download(uri)
9
+ fail 'URI must begin with "file://"' unless uri.start_with? 'file://'
10
+ File.open(uri.split('file://').last)
11
+ end
12
+
13
+ def self.remote?(uri)
14
+ uri =~ %r{file://}
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module EXEL
2
+ module Providers
3
+ class ThreadedAsyncProvider
4
+ def initialize(context)
5
+ @context = context
6
+ end
7
+
8
+ def do_async(block)
9
+ Thread.new { block.start(@context) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,11 +1,11 @@
1
1
  module EXEL
2
- class Resource
2
+ module Value
3
3
  def self.remotize(value)
4
4
  file?(value) ? upload(value) : value
5
5
  end
6
6
 
7
7
  def self.localize(value)
8
- serialized?(value) ? deserialize_file(value) : value
8
+ remote?(value) ? download(value) : value
9
9
  end
10
10
 
11
11
  class << self
@@ -15,20 +15,16 @@ module EXEL
15
15
  value.is_a?(File) || value.is_a?(Tempfile)
16
16
  end
17
17
 
18
- def serialized?(value)
19
- value =~ %r{^s3://}
18
+ def upload(file)
19
+ EXEL.remote_provider.new.upload(file)
20
20
  end
21
21
 
22
- def deserialize_file(uri)
23
- download(uri)
22
+ def remote?(value)
23
+ EXEL.remote_provider.remote?(value)
24
24
  end
25
25
 
26
26
  def download(uri)
27
- Handlers::S3Handler.new.download(uri)
28
- end
29
-
30
- def upload(file)
31
- Handlers::S3Handler.new.upload(file)
27
+ EXEL.remote_provider.new.download(uri)
32
28
  end
33
29
  end
34
30
  end
data/lib/exel/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module EXEL
2
- VERSION = '0.9.0'
2
+ VERSION = '1.0.0'
3
3
  end
data/lib/exel.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'exel/version'
2
2
  require 'exel/logging'
3
+ require 'ostruct'
3
4
 
4
5
  module EXEL
5
6
  def self.logger
@@ -11,13 +12,21 @@ module EXEL
11
12
  end
12
13
 
13
14
  def self.configuration
14
- @config ||= {}
15
+ @config ||= OpenStruct.new
15
16
  end
16
17
 
17
18
  def self.configure
18
19
  yield configuration
19
20
  end
20
21
 
22
+ def self.async_provider
23
+ configuration.async_provider || Providers::ThreadedAsyncProvider
24
+ end
25
+
26
+ def self.remote_provider
27
+ configuration.remote_provider || Providers::LocalFileProvider
28
+ end
29
+
21
30
  root = File.expand_path('../..', __FILE__)
22
31
  Dir[File.join(root, 'lib/exel/**/*.rb')].each { |file| require file }
23
32
  end
@@ -1,23 +1,14 @@
1
1
  module EXEL
2
2
  describe ASTNode do
3
- let(:context) { {} }
4
-
5
- def build_tree
6
- @node_3 = ASTNode.new(instruction)
7
- @node_4 = ASTNode.new(instruction)
8
- @node_2 = ASTNode.new(instruction, [@node_3, @node_4])
9
- @node_5 = ASTNode.new(instruction)
10
- @node_1 = ASTNode.new(instruction, [@node_2, @node_5])
11
- end
3
+ let(:context) { instance_double(EXEL::Context) }
12
4
 
13
5
  def instruction
14
6
  instance_double(Instruction, execute: nil)
15
7
  end
16
8
 
17
- describe '#start' do
18
- class TestNode < ASTNode
19
- end
9
+ TestNode = Class.new(ASTNode)
20
10
 
11
+ describe '#start' do
21
12
  context 'when an JobTermination error bubbles up' do
22
13
  it 'should ensure the process fails silently' do
23
14
  node = TestNode.new(instruction)
@@ -29,9 +20,6 @@ module EXEL
29
20
  end
30
21
 
31
22
  describe '#run' do
32
- class TestNode < ASTNode;
33
- end
34
-
35
23
  it 'should raise an error if not implemented' do
36
24
  expect { TestNode.new(instruction).run(context) }.to raise_error 'EXEL::TestNode does not implement #process'
37
25
  end