jackal 0.1.0 → 0.1.2

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.
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