jackal 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,2 +1,7 @@
1
+ # v0.1.2
2
+ * Update configuration convention to require toplevel namespace
3
+ * Clean up testing helper and setup
4
+ * Include basic test for proper setup
5
+
1
6
  # v0.1.0
2
7
  * Initial commit
data/README.md CHANGED
@@ -1,3 +1,89 @@
1
1
  # Jackal
2
2
 
3
- Run your carnivores
3
+ Run your carnivores.
4
+
5
+ ## Configuration
6
+
7
+ ```json
8
+ {
9
+ "jackal": {
10
+ "require": [
11
+ "carnivore-http",
12
+ "fubar-helloer"
13
+ ]
14
+ },
15
+ "fubar": {
16
+ "helloer": {
17
+ "sources": {
18
+ "input": {
19
+ ...
20
+ },
21
+ "output": {
22
+ ...
23
+ },
24
+ "error": {
25
+ ...
26
+ }
27
+ },
28
+ "callbacks": [
29
+ "Fubar::Helloer::SayHello"
30
+ ],
31
+ "config": {
32
+ "output_prefix": "Received message: "
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ * `jackal` provides subsystem configuration
40
+ * `require` libraries to load at startup
41
+ * `fubar` configuration of components (snake cased top level module)
42
+ * `helloer` configuration of specific component (snake cased second level module)
43
+ * `sources` configuration for carnivore sources
44
+ * `callbacks` callback class names to initialize and attach to input source
45
+ * `config` configuration hash used by callbacks
46
+
47
+ ## Jackal Callbacks
48
+
49
+ Jackal callbacks are subclassed Carnivore callbacks adding a bit more structure. The
50
+ general implementation of a Jackal callback:
51
+
52
+ ```ruby
53
+ module Fubar
54
+ module Helloer
55
+ class SayHello < Jackal::Callback
56
+
57
+ def valid?(message)
58
+ super do |payload|
59
+ payload.get(:data, :helloer, :output)
60
+ end
61
+ end
62
+
63
+ def execute(message)
64
+ failure_wrap(message) do |payload|
65
+ info config[:output_prefix] + payload.get(:data, :helloer, :output)
66
+ job_completed(:helloer, payload, message)
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+ ```
74
+
75
+ ## Testing
76
+
77
+ Jackal provides test helpers building upon the helpers provided by
78
+ Carnivore.
79
+
80
+ ### jackal-test
81
+
82
+ This executable will load minitest and auto run all files matched
83
+ by the glob: `test/specs/*.rb`.
84
+
85
+ ## Info
86
+
87
+ * Repository: https://github.com/carnivore-rb/jackal
88
+ * Carnivore: https://github.com/carnivore-rb/carnivore
89
+ * IRC: Freenode @ #carnivore
data/bin/jackal CHANGED
File without changes
data/bin/jackal-test ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+ # -*- encoding: utf-8 -*-
4
+
5
+ require 'jackal/utils/spec/loader'
data/jackal.gemspec CHANGED
@@ -14,4 +14,5 @@ Gem::Specification.new do |s|
14
14
  s.add_dependency 'mixlib-cli'
15
15
  s.files = Dir['lib/**/*'] + %w(jackal.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
16
16
  s.executables << 'jackal'
17
+ s.executables << 'jackal-test'
17
18
  end
data/lib/jackal.rb CHANGED
@@ -4,6 +4,7 @@ module Jackal
4
4
  autoload :Cli, 'jackal/cli'
5
5
  autoload :Callback, 'jackal/callback'
6
6
  autoload :Error, 'jackal/error'
7
+ autoload :Utils, 'jackal/utils'
7
8
  end
8
9
 
9
10
  require 'jackal/version'
@@ -1,25 +1,45 @@
1
1
  require 'jackal'
2
2
 
3
3
  module Jackal
4
+ # Jackal customized callback
4
5
  class Callback < Carnivore::Callback
5
6
 
6
- def config_key
7
- self.class.name.split('::').first.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase
7
+ include Utils::Payload
8
+
9
+ # @return [Array] key path in configuration
10
+ def config_path
11
+ self.class.name.split('::')[0,2].map do |string|
12
+ string.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase
13
+ end
14
+ end
15
+
16
+ # @return [String] prefix of source for this callback
17
+ def source_prefix
18
+ config_path.join('_')
8
19
  end
9
20
 
10
- def new_payload(payload)
11
- Smash.new(
12
- :id => Celluloid.uuid,
13
- :data => payload
14
- )
21
+ # @return [Hash] configuration
22
+ def config
23
+ Carnviore::Config.get(*config_path.push(:config)) || Smash.new
24
+ end
25
+
26
+ # Validity of message
27
+ #
28
+ # @param message [Carnivore::Message]
29
+ # @return [TrueClass, FalseClass]
30
+ def valid?(message)
31
+ m = unpack(message)
32
+ block_given? ? yield(m) : true
15
33
  end
16
34
 
17
- # message:: Original message
18
35
  # Executes block and catches unexpected exceptions if encountered
36
+ #
37
+ # @param message [Carnivore::Message]
38
+ # @return [Object]
19
39
  def failure_wrap(message)
20
40
  abort 'Failure wrap requires block for execution' unless block_given?
21
41
  begin
22
- payload = message[:message]
42
+ payload = unpack(message)
23
43
  yield payload
24
44
  rescue => e
25
45
  error "!!! Unexpected failure encountered -> #{e.class}: #{e}"
@@ -28,29 +48,51 @@ module Jackal
28
48
  end
29
49
  end
30
50
 
51
+ # Send payload to error handler
52
+ #
53
+ # @param payload [Hash]
54
+ # @param message [Carnivore::Message]
55
+ # @param reason [String]
31
56
  def failed(payload, message, reason='No reason provided')
32
57
  error "Processing of #{message} failed! Reason: #{reason}"
33
58
  message.confirm!
34
- destination = "#{config_key}_error"
59
+ destination = "#{source_prefix}_error"
35
60
  source = Carnivore::Supervisor.supervisor[destination]
36
61
  error "Sending #{message} to error handler: #{source}"
37
62
  source.transmit(payload)
38
63
  end
39
64
 
65
+ # Mark payload complete and forward
66
+ #
67
+ # @param payload [Hash]
68
+ # @param message [Carnivore::Message]
40
69
  def completed(payload, message)
41
70
  message.confirm!
42
71
  info "Processing of #{message} complete on this callback"
43
72
  forward(payload)
44
73
  end
45
74
 
75
+ # Forward payload to output source
76
+ #
77
+ # @param payload [Hash]
46
78
  def forward(payload)
47
- destination = "#{config_key}_output"
79
+ destination = "#{source_prefix}_output"
48
80
  source = Carnivore::Supervisor.supervisor[destination]
49
- info "Forwarding payload to output destination... (#{source})"
50
- debug "Forwarded payload: #{payload.inspect}"
51
- source.transmit(payload)
81
+ if(source)
82
+ info "Forwarding payload to output destination... (#{source})"
83
+ debug "Forwarded payload: #{payload.inspect}"
84
+ source.transmit(payload)
85
+ else
86
+ warn "No destination source found for generated source path: #{destination}"
87
+ info "Processing of message #{message} has completed. Message now discarded."
88
+ end
52
89
  end
53
90
 
91
+ # Mark job as completed
92
+ #
93
+ # @param name [String]
94
+ # @param payload [Hash]
95
+ # @param message [Carnivore::Message]
54
96
  def job_completed(name, payload, message)
55
97
  info "Processing of message #{message} has completed within this component #{name}"
56
98
  message.confirm!
data/lib/jackal/cli.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'mixlib/cli'
2
2
 
3
3
  module Jackal
4
+ # CLI interface
4
5
  class Cli
5
6
  include Mixlib::CLI
6
7
 
data/lib/jackal/error.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'jackal'
2
2
 
3
3
  module Jackal
4
+ # Base error class
4
5
  class Error < StandardError
5
6
  end
6
7
  end
data/lib/jackal/loader.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  require 'carnivore'
2
2
  require 'jackal'
3
3
 
4
- cli = Jackal::Cli.new
5
- cli.parse_options
6
- Carnivore::Config.configure(cli.config)
4
+ unless(ENV['JACKAL_TESTING_MODE'])
5
+ cli = Jackal::Cli.new
6
+ cli.parse_options
7
+ Carnivore::Config.configure(cli.config)
8
+ end
9
+
7
10
  Carnivore::Config.auto_symbolize(true)
8
11
 
9
12
  (Carnivore::Config.get(:jackal, :require) || []).each do |path|
@@ -11,26 +14,25 @@ Carnivore::Config.auto_symbolize(true)
11
14
  end
12
15
 
13
16
  begin
14
- Carnivore::Utils.symbolize_hash(Carnivore::Config.hash_dup).each do |key, opts|
15
- next if key == :jackal || !opts.is_a?(Hash)
16
- Carnivore::Utils.info "Processing: #{opts.inspect}"
17
- Carnivore.configure do
18
- opts.fetch(:sources, {}).each do |kind, source_args|
19
- source = Carnivore::Source.build(
20
- :type => source_args[:type].to_sym,
21
- :args => source_args.fetch(:args, {}).merge(:name => "#{key}_#{kind}")
22
- )
23
- Carnivore::Utils.info "Initialized new source: #{key}_#{kind}"
24
- if(kind == :input)
25
- opts.fetch(:callbacks, []).each do |klass_name|
26
- klass = klass_name.split('::').inject(
27
- Object.const_get(
28
- key.to_s.split('_').map(&:capitalize).join
29
- )
30
- ) do |memo, name|
31
- memo.const_get(name)
17
+ Carnivore::Utils.symbolize_hash(Carnivore::Config.hash_dup).each do |namespace, args|
18
+ next unless args.is_a?(Hash)
19
+ args.each do |key, opts|
20
+ next unless opts.is_a?(Hash) && opts[:sources]
21
+ Carnivore::Utils.info "Processing: #{opts.inspect}"
22
+ Carnivore.configure do
23
+ opts.fetch(:sources, {}).each do |kind, source_args|
24
+ source = Carnivore::Source.build(
25
+ :type => source_args[:type].to_sym,
26
+ :args => source_args.fetch(:args, {}).merge(:name => "#{namespace}_#{key}_#{kind}")
27
+ )
28
+ Carnivore::Utils.info "Initialized new source: #{namespace}_#{key}_#{kind}"
29
+ if(kind == :input)
30
+ opts.fetch(:callbacks, []).each do |klass_name|
31
+ klass = klass_name.split('::').inject(Object) do |memo, name|
32
+ memo.const_get(name)
33
+ end
34
+ source.add_callback(klass_name, klass)
32
35
  end
33
- source.add_callback(klass_name, klass)
34
36
  end
35
37
  end
36
38
  end
@@ -0,0 +1,38 @@
1
+ require 'jackal'
2
+
3
+ module Jackal
4
+ # Helper utilities
5
+ module Utils
6
+
7
+ autoload :Spec, 'jackal/utils/spec'
8
+
9
+ # Module utilities
10
+ module Payload
11
+
12
+ # Generate a new payload
13
+ #
14
+ # @param name [String]
15
+ # @param payload [Hash]
16
+ # @return [Smash]
17
+ def new_payload(name, payload)
18
+ Smash.new(
19
+ :name => name,
20
+ :id => Celluloid.uuid,
21
+ :data => payload
22
+ )
23
+ end
24
+
25
+ # Extract payload from message
26
+ #
27
+ # @param message [Carnivore::Message]
28
+ # @return [Smash]
29
+ def unpack(message)
30
+ Smash.new(message[:message])
31
+ end
32
+
33
+ end
34
+
35
+ extend Payload
36
+
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ require 'jackal'
2
+
3
+ module Jackal
4
+ module Utils
5
+ # Testing helpers
6
+ module Spec
7
+ autoload :CallbackLocal, 'jackal/utils/spec/callback_local'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ require 'jackal'
2
+
3
+ module Jackal
4
+ module Utils
5
+ module Spec
6
+ # Callback helper module for isolated testing
7
+ module CallbackLocal
8
+
9
+ # @return [Array] forwarded payloads
10
+ def forwarded
11
+ @forwarded ||= []
12
+ end
13
+
14
+ # Force payload into local store
15
+ #
16
+ # @param payload [Hash]
17
+ def forward(payload)
18
+ @forwarded << payload
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ require 'multi_json'
2
+ require 'carnivore/spec_helper'
3
+
4
+ Celluloid.logger.level = 0 if ENV['DEBUG']
5
+
6
+ # Default source setup higher than base carivore default
7
+ unless(ENV['CARNIVORE_SOURCE_SETUP'])
8
+ ENV['CARNIVORE_SOURCE_SETUP'] = '0.5'
9
+ end
10
+
11
+ # Pass any jackal specific wait settings down to carnivore
12
+ ENV.each do |key, value|
13
+ if(key.start_with?('JACKAL_SOURCE_'))
14
+ carnivore_key = key.sub('JACKAL_SOURCE', 'CARNIVORE_SOURCE')
15
+ ENV[carnivore_key] = value
16
+ end
17
+ end
18
+
19
+ def payload_for(style, args={})
20
+ file = "#{style}.json"
21
+ path = [File.join(Dir.pwd, 'test/specs/payloads'), File.join(File.dirname(__FILE__), 'payloads')].map do |dir|
22
+ if(File.exists?(full_path = File.join(dir, file)))
23
+ full_path
24
+ end
25
+ end.compact.first
26
+ if(path)
27
+ if(args[:raw])
28
+ MultiJson.load(File.read(path))
29
+ else
30
+ if(args[:nest])
31
+ Jackal::Utils.new_payload(:test, args[:nest] => MultiJson.load(File.read(path)))
32
+ else
33
+ Jackal::Utils.new_payload(:test, File.read(path))
34
+ end
35
+ end
36
+ else
37
+ raise "Requested payload path for test does not exist: #{File.expand_path(path)}"
38
+ end
39
+ end
40
+
41
+ def run_setup(config)
42
+ path = File.join(Dir.pwd, 'test/specs/config', "#{config}.json")
43
+ Carnivore::Config.configure(:config_path => path)
44
+ runner = Thread.new do
45
+ require 'jackal/loader'
46
+ end
47
+ source_wait(:setup)
48
+ runner
49
+ end
@@ -0,0 +1,14 @@
1
+ require 'carnivore/config'
2
+
3
+ ENV['JACKAL_TESTING_MODE'] = 'true'
4
+
5
+ path = File.join(Dir.pwd, 'test')
6
+
7
+ if(File.directory?(path))
8
+ if(File.exists?(spec_file = File.join(path, 'spec.rb')))
9
+ require spec_file
10
+ end
11
+ require 'jackal/utils/spec/runner'
12
+ else
13
+ raise "No test directory found: #{path}"
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'jackal/utils/spec/helpers'
2
+
3
+ path = File.join(Dir.pwd, 'test', 'specs')
4
+
5
+ if(File.directory?(path))
6
+ Dir.glob(File.join(path, '*.rb')).each do |spec|
7
+ require spec
8
+ end
9
+ else
10
+ raise "No specs directory found: #{path}"
11
+ end
@@ -1,5 +1,5 @@
1
1
  module Jackal
2
2
  class Version < Gem::Version
3
3
  end
4
- VERSION = Version.new('0.1.0')
4
+ VERSION = Version.new('0.1.2')
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jackal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-01 00:00:00.000000000 Z
12
+ date: 2014-05-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: carnivore
@@ -47,14 +47,21 @@ description: Message processing helper
47
47
  email: code@chrisroberts.org
48
48
  executables:
49
49
  - jackal
50
+ - jackal-test
50
51
  extensions: []
51
52
  extra_rdoc_files: []
52
53
  files:
53
54
  - lib/jackal/callback.rb
54
55
  - lib/jackal/version.rb
56
+ - lib/jackal/utils/spec/callback_local.rb
57
+ - lib/jackal/utils/spec/runner.rb
58
+ - lib/jackal/utils/spec/helpers.rb
59
+ - lib/jackal/utils/spec/loader.rb
60
+ - lib/jackal/utils/spec.rb
55
61
  - lib/jackal/cli.rb
56
62
  - lib/jackal/loader.rb
57
63
  - lib/jackal/error.rb
64
+ - lib/jackal/utils.rb
58
65
  - lib/jackal.rb
59
66
  - jackal.gemspec
60
67
  - README.md
@@ -62,6 +69,7 @@ files:
62
69
  - CONTRIBUTING.md
63
70
  - LICENSE
64
71
  - bin/jackal
72
+ - bin/jackal-test
65
73
  homepage: https://github.com/carnivore-rb/jackal
66
74
  licenses:
67
75
  - Apache 2.0