stooge 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3@stooge --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in stooge.gemspec
4
+ gemspec
data/HISTORY.md ADDED
@@ -0,0 +1,10 @@
1
+
2
+ ### 0.1.0 (2012-03-09)
3
+
4
+ * Implemented same basic feature set as Minion (i.e. work queues)
5
+ * Added ability to easily test job handlers
6
+ * Made both #enqueue and #job asynchronous
7
+ * Changed from using Bunny to the using official AMQP gem
8
+ * Improved error handling logic and logging
9
+ * Handling of message content types (JSON is still default)
10
+ * Added reconnect to broker on connection failure support
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Niklas Holmgren, Sutajio
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ Stooge
2
+ ======
3
+
4
+ Stooge is a fully EventMachine enabled DSL for interacting with an AMQP broker. Heavily inspired by Minion (but fresher and completely asynchronous).
5
+
6
+ Setup
7
+ -----
8
+
9
+ Assuming you already have an AMQP broker installed:
10
+
11
+ $ gem install stooge
12
+
13
+ You can configure the address to the broker using the ```AMQP_URL``` environment variable, or programmatically like this:
14
+
15
+ ```ruby
16
+ Stooge.amqp_url = 'amqp://johndoe:abc123@localhost/my_vhost'
17
+ ```
18
+
19
+ The default if not specified is ```amqp://guest:guest@localhost/```.
20
+
21
+ Example usage
22
+ -------------
23
+
24
+ To process a job add the following to a file called ```worker.rb``` and run it with ```ruby worker.rb```. Stooge will start an EventMachine loop that waits for AMQP messages and processes matching jobs until you send ```SIG_INT``` or ```SIG_TERM``` to it.
25
+
26
+ ```ruby
27
+ require 'stooge'
28
+
29
+ Stooge.job('example.puts') do |args|
30
+ puts args['message']
31
+ end
32
+ ```
33
+
34
+ To push a job onto a queue you call ```Stooge.enqueue``` with the name of the work queue and the data you want to process. The data needs to be JSON serializable and can be for example a hash.
35
+
36
+ ```ruby
37
+ require 'stooge'
38
+
39
+ EM.run do
40
+ Stooge.enqueue('example.puts', :message => 'Hello, world!')
41
+ end
42
+ ```
43
+
44
+ Error handling
45
+ --------------
46
+
47
+ When an error is thrown in a job handler, the job is requeued to be done later and the Stooge process exits. If you define an error handler, however, the error handler is run and the job is removed from the queue.
48
+
49
+ ```ruby
50
+ Stooge.error do |e|
51
+ puts "got an error! #{e}"
52
+ end
53
+ ```
54
+
55
+ Logging
56
+ -------
57
+
58
+ Stooge logs to stdout via ```puts```. You can specify a custom logger like this:
59
+
60
+ ```ruby
61
+ Stooge.logger do |msg|
62
+ puts msg
63
+ end
64
+ ```
65
+
66
+ Testing
67
+ -------
68
+
69
+ To test the business logic in your job handler you can use the ```Stooge.run_handler``` helper method:
70
+
71
+ ```ruby
72
+ Stooge.run_handler('example.work', { :foo => 'bar' }).should == 42
73
+ ```
74
+
75
+ The return value is the return value from executing the job handler block.
76
+
77
+ Author
78
+ ------
79
+
80
+ Stooge was created by Niklas Holmgren (niklas@sutajio.se) with help from Martin Bruse (@zond) and released under the MIT license. Stooge is very much inspired by Minion (created by Orion Henry), Resque (created by Chris Wanstrath) and Stalker (created by Adam Wiggins).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ $LOAD_PATH.unshift 'lib'
5
+
6
+ task :default => [:spec]
7
+
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.rspec_opts = '--color'
10
+ t.pattern = 'spec/**/*_spec.rb'
11
+ end
data/lib/stooge.rb ADDED
@@ -0,0 +1,248 @@
1
+ require 'uri'
2
+ require 'multi_json'
3
+ require 'amqp'
4
+ require 'em-synchrony'
5
+ require 'stooge/version'
6
+ require 'stooge/handler'
7
+ require 'stooge/work_queue'
8
+ require 'stooge/worker'
9
+
10
+ module Stooge
11
+ extend self
12
+ extend Stooge::WorkQueue
13
+
14
+ @@connection = nil
15
+ @@channel = nil
16
+ @@handlers = []
17
+ @@error_handler = Proc.new do |exception, handler, payload, metadata|
18
+ Stooge.log "#{handler.queue_name} failed: #{exception.inspect}"
19
+ raise exception
20
+ end
21
+
22
+ #
23
+ # Will return a URL to the AMQP broker to use. Will get this from the
24
+ # <code>ENV</code> variable <code>AMQP_URL</code> if present, or use the
25
+ # default which is "amqp://guest:guest@localhost/" (the same as the default
26
+ # for a local RabbitMQ install).
27
+ #
28
+ # @return [String] a URL to an AMQP broker.
29
+ #
30
+ def amqp_url
31
+ @@amqp_url ||= ENV["AMQP_URL"] || "amqp://guest:guest@localhost/"
32
+ end
33
+
34
+ #
35
+ # Set the URL to the AMQP broker to use. The format of the URL should be
36
+ # "amqp://username:password@hostname/vhost" (vhost and username/password is
37
+ # optional).
38
+ #
39
+ def amqp_url=(url)
40
+ @@amqp_url = url
41
+ end
42
+
43
+ #
44
+ # Log message using the Stooge logger.
45
+ #
46
+ # @param [String] msg the message to log.
47
+ #
48
+ def log(msg)
49
+ @@logger ||= proc { |m| puts "#{Time.now} :stooge: #{m}" }
50
+ @@logger.call(msg)
51
+ end
52
+
53
+ #
54
+ # Configure a custom logger for Stooge. The block gets yielded with the
55
+ # log message. You can do whatever you want with it. The default behaviour
56
+ # is to simply output to stdout.
57
+ #
58
+ # @param [Proc] block a {::Proc} to yield when a message needs to be logged.
59
+ # @yieldparam [String] msg the message to log.
60
+ #
61
+ def logger(&blk)
62
+ @@logger = blk
63
+ end
64
+
65
+ #
66
+ # Configure a global error handler for Stooge jobs. The block gets yielded
67
+ # when a job handler raises an exception. The default error handler simply
68
+ # logs the error and re-raises the exception. You can use this to for
69
+ # example re-queue a failed job or send email notifications when a job
70
+ # fails.
71
+ #
72
+ # If you don't raise an exception in the error handler the job will be
73
+ # acked with the broker and the broker will consider the job done and remove
74
+ # it from the queue. If you for some reason want to force the job to be
75
+ # acked even when you raise an error you can manually ack it before you
76
+ # raise the error, like this:
77
+ #
78
+ # Stooge.error do |exception, handler, payload, metadata|
79
+ # metadata.ack
80
+ # raise exception
81
+ # end
82
+ #
83
+ # @param [Proc] block a {::Proc} to yield when an error happens.
84
+ # @yieldparam [Exception] exception the exception object raised.
85
+ # @yieldparam [Stooge::Handler] handler the handler that failed.
86
+ # @yieldparam [Object] payload the message payload that was processed when
87
+ # the handler failed.
88
+ # @yieldparam [Hash] metadata the message metadata (headers, etc.)
89
+ #
90
+ def error(&blk)
91
+ @@error_handler = blk
92
+ end
93
+
94
+ #
95
+ # The global error handler.
96
+ #
97
+ # @return [Proc] the error handler block.
98
+ #
99
+ def error_handler
100
+ @@error_handler
101
+ end
102
+
103
+ #
104
+ # Start listening to for new jobs on all queues using the specified channel.
105
+ #
106
+ # @param [AMQP::Channel] channel an open AMQP channel
107
+ #
108
+ def start_handlers(channel)
109
+ @@handlers.each { |h| h.start(channel) }
110
+ end
111
+
112
+ #
113
+ # Add a new job handler. Used by {Stooge.job}.
114
+ #
115
+ # @param [Stooge::Handler] handler a handler object
116
+ #
117
+ def add_handler(handler)
118
+ @@handlers << handler
119
+ end
120
+
121
+ #
122
+ # Are there any job handlers defined? Used by {Stooge::Worker} to check if
123
+ # it should start a worker process.
124
+ #
125
+ # @return [Boolean] true or false
126
+ #
127
+ def handlers?
128
+ @@handlers.empty? == false
129
+ end
130
+
131
+ #
132
+ # Execute a handler block without going through AMQP at all. This is a
133
+ # helper method for use in tests. It allows you to test the business logic
134
+ # in a handler block without having to mess with the details of how Stooge
135
+ # works internally.
136
+ #
137
+ # Examples
138
+ #
139
+ # Stooge.run_handler('example.work', :foo => 'bar').should == 42
140
+ #
141
+ # @param [String] queue_name the name of the handler to run
142
+ # @param [Object] data message data to send to the handler as arguments
143
+ # @param [Hash] headers optional headers to send as second argument to the
144
+ # handler block
145
+ #
146
+ # @return the return value of the handler block
147
+ #
148
+ def run_handler(queue_name, data, headers = {})
149
+ @@handlers.each do |handler|
150
+ if handler.queue_name == queue_name
151
+ return handler.block.call(data, headers)
152
+ end
153
+ end
154
+ end
155
+
156
+ #
157
+ # Will yield an open and ready connection.
158
+ #
159
+ # @param [Proc] block a {::Proc} to yield an open and ready connection to.
160
+ #
161
+ def with_connection(&block)
162
+ if @@connection.nil? || @@connection.status == :closed
163
+ @@connection = AMQP.connect(amqp_config)
164
+ @@connection.on_tcp_connection_loss do
165
+ Stooge.log "[network failure] trying to reconnect..."
166
+ @@connection.reconnect
167
+ end
168
+ @@connection.on_recovery do
169
+ Stooge.log "connection with broker recovered"
170
+ end
171
+ @@connection.on_error do |ch, connection_close|
172
+ raise connection_close.reply_text
173
+ end
174
+ end
175
+ @@connection.on_open do
176
+ yield @@connection
177
+ end
178
+ end
179
+
180
+ #
181
+ # Will yield an open and ready channel.
182
+ #
183
+ # @param [Proc] block a {::Proc} to yield an open and ready channel to.
184
+ #
185
+ def with_channel(&block)
186
+ with_connection do |connection|
187
+ if @@channel.nil? || @@channel.status == :closed
188
+ @@channel = AMQP::Channel.new(connection, AMQP::Channel.next_channel_id, channel_options)
189
+ @@channel.on_error do |ch, channel_close|
190
+ Stooge.log "channel-level exception: code = #{channel_close.reply_code}, message = #{channel_close.reply_text}"
191
+ end
192
+ end
193
+ @@channel.once_open do
194
+ yield @@channel
195
+ end
196
+ end
197
+ end
198
+
199
+ # Start a {::Stooge} worker that consumes messages from the AMQP broker.
200
+ def start!
201
+ EM.synchrony do
202
+ with_channel do |channel|
203
+ start_handlers(channel)
204
+ end
205
+ end
206
+ end
207
+
208
+ # Will stop and deactivate {::Stooge}.
209
+ def stop!
210
+ EM.next_tick do
211
+ with_channel do |channel|
212
+ channel.close
213
+ end
214
+ @@channel = nil
215
+ with_connection do |connection|
216
+ connection.close
217
+ end
218
+ @@connection = nil
219
+ EM.stop
220
+ end
221
+ end
222
+
223
+ private
224
+
225
+ # Will return the default options to use when creating channels.
226
+ def channel_options
227
+ @channel_options ||= {
228
+ :prefetch => 1,
229
+ :auto_recovery => true
230
+ }
231
+ end
232
+
233
+ # Will return the default options when connecting to the AMQP broker.
234
+ # Uses the URL from {#amqp_url} to construct these options.
235
+ def amqp_config
236
+ uri = URI.parse(amqp_url)
237
+ {
238
+ :vhost => uri.path,
239
+ :host => uri.host,
240
+ :user => uri.user,
241
+ :port => (uri.port || 5672),
242
+ :pass => uri.password
243
+ }
244
+ rescue Object => e
245
+ raise "invalid AMQP_URL: #{uri.inspect} (#{e})"
246
+ end
247
+
248
+ end
@@ -0,0 +1,52 @@
1
+ module Stooge
2
+ class Handler
3
+
4
+ attr_accessor :queue_name, :queue_options, :block
5
+
6
+ #
7
+ # Create a new handler object.
8
+ #
9
+ # @param [String] queue the name of the queue that this handler will pick
10
+ # jobs from.
11
+ # @param [Hash] options handler options.
12
+ # @option options [Hash] :queue_options Options to use when creating the
13
+ # queue.
14
+ #
15
+ def initialize(queue, options = {})
16
+ @queue_name = queue
17
+ @queue_options = options[:queue_options] || {}
18
+ @options = options
19
+ @block = lambda {}
20
+ end
21
+
22
+ #
23
+ # Start subscribing to the queue that this handler corresponds to. When
24
+ # a message arive; parse it and call the handler block with the data.
25
+ #
26
+ # @param [AMQP::Channel] channel an open AMQP channel
27
+ #
28
+ def start(channel)
29
+ Stooge.log "starting handler for #{@queue_name}"
30
+ channel.queue(@queue_name, @queue_options) do |queue|
31
+ queue.subscribe(:ack => true) do |metadata, payload|
32
+ Stooge.log "recv: #{@queue_name}"
33
+ begin
34
+ case metadata.content_type
35
+ when 'application/json'
36
+ args = MultiJson.decode(payload)
37
+ else
38
+ args = payload
39
+ end
40
+ @block.call(args, metadata.headers)
41
+ rescue Object => e
42
+ if Stooge.error_handler
43
+ Stooge.error_handler.call(e,self,payload,metadata)
44
+ end
45
+ end
46
+ metadata.ack
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Stooge
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,74 @@
1
+ module Stooge
2
+ module WorkQueue
3
+
4
+ #
5
+ # Push a job onto a named queue.
6
+ #
7
+ # Example:
8
+ #
9
+ # Stooge.enqueue('example.work')
10
+ #
11
+ # @param [String] queue_name The name of a work queue or an array of
12
+ # names that the job will be sent through in sequential order (a
13
+ # workflow).
14
+ # @param [Object] data The data to send as input to the job. Needs to be
15
+ # JSON serializable.
16
+ # @param [Hash] headers AMQP headers to include in the job that gets
17
+ # pushed. Needs to be a hash with key/value pairs.
18
+ #
19
+ def enqueue(queue_name, data, headers = {})
20
+ EM::Synchrony.sync(aenqueue(queue_name, data, headers))
21
+ end
22
+
23
+ #
24
+ # Asynchrounous version of enqueue. Yields when the job has been put onto
25
+ # the queue, if a block is given.
26
+ #
27
+ # @param [String] queue_name The name of a work queue or an array of
28
+ # names that the job will be sent through in sequential order (a
29
+ # workflow).
30
+ # @param [Object] data The data to send as input to the job. Needs to be
31
+ # JSON serializable.
32
+ # @param [Hash] headers AMQP headers to include in the job that gets
33
+ # pushed. Needs to be a hash with key/value pairs.
34
+ #
35
+ # @return [EM::DefaultDeferrable] Returns an EM::DefaultDeferrable object.
36
+ #
37
+ def aenqueue(queue_name, data, headers = {})
38
+ deferrable = EM::DefaultDeferrable.new
39
+ with_channel do |channel|
40
+ options = {
41
+ :routing_key => queue_name,
42
+ :mandatory => true,
43
+ :content_type => 'application/json',
44
+ :headers => headers
45
+ }
46
+ channel.default_exchange.publish(MultiJson.encode(data), options) do
47
+ Stooge.log("enqueue: #{queue_name}(#{data})")
48
+ yield if block_given?
49
+ deferrable.set_deferred_status :succeeded
50
+ end
51
+ end
52
+ deferrable
53
+ end
54
+
55
+ #
56
+ # Creates a job handler for a named queue.
57
+ #
58
+ # Example:
59
+ #
60
+ # Stooge.job('example.work') do |args,headers|
61
+ # # Do the work here...
62
+ # end
63
+ #
64
+ # @param [String] queue The name of the work queue.
65
+ # @param [Proc] blk a {::Proc} that processes the jobs.
66
+ #
67
+ def job(queue, &blk)
68
+ handler = Stooge::Handler.new(queue, :queue_options => { :durable => true })
69
+ handler.block = blk
70
+ add_handler(handler)
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,61 @@
1
+ module Stooge
2
+ class Worker
3
+
4
+ #
5
+ # Start the Stooge worker that processes jobs.
6
+ #
7
+ def self.run!
8
+ Stooge.log "Starting stooge"
9
+
10
+ Signal.trap('INT') { Stooge.stop! }
11
+ Signal.trap('TERM'){ Stooge.stop! }
12
+
13
+ Stooge.start!
14
+ end
15
+
16
+ #
17
+ # Should the Stooge worker be started?
18
+ #
19
+ # @return [Boolean] true or false
20
+ #
21
+ def self.run?
22
+ Stooge.handlers? &&
23
+ File.expand_path($0) == File.expand_path(app_file)
24
+ end
25
+
26
+ private
27
+
28
+ CALLERS_TO_IGNORE = [
29
+ /\/stooge(\/worker)?\.rb$/,
30
+ /rubygems\/custom_require\.rb$/,
31
+ /bundler(\/runtime)?\.rb/,
32
+ /<internal:/
33
+ ]
34
+
35
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
36
+
37
+ def self.caller_files
38
+ cleaned_caller(1).flatten
39
+ end
40
+
41
+ def self.caller_locations
42
+ cleaned_caller 2
43
+ end
44
+
45
+ def self.cleaned_caller(keep = 3)
46
+ caller(1).
47
+ map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
48
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
49
+ end
50
+
51
+ def self.app_file
52
+ caller_files.first || $0
53
+ end
54
+
55
+ end
56
+
57
+ #
58
+ # Starte the worker at exit, if appropriate.
59
+ #
60
+ at_exit { Worker.run! if $!.nil? && Worker.run? }
61
+ end
@@ -0,0 +1,9 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $LOAD_PATH.unshift dir + '/../lib'
3
+
4
+ require 'stooge'
5
+ require 'rspec'
6
+
7
+ describe Stooge do
8
+
9
+ end
data/stooge.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "stooge/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "stooge"
7
+ s.version = Stooge::VERSION
8
+ s.authors = ["Niklas Holmgren"]
9
+ s.email = ["niklas@sutajio.se"]
10
+ s.homepage = "https://github.com/sutajio/stooge"
11
+ s.summary = %q{Super advanced job queue over AMQP}
12
+ s.description = %q{Super advanced job queue over AMQP}
13
+
14
+ s.rubyforge_project = "stooge"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'amqp'
22
+ s.add_dependency 'em-synchrony'
23
+ s.add_dependency 'multi_json'
24
+ s.add_development_dependency 'rspec'
25
+
26
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stooge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Niklas Holmgren
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: amqp
16
+ requirement: &70330131821100 !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: *70330131821100
25
+ - !ruby/object:Gem::Dependency
26
+ name: em-synchrony
27
+ requirement: &70330131820660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70330131820660
36
+ - !ruby/object:Gem::Dependency
37
+ name: multi_json
38
+ requirement: &70330131820200 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70330131820200
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &70330131819740 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70330131819740
58
+ description: Super advanced job queue over AMQP
59
+ email:
60
+ - niklas@sutajio.se
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .rvmrc
67
+ - Gemfile
68
+ - HISTORY.md
69
+ - LICENSE
70
+ - README.md
71
+ - Rakefile
72
+ - lib/stooge.rb
73
+ - lib/stooge/handler.rb
74
+ - lib/stooge/version.rb
75
+ - lib/stooge/work_queue.rb
76
+ - lib/stooge/worker.rb
77
+ - spec/stooge_spec.rb
78
+ - stooge.gemspec
79
+ homepage: https://github.com/sutajio/stooge
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project: stooge
99
+ rubygems_version: 1.8.17
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Super advanced job queue over AMQP
103
+ test_files:
104
+ - spec/stooge_spec.rb