carnivore 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ carnivore (0.1.0)
5
+ bunny
6
+ celluloid
7
+ fog
8
+ mixlib-config
9
+ reel
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ builder (3.2.2)
15
+ bunny (0.8.0)
16
+ celluloid (0.15.1)
17
+ timers (~> 1.1.0)
18
+ celluloid-io (0.15.0)
19
+ celluloid (>= 0.15.0)
20
+ nio4r (>= 0.5.0)
21
+ certified (0.1.1)
22
+ excon (0.25.3)
23
+ fog (1.15.0)
24
+ builder
25
+ excon (~> 0.25.0)
26
+ formatador (~> 0.2.0)
27
+ mime-types
28
+ multi_json (~> 1.0)
29
+ net-scp (~> 1.1)
30
+ net-ssh (>= 2.1.3)
31
+ nokogiri (~> 1.5)
32
+ ruby-hmac
33
+ formatador (0.2.4)
34
+ http (0.4.0)
35
+ certified
36
+ http_parser.rb
37
+ http_parser.rb (0.5.3)
38
+ mime-types (1.25)
39
+ mini_portile (0.5.1)
40
+ mixlib-config (1.1.2)
41
+ multi_json (1.8.0)
42
+ net-scp (1.1.2)
43
+ net-ssh (>= 2.6.5)
44
+ net-ssh (2.6.8)
45
+ nio4r (0.5.0)
46
+ nokogiri (1.6.0)
47
+ mini_portile (~> 0.5.0)
48
+ rack (1.5.2)
49
+ reel (0.3.0)
50
+ celluloid-io (>= 0.8.0)
51
+ http (>= 0.2.0)
52
+ http_parser.rb (>= 0.5.3)
53
+ rack (>= 1.4.0)
54
+ websocket_parser (>= 0.1.0)
55
+ ruby-hmac (0.4.0)
56
+ timers (1.1.0)
57
+ websocket_parser (0.1.4)
58
+ http
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ carnivore!
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # Carnivore
2
+
3
+ Eat messages, rule the world.
4
+
5
+ ## Purpose
6
+
7
+ Slim library to consume messages. Sources are defined
8
+ and callbacks are registered to defined sources. Sources
9
+ feed messages to callback workers asynchronously and
10
+ stuff gets done. Super simple!
11
+
12
+ ## Usage
13
+
14
+ 1. Build a source
15
+ 2. Add callbacks
16
+ 3. Profit!
17
+
18
+ ```ruby
19
+ Carnivore.configure do
20
+ src = Source.build(:type => :test, :args => {})
21
+ src.add_callback(:print_message) do |msg|
22
+ puts "Received message: #{message}"
23
+ end
24
+ end.start!
25
+ ```
26
+
27
+ ### Advanced Usage
28
+
29
+ Under the hood, callbacks are built into `Carnivore::Callback`
30
+ instances. This class can be subclassed and provided directly
31
+ instead of a simple block. This has the added bonus of being
32
+ able to define the number of worker instances to be created
33
+ for the callback (blocks default to 1):
34
+
35
+ ```ruby
36
+ require 'carnivore'
37
+
38
+ class CustomCallback < Carnivore::Callback
39
+
40
+ self.workers = 5
41
+
42
+ def setup
43
+ info "Custom callback setup called!"
44
+ end
45
+
46
+ def execute(message)
47
+ info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
48
+ end
49
+ end
50
+
51
+ Carnivore.configure do
52
+ s = Carnivore::Source.build(:type => :test, :args => {})
53
+ s.add_callback(:printer, CustomCallback)
54
+ end.start!
55
+ ```
56
+
57
+ ## Info
58
+
59
+ * Repository: https://github.com/heavywater/carnivore
data/carnivore.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'carnivore/version'
3
+ Gem::Specification.new do |s|
4
+ s.name = 'carnivore'
5
+ s.version = Carnivore::VERSION.version
6
+ s.summary = 'Message processing helper'
7
+ s.author = 'Chris Roberts'
8
+ s.email = 'chrisroberts.code@gmail.com'
9
+ s.homepage = 'https://github.com/heavywater/carnivore'
10
+ s.description = 'Message processing helper'
11
+ s.require_path = 'lib'
12
+ s.add_dependency 'fog'
13
+ s.add_dependency 'celluloid'
14
+ s.add_dependency 'reel'
15
+ s.add_dependency 'mixlib-config'
16
+ s.add_dependency 'bunny'
17
+ s.files = Dir['**/*']
18
+ end
@@ -0,0 +1,10 @@
1
+ require 'carnivore'
2
+
3
+ Carnivore.configure do
4
+ s = Carnivore::Source.build(:type => :test, :args => {})
5
+
6
+ s.add_callback(:printer) do |message|
7
+ info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
8
+ end
9
+
10
+ end.start!
@@ -0,0 +1,20 @@
1
+ require 'carnivore'
2
+
3
+ class CustomCallback < Carnivore::Callback
4
+
5
+ self.workers = 5
6
+
7
+ def setup
8
+ info "Custom callback setup called!"
9
+ end
10
+
11
+ def execute(message)
12
+ info "GOT MESSAGE: #{message[:message]} - source: #{message[:source]} - instance: #{self}"
13
+ end
14
+ end
15
+
16
+ Carnivore.configure do
17
+ s = Carnivore::Source.build(:type => :test, :args => {})
18
+ s.add_callback(:printer, CustomCallback)
19
+
20
+ end.start!
@@ -0,0 +1,10 @@
1
+ require 'carnivore'
2
+
3
+ Carnivore.configure do
4
+ s = Carnivore::Source.build(:type => :http, :args => {:bind => '0.0.0.0', :port => 3000})
5
+
6
+ s.add_callback(:printer) do |message|
7
+ info "GOT MESSAGE: #{message[:message][:body]} - path: #{message[:message][:request].url} - method: #{message[:message][:request].method} - source: #{message[:source]} - instance: #{self}"
8
+ end
9
+
10
+ end.start!
@@ -0,0 +1,48 @@
1
+ module Carnivore
2
+ class Callback
3
+
4
+ class << self
5
+ attr_accessor :workers
6
+ end
7
+
8
+ include Celluloid
9
+ include Celluloid::Logger
10
+
11
+ execute_block_on_receiver :execute
12
+
13
+ attr_reader :name
14
+
15
+ def initialize(name, block=nil)
16
+ @name = name
17
+ @block = block
18
+ if(@block.nil? && self.class == Callback)
19
+ raise ArgumentError.new 'Block is required for dynamic callbacks!'
20
+ end
21
+ setup
22
+ end
23
+
24
+ def setup
25
+ end
26
+
27
+ def inspect
28
+ "callback<#{self.object_id}>"
29
+ end
30
+
31
+ def valid?(message)
32
+ true
33
+ end
34
+
35
+ def call(message)
36
+ raise TypeError.new('Invalid message for this callback!') unless valid?(message)
37
+ @block ? execute(message, &@block) : execute(message)
38
+ rescue => e
39
+ error "[callback: #{self}, source: #{message[:source]}, message: #{message[:message].object_id}]: #{e.class} - #{e}"
40
+ debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
41
+ end
42
+
43
+ def execute(message)
44
+ yield message
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require 'mixlib/config'
3
+
4
+ module Carnivore
5
+ class Config
6
+
7
+ extend Mixlib::Config
8
+
9
+ class << self
10
+
11
+ def configure(args)
12
+ build(args[:config_path]) if args[:config_path]
13
+ self.merge!(args)
14
+ self
15
+ end
16
+
17
+ def build(path_or_hash)
18
+ if(path_or_hash.is_a?(Hash))
19
+ conf = path_or_hash
20
+ else
21
+ if(File.exists?(path_or_hash.to_s))
22
+ conf = JSON.load(File.read(path_or_hash))
23
+ self.config_path = path_or_hash
24
+ else
25
+ raise "Failed to load configuration file: #{path_or_hash}"
26
+ end
27
+ end
28
+ conf.each do |k,v|
29
+ self.send(k, v)
30
+ end
31
+ self
32
+ end
33
+
34
+ def get(*ary)
35
+ ary.flatten.inject(self) do |memo, key|
36
+ memo[key.to_s] || memo[key.to_sym] || break
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ require 'celluloid/logger'
2
+
3
+ module Carnivore
4
+ class Container < Module
5
+
6
+ include Celluloid::Logger
7
+
8
+ class << self
9
+ def log
10
+ Celluloid::Logger
11
+ end
12
+ end
13
+ def log
14
+ Celluloid::Logger
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ require 'celluloid'
2
+ require 'carnivore/source'
3
+ require 'carnivore/container'
4
+
5
+ module Carnivore
6
+ class << self
7
+ def configure(&block)
8
+ mod = Container.new
9
+ mod.instance_exec(mod, &block)
10
+ self
11
+ end
12
+
13
+ def start!
14
+ begin
15
+ require 'carnivore/supervisor'
16
+ Supervisor.run
17
+ rescue Exception => e
18
+ # Gracefully shut down
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require 'reel'
2
+ require 'carnivore/source'
3
+
4
+ module Carnivore
5
+ class Source
6
+
7
+ class Http < Source
8
+
9
+ attr_reader :args
10
+
11
+ def setup(args={})
12
+ @args = default_args(args)
13
+ end
14
+
15
+ def default_args(args)
16
+ {
17
+ :bind => '0.0.0.0',
18
+ :port => '3000'
19
+ }.merge(args)
20
+ end
21
+
22
+ def process(*process_args)
23
+ srv = Reel::Server.supervise(args[:bind], args[:port]) do |con|
24
+ while(req = con.request)
25
+ begin
26
+ msg = format(:request => req, :body => req.body)
27
+ callbacks.each do |name|
28
+ c_name = callback_name(name)
29
+ debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{c_name})>"
30
+ Celluloid::Actor[c_name].async.call(msg)
31
+ end
32
+ con.respond(:ok, 'So long, and thanks for all the fish!')
33
+ rescue => e
34
+ con.respond(:bad_request, 'Failed to process request')
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ require 'fog'
2
+ require 'carnivore/source'
3
+
4
+ module Carnivore
5
+ class Source
6
+ class RabbitMq < Source
7
+
8
+ def setup(args={})
9
+ @bunny = nil
10
+ @connection_args = args[:bunny]
11
+ @queue_name = args[:queue]
12
+ @exchange_name = args[:exchange]
13
+ debug "Creating RabbitMq source instance <#{name}>"
14
+ end
15
+
16
+ def connect
17
+ @bunny = Bunny.new(@connection_args)
18
+ @bunny.start
19
+ @channel = @bunny.create_channel
20
+ @exchange = @channel.topic(@exchange_name)
21
+ @queue = @channel.queue(@queue_name).bind(@exchange) # TODO: Add topic key
22
+ end
23
+
24
+ def process
25
+ @queue.subscribe do |info, metadata, payload|
26
+ msg = format(payload)
27
+ callbacks.each do |name|
28
+ c_name = callback_name(name)
29
+ debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{c_name})>"
30
+ Celluloid::Actor[c_name].async.call(msg)
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,76 @@
1
+ require 'fog'
2
+ require 'carnivore/source'
3
+
4
+ module Carnivore
5
+ class Source
6
+ class Sqs < Source
7
+
8
+ OUTPUT_REPEAT_EVERY=5
9
+
10
+ attr_reader :pause_time
11
+
12
+ def setup(args={})
13
+ @fog = nil
14
+ @connection_args = args[:fog]
15
+ @queue = args[:queue_url]
16
+ @pause_time = args[:pause] || 5
17
+ @receive_timeout = after(args[:receive_timeout] || 30){ terminate }
18
+ debug "Creating SQS source instance <#{name}>"
19
+ end
20
+
21
+ def connect
22
+ @fog = Fog::AWS::SQS.new(@connection_args)
23
+ end
24
+
25
+ def receive(n=1)
26
+ count = 0
27
+ m = nil
28
+ until(m)
29
+ m = nil
30
+ @receive_timeout.reset
31
+ m = @fog.receive_message(@queue, 'MaxNumberOfMessages' => n).body['Message'].first
32
+ @receive_timeout.reset
33
+ unless(m)
34
+ if(count == 0)
35
+ debug "Source<#{name}> no message received. Sleeping for #{pause_time} seconds."
36
+ elsif(count % OUTPUT_REPEAT_EVERY == 0)
37
+ debug "Source<#{name}> last message repeated #{count} times"
38
+ end
39
+ sleep(pause_time)
40
+ end
41
+ count += 1
42
+ end
43
+ pre_process(m)
44
+ end
45
+
46
+ def send(message)
47
+ @fog.send_message(@queue, message)
48
+ end
49
+
50
+ def confirm(message)
51
+ @fog.delete_message(@queue, message['ReceiptHandle'])
52
+ end
53
+
54
+ private
55
+
56
+ def fog
57
+ unless(@fog)
58
+ connect
59
+ end
60
+ @fog
61
+ end
62
+
63
+ def pre_process(m)
64
+ if(m['Body'])
65
+ begin
66
+ m['Body'] = JSON.load(m['Body'])
67
+ rescue JSON::ParserError
68
+ # well, we did our best
69
+ end
70
+ end
71
+ m
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ module Carnivore
2
+ class Source
3
+ class Test < Source
4
+
5
+ RAND_MAX = 99999
6
+ RAND_DIV = 3
7
+ RAND_SLEEP = 10
8
+
9
+ def setup(args={})
10
+ end
11
+
12
+ def connect(*args)
13
+ puts 'Test connect called'
14
+ end
15
+
16
+ def receive(*args)
17
+ if(rand(RAND_MAX) % RAND_DIV == 0)
18
+ sleep_for = rand(RAND_SLEEP)
19
+ debug "Test source sleep for: #{sleep_for}"
20
+ sleep sleep_for
21
+ end
22
+ 20.times.map{('a'..'z').to_a.shuffle.first}.join
23
+ end
24
+
25
+ def transmit(message)
26
+ puts "Transmit requested: #{message}"
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,151 @@
1
+ require 'carnivore/callback'
2
+
3
+ module Carnivore
4
+ class Source < Celluloid::SupervisionGroup
5
+
6
+ class SourceContainer
7
+
8
+ attr_reader :klass
9
+ attr_reader :source_hash
10
+
11
+ def initialize(class_name, args={})
12
+ @klass = class_name
13
+ @source_hash = args || {}
14
+ @source_hash[:callbacks] = {}
15
+ end
16
+
17
+ def add_callback(name, klass=nil, &block)
18
+ @source_hash[:callbacks][name] = klass || block
19
+ end
20
+ end
21
+
22
+ class << self
23
+
24
+ def build(args={})
25
+ [:args, :type].each do |key|
26
+ unless(args.has_key?(key))
27
+ raise ArgumentError.new "Missing required parameter `:#{key}`"
28
+ end
29
+ end
30
+ require "carnivore/source/#{args[:type]}"
31
+ klass = args[:type].to_s.split('_').map(&:capitalize).join
32
+ klass = Source.const_get(klass)
33
+ args[:args][:name] ||= Celluloid.uuid
34
+ inst = SourceContainer.new(klass, args[:args])
35
+ register(inst)
36
+ inst
37
+ end
38
+
39
+ def register(inst)
40
+ @sources ||= []
41
+ @sources << inst
42
+ true
43
+ end
44
+
45
+ def sources
46
+ @sources || []
47
+ end
48
+ end
49
+
50
+ include Celluloid
51
+ include Celluloid::Logger
52
+
53
+ attr_reader :name
54
+ attr_reader :callbacks
55
+ attr_reader :auto_confirm
56
+ attr_reader :callback_supervisor
57
+
58
+ def initialize(args={})
59
+ @callbacks = []
60
+ @callback_names = {}
61
+ @callback_supervisor = Celluloid::SupervisionGroup.run!
62
+ @name = args[:name] || Celluloid.uuid
63
+ @auto_confirm = !!args[:auto_confirm]
64
+ if(args[:callbacks])
65
+ args[:callbacks].each do |name, block|
66
+ add_callback(name, block)
67
+ end
68
+ end
69
+ setup(args)
70
+ connect
71
+ async.process
72
+ rescue => e
73
+ debug "Failed to initialize: #{self} - #{e.class}: #{e}\n#{e.backtrace.join("\n")}"
74
+ raise
75
+ end
76
+
77
+ def auto_confirm?
78
+ @auto_confirm
79
+ end
80
+
81
+ def inspect
82
+ "<#{self.class.name}:#{object_id} @name=#{name} @callbacks=#{Hash[*callbacks.map{|k,v| [k,v.object_id]}.flatten]}>"
83
+ end
84
+
85
+ def setup(args={})
86
+ debug "<#{self.class}> No custom setup declared"
87
+ end
88
+
89
+ def connect(args={})
90
+ debug "<#{self.class}> No custom connect declared"
91
+ end
92
+
93
+ def receive(n=1)
94
+ raise NoMethodError.new('Abstract method not valid for runtime')
95
+ end
96
+
97
+ def transmit(message)
98
+ raise NoMethodError.new('Abstract method not valid for runtime')
99
+ end
100
+
101
+ def terminate
102
+ if(@callback_supervisor)
103
+ @callback_supervisor.actors.map(&:terminate)
104
+ end
105
+ end
106
+
107
+ def add_callback(name, block_or_class)
108
+ if(block_or_class.is_a?(Class))
109
+ debug "Adding callback class (#{block_or_class}) under supervision. Name: #{callback_name(name)}"
110
+ size = block_or_class.workers || 1
111
+ @callback_supervisor.pool block_or_class, as: callback_name(name), size: size, args: [name]
112
+ else
113
+ debug "Adding custom callback class from block (#{block_or_class}) under supervision. Name: #{callback_name(name)}"
114
+ @callback_supervisor.supervise_as callback_name(name), Callback, name, block_or_class
115
+ end
116
+ @callbacks.push(name).uniq!
117
+ self
118
+ end
119
+
120
+ def remove_callback(name)
121
+ unless(@callbacks.include?(callback_name(name)))
122
+ raise NameError.new("Failed to locate callback named: #{name}")
123
+ end
124
+ actors[callback_name(name)].terminate
125
+ @callbacks.delete(name)
126
+ self
127
+ end
128
+
129
+ def callback_name(name)
130
+ unless(@callback_names[name])
131
+ @callback_names[name] = [@name, self.object_id, name].join(':').to_sym
132
+ end
133
+ @callback_names[name]
134
+ end
135
+
136
+ def format(msg)
137
+ {:message => msg, :source => self}
138
+ end
139
+
140
+ def process
141
+ loop do
142
+ msg = format(receive)
143
+ @callbacks.each do |name|
144
+ debug "Dispatching message<#{msg[:message].object_id}> to callback<#{name} (#{callback_name(name)})>"
145
+ Celluloid::Actor[callback_name(name)].async.call(msg)
146
+ end
147
+ end
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,9 @@
1
+ module Carnivore
2
+ class Supervisor < Celluloid::SupervisionGroup
3
+
4
+ Source.sources.each do |source|
5
+ supervise source.klass, as: source.source_hash[:name], args: [source.source_hash]
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Carnivore
2
+ module Utils
3
+
4
+ module Params
5
+ def symbolize_hash(hash)
6
+ Hash[*(
7
+ hash.map do |k,v|
8
+ [
9
+ k.gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym,
10
+ v.is_a?(Hash) ? symbolize_hash(v) : v
11
+ ]
12
+ end.flatten
13
+ )]
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Carnivore
2
+ class Version < Gem::Version
3
+ end
4
+ VERSION = Version.new('0.1.0')
5
+ end
data/lib/carnivore.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'carnivore/version'
2
+ require 'carnivore/runner'
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: carnivore
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Roberts
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: celluloid
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: reel
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: mixlib-config
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bunny
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Message processing helper
95
+ email: chrisroberts.code@gmail.com
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files: []
99
+ files:
100
+ - lib/carnivore/callback.rb
101
+ - lib/carnivore/supervisor.rb
102
+ - lib/carnivore/source/http.rb
103
+ - lib/carnivore/source/sqs.rb
104
+ - lib/carnivore/source/test.rb
105
+ - lib/carnivore/source/rabbitmq.rb
106
+ - lib/carnivore/source.rb
107
+ - lib/carnivore/version.rb
108
+ - lib/carnivore/runner.rb
109
+ - lib/carnivore/config.rb
110
+ - lib/carnivore/utils.rb
111
+ - lib/carnivore/container.rb
112
+ - lib/carnivore.rb
113
+ - examples/test_http.rb
114
+ - examples/test_class.rb
115
+ - examples/test_block.rb
116
+ - Gemfile
117
+ - README.md
118
+ - CHANGELOG.md
119
+ - Gemfile.lock
120
+ - carnivore.gemspec
121
+ homepage: https://github.com/heavywater/carnivore
122
+ licenses: []
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.24
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Message processing helper
145
+ test_files: []
146
+ has_rdoc: