buster 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +8 -0
- data/README.md +36 -0
- data/Rakefile +4 -0
- data/buster.gemspec +23 -0
- data/lib/buster.rb +40 -0
- data/lib/buster/command_handler.rb +11 -0
- data/lib/buster/context.rb +25 -0
- data/lib/buster/host.rb +36 -0
- data/lib/buster/poller.rb +40 -0
- data/lib/buster/router.rb +34 -0
- data/lib/buster/sender.rb +20 -0
- data/lib/buster/version.rb +3 -0
- data/lib/buster/worker.rb +49 -0
- data/spec/integration_spec.rb +82 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/test_endpoints/mars.rb +14 -0
- data/spec/test_endpoints/pluto.rb +14 -0
- data/spec/test_endpoints/station.rb +14 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
Buster
|
2
|
+
---
|
3
|
+
|
4
|
+
A Ruby Service Bus
|
5
|
+
|
6
|
+
License
|
7
|
+
---
|
8
|
+
|
9
|
+
Copyright (c) 2012, Jason Staten, PeerIntel
|
10
|
+
|
11
|
+
All rights reserved.
|
12
|
+
|
13
|
+
Redistribution and use in source and binary forms, with or without
|
14
|
+
modification, are permitted provided that the following conditions are met:
|
15
|
+
|
16
|
+
- Redistributions of source code must retain the above copyright notice, this
|
17
|
+
list of conditions and the following disclaimer.
|
18
|
+
|
19
|
+
- Redistributions in binary form must reproduce the above copyright notice,
|
20
|
+
this list of conditions and the following disclaimer in the documentation
|
21
|
+
and/or other materials provided with the distribution.
|
22
|
+
|
23
|
+
- Neither the name of PeerIntel nor the names of its contributors may be used
|
24
|
+
to endorse or promote products derived from this software without specific
|
25
|
+
prior written permission.
|
26
|
+
|
27
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
28
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
29
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
30
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
31
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
32
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
33
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
34
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
35
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
36
|
+
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
data/buster.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "buster/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "buster"
|
7
|
+
s.version = Buster::VERSION
|
8
|
+
s.authors = ["Jason Staten"]
|
9
|
+
s.email = ["jstaten07@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Service Bus}
|
12
|
+
s.description = %q{Service Bus}
|
13
|
+
|
14
|
+
s.rubyforge_project = "buster"
|
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_runtime_dependency "ffi-rzmq"
|
22
|
+
s.add_runtime_dependency "msgpack"
|
23
|
+
end
|
data/lib/buster.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
%w{host sender worker poller command_handler router context version}.each { |r| require "buster/#{r}" }
|
2
|
+
|
3
|
+
require 'ffi-rzmq'
|
4
|
+
require 'msgpack'
|
5
|
+
|
6
|
+
module Buster
|
7
|
+
class << self
|
8
|
+
attr_accessor :local_endpoint
|
9
|
+
|
10
|
+
def routes
|
11
|
+
@routes ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
raise "Bus is already started" if @host
|
16
|
+
@host = Buster::Host.new(routes, context, self.local_endpoint)
|
17
|
+
@host.start
|
18
|
+
end
|
19
|
+
|
20
|
+
def stop
|
21
|
+
raise "Bus is not running" unless @host
|
22
|
+
@host.stop
|
23
|
+
@host = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def fire(name, props = {})
|
27
|
+
sender.fire(name, props)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def sender
|
33
|
+
@sender ||= Buster::Sender.new(context)
|
34
|
+
end
|
35
|
+
|
36
|
+
def context
|
37
|
+
@context ||= Buster::Context.new(ZMQ::Context.create)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Buster
|
2
|
+
class Context
|
3
|
+
|
4
|
+
attr_accessor :context
|
5
|
+
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
@sockets = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def socket(type)
|
12
|
+
socket = @context.socket(type)
|
13
|
+
@sockets << socket
|
14
|
+
socket
|
15
|
+
end
|
16
|
+
|
17
|
+
def terminate
|
18
|
+
@sockets.reverse.each do |s|
|
19
|
+
s.setsockopt(ZMQ::LINGER, 0)
|
20
|
+
s.close
|
21
|
+
end
|
22
|
+
@context.terminate
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/buster/host.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Buster
|
2
|
+
class Host
|
3
|
+
|
4
|
+
def initialize(routes, context, local_endpoint = nil)
|
5
|
+
@local_endpoint = local_endpoint ||"inproc://local_endpoint"
|
6
|
+
@context = context
|
7
|
+
@router = Buster::Router.new(@context, routes)
|
8
|
+
@poller = Buster::Poller.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
frontend = @context.socket(ZMQ::ROUTER)
|
13
|
+
frontend.bind(@local_endpoint)
|
14
|
+
|
15
|
+
backend = @context.socket(ZMQ::DEALER)
|
16
|
+
backend.bind("inproc://workers")
|
17
|
+
|
18
|
+
@router.connect_routes backend, @poller
|
19
|
+
|
20
|
+
Buster::Worker.start_worker @context
|
21
|
+
|
22
|
+
@poller.pipe frontend, backend
|
23
|
+
@poller.pipe backend, frontend
|
24
|
+
|
25
|
+
poll_thread = Thread.new { @poller.start }
|
26
|
+
poll_thread.join
|
27
|
+
|
28
|
+
@context.terminate
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
@poller.stop
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Buster
|
2
|
+
class Poller
|
3
|
+
def initialize
|
4
|
+
@poller = ZMQ::Poller.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def start
|
8
|
+
@run = true
|
9
|
+
while @run
|
10
|
+
@poller.poll(500)
|
11
|
+
@poller.readables.each do |socket|
|
12
|
+
action = @readable_actions[socket]
|
13
|
+
action.call(socket) if action
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
@run = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def register(socket, &block)
|
23
|
+
@poller.register(socket, ZMQ::POLLIN)
|
24
|
+
readable_actions[socket] = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def pipe(incoming, outgoing)
|
28
|
+
register(incoming) do |s|
|
29
|
+
s.recv_strings(msgs = [])
|
30
|
+
outgoing.send_strings(msgs, ZMQ::NOBLOCK)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def readable_actions
|
37
|
+
@readable_actions ||= {}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Buster
|
2
|
+
class Router
|
3
|
+
|
4
|
+
def initialize(context, routes = {})
|
5
|
+
@context = context
|
6
|
+
@routes = routes
|
7
|
+
end
|
8
|
+
|
9
|
+
def connect_routes(reply_socket, poller)
|
10
|
+
local = @context.socket(ZMQ::DEALER)
|
11
|
+
local.bind("inproc://routes")
|
12
|
+
|
13
|
+
sockets = @routes.map do |pattern, uri|
|
14
|
+
remote = @context.socket(ZMQ::DEALER)
|
15
|
+
result = remote.connect(uri)
|
16
|
+
|
17
|
+
poller.pipe remote, reply_socket
|
18
|
+
[pattern, remote]
|
19
|
+
end
|
20
|
+
|
21
|
+
poller.register(local) do |s|
|
22
|
+
s.recv_strings(msgs = [])
|
23
|
+
message_name = msgs[0]
|
24
|
+
#TODO: More robust routing
|
25
|
+
remote = sockets.detect([nil,nil]){|x| x[0].match message_name}[1]
|
26
|
+
if remote.nil?
|
27
|
+
puts "No remote matching '#{message_name}'"
|
28
|
+
return
|
29
|
+
end
|
30
|
+
remote.send_strings(msgs)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Buster
|
2
|
+
class Sender
|
3
|
+
def initialize(context)
|
4
|
+
@context = context
|
5
|
+
end
|
6
|
+
|
7
|
+
def fire(name, props = {})
|
8
|
+
bin = MessagePack.pack(props)
|
9
|
+
sender.send_strings([name.to_s, bin])
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def sender
|
15
|
+
s = @context.socket(ZMQ::DEALER)
|
16
|
+
s.connect("inproc://routes")
|
17
|
+
s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Buster
|
2
|
+
class Worker
|
3
|
+
def initialize(context)
|
4
|
+
@context = context
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
worker = @context.socket(ZMQ::DEALER)
|
9
|
+
worker.connect("inproc://workers")
|
10
|
+
while true
|
11
|
+
worker.recv_strings(msgs = [])
|
12
|
+
return if msgs.length < 2
|
13
|
+
|
14
|
+
body = MessagePack.unpack(msgs.pop)
|
15
|
+
message_name = msgs.pop
|
16
|
+
reply_id = msgs.pop
|
17
|
+
|
18
|
+
handler = find_handler message_name
|
19
|
+
if handler.nil?
|
20
|
+
puts "No handler found for #{message_name}"
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
handler.reply_action = lambda do |name,props|
|
25
|
+
worker.send_strings([reply_id, name.to_s, MessagePack.pack(props)])
|
26
|
+
end
|
27
|
+
|
28
|
+
handler.execute body
|
29
|
+
end
|
30
|
+
worker.close
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def self.start_worker(context)
|
35
|
+
worker = Worker.new(context)
|
36
|
+
t = Thread.new { worker.run }
|
37
|
+
t.run
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def find_handler(name)
|
43
|
+
camelized = name.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
|
44
|
+
const_name = "#{camelized}Handler"
|
45
|
+
Object.const_get(const_name).new if Object.const_defined?(const_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'integration' do
|
4
|
+
before :all do
|
5
|
+
start_endpoint :mars
|
6
|
+
start_endpoint :pluto
|
7
|
+
Buster.local_endpoint = 'ipc://earth.ipc'
|
8
|
+
Buster.routes[/mars/] = 'ipc://mars.ipc'
|
9
|
+
Buster.routes[/pluto/] = 'ipc://pluto.ipc'
|
10
|
+
Buster.routes[/station/] = 'ipc://station.ipc'
|
11
|
+
Thread.new { Buster.start }
|
12
|
+
sleep 1 #bus startup delay
|
13
|
+
end
|
14
|
+
|
15
|
+
after :all do
|
16
|
+
Buster.stop
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:mutex) { Mutex.new }
|
20
|
+
let(:baton) { ConditionVariable.new }
|
21
|
+
|
22
|
+
describe 'when sending a hello_mars' do
|
23
|
+
before do
|
24
|
+
Handler 'HelloEarth' do |props|
|
25
|
+
mutex.synchronize do
|
26
|
+
@response = props['name']
|
27
|
+
baton.signal
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should respond in the mars uppercase style' do
|
33
|
+
name = 'Major Tom'
|
34
|
+
Buster.fire :hello_mars, :name => name
|
35
|
+
mutex.synchronize do
|
36
|
+
baton.wait(mutex, 1)
|
37
|
+
@response.should == name.upcase
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'when sending a hello_pluto' do
|
43
|
+
before do
|
44
|
+
Handler 'HelloEarth' do |props|
|
45
|
+
mutex.synchronize do
|
46
|
+
@response = props['name']
|
47
|
+
baton.signal
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should respond in the pluto reversed style' do
|
53
|
+
name = 'Ground Control'
|
54
|
+
Buster.fire :hello_pluto, :name => name
|
55
|
+
mutex.synchronize do
|
56
|
+
baton.wait(mutex, 1)
|
57
|
+
@response.should == name.reverse
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'when sending to a powered down space station' do
|
63
|
+
before do
|
64
|
+
Handler 'TenFour' do |props|
|
65
|
+
mutex.synchronize do
|
66
|
+
@ten_foured = true
|
67
|
+
baton.signal
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should receive response when the station powers up' do
|
73
|
+
Buster.fire :adjust_station_temperature
|
74
|
+
sleep 1 #Oh no! Station isn't started!
|
75
|
+
start_endpoint :station
|
76
|
+
mutex.synchronize do
|
77
|
+
baton.wait(mutex, 1)
|
78
|
+
@ten_foured.should == true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
5
|
+
require 'buster'
|
6
|
+
|
7
|
+
Bundler.require :development
|
8
|
+
|
9
|
+
Dir[File.join(File.dirname(__FILE__), 'support', '*.rb')].each { |d| require d }
|
10
|
+
|
11
|
+
Thread.abort_on_exception = true
|
12
|
+
|
13
|
+
$endpoint_pids = []
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.mock_with :rspec
|
17
|
+
|
18
|
+
config.after(:all) do
|
19
|
+
$endpoint_pids.each { |p| Process.kill("TERM", p) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def Handler(name, &block)
|
24
|
+
const_name = "#{name}Handler"
|
25
|
+
if Object.const_defined?(const_name)
|
26
|
+
klass = Object.const_get(const_name)
|
27
|
+
else
|
28
|
+
klass = Class.new do
|
29
|
+
include Buster::CommandHandler
|
30
|
+
|
31
|
+
def execute(props)
|
32
|
+
@@execute_action.call(props,self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.execute_action=(action)
|
36
|
+
@@execute_action = action
|
37
|
+
end
|
38
|
+
end
|
39
|
+
Object.const_set(const_name, klass)
|
40
|
+
end
|
41
|
+
|
42
|
+
klass.execute_action = block
|
43
|
+
|
44
|
+
klass
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_endpoint(name)
|
48
|
+
pid = Process.spawn("ruby #{File.join(File.dirname(__FILE__),'test_endpoints', "#{name}.rb")}")
|
49
|
+
$endpoint_pids << pid
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
class HelloMarsHandler
|
4
|
+
include Buster::CommandHandler
|
5
|
+
|
6
|
+
def execute(props)
|
7
|
+
reply :hello_earth, 'name' => props['name'].upcase
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
trap("TERM") { Buster.stop }
|
12
|
+
|
13
|
+
Buster.local_endpoint = 'ipc://mars.ipc'
|
14
|
+
Buster.start
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
class HelloPlutoHandler
|
4
|
+
include Buster::CommandHandler
|
5
|
+
|
6
|
+
def execute(props)
|
7
|
+
reply :hello_earth, 'name' => props['name'].reverse
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
trap("TERM") { Buster.stop }
|
12
|
+
|
13
|
+
Buster.local_endpoint = 'ipc://pluto.ipc'
|
14
|
+
Buster.start
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
class AdjustStationTemperatureHandler
|
4
|
+
include Buster::CommandHandler
|
5
|
+
|
6
|
+
def execute(props)
|
7
|
+
reply :ten_four
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
trap("TERM") { Buster.stop }
|
12
|
+
|
13
|
+
Buster.local_endpoint = 'ipc://station.ipc'
|
14
|
+
Buster.start
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: buster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jason Staten
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-13 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ffi-rzmq
|
16
|
+
requirement: &11559360 !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: *11559360
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: msgpack
|
27
|
+
requirement: &11558760 !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: *11558760
|
36
|
+
description: Service Bus
|
37
|
+
email:
|
38
|
+
- jstaten07@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- Gemfile
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- buster.gemspec
|
48
|
+
- lib/buster.rb
|
49
|
+
- lib/buster/command_handler.rb
|
50
|
+
- lib/buster/context.rb
|
51
|
+
- lib/buster/host.rb
|
52
|
+
- lib/buster/poller.rb
|
53
|
+
- lib/buster/router.rb
|
54
|
+
- lib/buster/sender.rb
|
55
|
+
- lib/buster/version.rb
|
56
|
+
- lib/buster/worker.rb
|
57
|
+
- spec/integration_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- spec/test_endpoints/mars.rb
|
60
|
+
- spec/test_endpoints/pluto.rb
|
61
|
+
- spec/test_endpoints/station.rb
|
62
|
+
homepage: ''
|
63
|
+
licenses: []
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
hash: 1543500698659680995
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
hash: 1543500698659680995
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project: buster
|
88
|
+
rubygems_version: 1.8.13
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Service Bus
|
92
|
+
test_files:
|
93
|
+
- spec/integration_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
- spec/test_endpoints/mars.rb
|
96
|
+
- spec/test_endpoints/pluto.rb
|
97
|
+
- spec/test_endpoints/station.rb
|