hayeah-ASS 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README.textile +0 -162
  2. data/Rakefile +1 -1
  3. data/VERSION.yml +2 -2
  4. data/lib/ass.rb +137 -338
  5. metadata +3 -4
  6. data/ASS.gemspec +0 -48
data/README.textile CHANGED
@@ -1,162 +0,0 @@
1
-
2
- Asynchronous Service Stages (ASS) is a way to
3
- organize distributed services by decoupling the
4
- what and how of computation from the when and
5
- where. Built on top of RabbitMQ (an implementation
6
- of AMQP), ASS helps you to build robust and
7
- scalable distributed applications.
8
-
9
- End of project pimping, let's get started.
10
-
11
- h1. Install
12
-
13
- You need
14
-
15
- (1) Erlang
16
- (2) RabbitMQ
17
- (3) AMQP gem
18
-
19
- Thread.new { EM.run }
20
- AMQP.start
21
-
22
- ^C to exit
23
-
24
-
25
- h1. The Basics
26
-
27
- A service component is a ruby script that
28
- communicates with RabbitMQ. You need to define the
29
- AMQP server your ASS depends on. Something like this,
30
-
31
-
32
- require 'rubygems'
33
- require 'ass'
34
- AMQP.start(:host => 'localhost',
35
- #:vhost => "/ass-test",
36
- :logging => false) do
37
- # ASS definition
38
- end
39
-
40
-
41
- To start a server
42
-
43
- server = ASS.new("echo")
44
- # => #<ASS::Server echo>
45
-
46
- But it doesn't do anything yet. You define the
47
- behaviour of the server by setting its
48
- callback. The callback can be a class, so that for
49
- each client request an object is created from the
50
- class to process the request. Like so,
51
-
52
-
53
- server.react(SomeReactorClass)
54
-
55
-
56
- However, often you just want something simple. The
57
- react method can take a block and construct an
58
- anonymous callback class from which the server
59
- creates an callback object for each request. Here
60
- we ask the server to react to @foo@ or @bar@.
61
-
62
-
63
- server.react {
64
- def foo(input)
65
- [:server,:foo,input]
66
- end
67
-
68
- def oof(input)
69
- [:server,:oof,input]
70
- end
71
- }
72
-
73
-
74
- The react method accepts for the callback either
75
- a Class, a Module, a block, or any object. When an
76
- object is used, it's considered a singleton, which
77
- is used to process all the requests.
78
-
79
- Now that we have a server, we need to get a client
80
- so to call the server. Because the call is
81
- asynchronous (the client doesn't wait for the
82
- result), to process the result when it gets back,
83
- we need to define callback for the client (just as
84
- we did for the server). For each call to the
85
- remote server, the result is processed at the
86
- client side by a method of the same name,
87
-
88
-
89
- client = server.client.react {
90
- def foo(output)
91
- p [:client,:foo,output]
92
- end
93
- def oof(output)
94
- p [:client,:oof,output]
95
- end
96
- }
97
-
98
- c.call(:foo,42)
99
- c.call(:oof,24)
100
-
101
- # [:client,:foo,[:server,:foo,42]]
102
- # [:client,:foo,[:server,:foo,24]]
103
-
104
-
105
- > ruby server.rb
106
- > ruby client.rb
107
-
108
- > ruby server.rb
109
- ^C
110
-
111
- While the server is down, the requests the client
112
- is making is queued by the underlying message
113
- middleware (RabbitMQ), so in some future time when
114
- we restart the server, we wouldn't lose any
115
- request. Let's restart the server.
116
-
117
- > ruby server.rb
118
-
119
- See that the server caught up with all the pending
120
- requests. To increase service capacity, we can
121
- just increase the number of server instances.
122
-
123
- > ruby server.rb
124
-
125
- Now the load is distributed between these two
126
- instances. We can also start more clients to
127
- handle more load.
128
-
129
- > ruby client.rb
130
-
131
- You can see requests coming in from two clients.
132
-
133
-
134
- h1. Service Configuration
135
-
136
- -how the name of a service map to AMQP entities.
137
- -various options for different functional characteristics.
138
-
139
- -using routing_key
140
- -using reply_to
141
-
142
-
143
- rabbitmqctl list_exchanges
144
- rabbitmqctl list_queues
145
-
146
-
147
- h1. RPC service
148
-
149
- The RPC client provides a synchronous API to the
150
- asynchronous services. This is so the users of an
151
- ASS application don't have to bother with the
152
- difficulties of asynchronous programming style
153
- with callbacks.
154
-
155
- The RPC client is intended to be used as the
156
- gateway into some reliable internal
157
- services. While the internal services coordinated
158
- with ASS needs to be robust to component failures,
159
- there's no such requirements for gateways. It is
160
- ok for a gateway to fail, and fail in delivering a
161
- response, as long as the internal services carry
162
- out the task without a hitch.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gem.email = "hayeah@gmail.com"
10
10
  gem.homepage = "http://github.com/hayeah/ass"
11
11
  gem.authors = ["Howard Yeh"]
12
- gem.add_dependency "amqp"
12
+ gem.add_dependency "mq"
13
13
  gem.files = FileList["[A-Z]*", "{lib,test}/**/*"]
14
14
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
15
  end
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 0
4
- :patch: 2
3
+ :minor: 1
4
+ :patch: 0
data/lib/ass.rb CHANGED
@@ -1,209 +1,137 @@
1
1
  require 'mq'
2
+ class Ass
3
+
4
+ attr_reader :server_exchange
2
5
 
3
- # TODO a way to specify serializer (json, marshal...)
4
- module ASS
6
+ def self.declare(server_exchange)
7
+ self.new([server_exchange,{:passive => true}])
8
+ end
9
+
10
+ # uh... we'll assume that the exchanges are all direct exchanges.
11
+ def initialize(server_exchange)
12
+ @server_exchange = get_exchange(server_exchange)
13
+ end
5
14
 
6
- # non-destructive get. Fail if server's not started.
7
- def self.get(name)
8
- ASS::Server.new(name,:passive => true)
15
+ def client(client_exchange,*args)
16
+ @client_exchange ||= get_exchange(client_exchange)
17
+ q = get_queue(@client_exchange,*args)
18
+ Client.new(@server_exchange,@client_exchange,q)
9
19
  end
10
20
 
11
- def self.new(name,opts={})
12
- ASS::Server.new(name)
21
+ def server(*args)
22
+ Server.new(@server_exchange,get_queue(@server_exchange,*args))
13
23
  end
14
24
 
15
- def self.peep(server_name,callback=nil,&block)
16
- callback = block if callback.nil?
17
- ASS::Peeper.new(server_name,callback)
25
+ def get_exchange(arg)
26
+ case arg
27
+ when Array
28
+ exchanges = exchange[0]
29
+ opts = exchange[1]
30
+ else
31
+ exchange = arg
32
+ opts = nil
33
+ end
34
+ opts = {} if opts.nil?
35
+ exchange = exchange.is_a?(MQ::Exchange) ? exchange : MQ.direct(exchange,opts)
36
+ raise "accepts only direct exchanges" unless exchange.type == :direct
37
+ exchange
18
38
  end
19
-
39
+
40
+ # can specify a key to create a queue for that subdomain
41
+ def get_queue(exchange,*args)
42
+ case args[0]
43
+ when Hash
44
+ key = nil
45
+ opts = args[0]
46
+ when String
47
+ key = args[0]
48
+ opts = args[1]
49
+ end
50
+ opts = {} if opts.nil?
51
+ if key
52
+ name = "#{exchange.name}--#{key}"
53
+ q = MQ.queue(name,opts)
54
+ q.bind(exchange,{ :routing_key => key})
55
+ else
56
+ q = MQ.queue(exchange.name,opts)
57
+ q.bind(exchange,{ :routing_key => exchange.name })
58
+ end
59
+ q
60
+ end
61
+
20
62
  module Callback
21
- module MagicMethods
22
- def header
23
- @__header__
24
- end
25
-
26
- def meta
27
- @__meta__
28
- end
29
-
30
- def service
31
- @__service__
32
- end
33
-
34
- def call(method,data=nil,meta=nil,opts={})
35
- @__service__.call(method,data,meta,opts)
36
- end
37
- end
38
63
 
39
- # called to initiate a callback
40
- def build_callback(callback)
41
- c = case callback
42
- when Proc
43
- Class.new &callback
44
- when Class
45
- callback
46
- when Module
47
- Class.new { include callback }
48
- when Object
49
- callback # use singleton objcet as callback
50
- end
51
- case c
64
+ def build_callback_klass(callback)
65
+ case callback
66
+ when Proc
67
+ Class.new &callback
52
68
  when Class
53
- c.instance_eval { include MagicMethods }
54
- else
55
- c.extend MagicMethods
69
+ callback
70
+ when Module
71
+ Class.new { include callback }
56
72
  end
57
- c
58
73
  end
59
-
60
- # called for each request
61
- def prepare_callback(callback,info,payload)
74
+
75
+ def callback(info,payload)
62
76
  # method,data,meta
63
- if callback.is_a? Class
64
- if callback.respond_to? :version
65
- klass = callback.get_version(payload[:version])
66
- else
67
- klass = callback
68
- end
69
- obj = klass.new
77
+ if @callback_klass.respond_to? :version
78
+ klass = @callback_klass.get_version(payload[:version])
70
79
  else
71
- obj = callback
80
+ klass = @callback_klass
72
81
  end
73
- obj.instance_variable_set("@__service__",self)
82
+ obj = klass.new
83
+ service = self
84
+ obj.instance_variable_set("@__service__",service)
74
85
  obj.instance_variable_set("@__header__",info)
75
86
  obj.instance_variable_set("@__meta__",payload[:meta])
76
- #p [:call,payload]
77
- obj
78
- end
79
- end
80
-
81
- class Server
82
- include Callback
83
-
84
- def initialize(name,opts={})
85
- @server_exchange = MQ.fanout(name,opts)
86
- end
87
-
88
- def name
89
- self.exchange.name
90
- end
91
-
92
- def exchange
93
- @server_exchange
94
- end
95
-
96
- # takes options available to MQ::Exchange
97
- def client(opts={})
98
- ASS::Client.new(self,opts)
99
- end
100
-
101
- def client_name
102
- "#{self.exchange.name}--"
103
- end
104
-
105
- # takes options available to MQ::Queue# takes options available to MQ::Queue#subscribe
106
- def rpc(opts={})
107
- ASS::RPC.new(self,opts)
108
- end
87
+ class << obj
88
+ def header
89
+ @__header__
90
+ end
109
91
 
110
- def queue(opts={})
111
- unless @queue
112
- @queue ||= MQ.queue(self.name,opts)
113
- @queue.bind(self.exchange)
114
- end
115
- self
116
- end
92
+ def meta
93
+ @__meta__
94
+ end
95
+
96
+ def service
97
+ @__service__
98
+ end
117
99
 
118
- # takes options available to MQ::Queue# takes options available to MQ::Queue#subscribe
119
- def react(callback=nil,opts=nil,&block)
120
- if block
121
- opts = callback
122
- callback = block
123
- end
124
- opts = {} if opts.nil?
125
-
126
- @callback = build_callback(callback)
127
- @ack = opts[:ack]
128
- self.queue unless @queue
129
- @queue.subscribe(opts) do |info,payload|
130
- payload = ::Marshal.load(payload)
131
- #p [info,info.reply_to,payload]
132
- obj = prepare_callback(@callback,info,payload)
133
- data2 = obj.send(payload[:method],payload[:data])
134
- payload2 = payload.merge :data => data2
135
- # the client MUST exist, otherwise it's an error.
136
- ## FIXME it's bad if the server dies b/c
137
- ## the client isn't there. It's bad that
138
- ## this can cause the server to fail.
139
- MQ.direct(info.reply_to,:passive => true).
140
- publish(::Marshal.dump(payload2),
141
- :routing_key => info.routing_key,
142
- :message_id => info.message_id) if info.reply_to
143
- info.ack if @ack
100
+ def call(method,data=nil,meta=nil,opts={})
101
+ @__service__.call(method,data,meta,opts)
102
+ end
144
103
  end
145
- self
146
- end
147
-
148
- def inspect
149
- "#<#{self.class} #{self.name}>"
104
+ #p [:call,payload]
105
+ obj.send(payload[:method],
106
+ payload[:data])
150
107
  end
151
108
  end
152
109
 
153
110
  class Client
154
111
  include Callback
155
-
156
- # takes options available to MQ::Exchange
157
- def initialize(server,opts={})
158
- @server = server
159
- # the routing key is also used as the name of the client
160
- @key = opts.delete :key
161
- @key = @key.to_s if @key
162
- @client_exchange = MQ.direct @server.client_name, opts
112
+ def initialize(server_exchange,client_exchange,queue)
113
+ @server_exchange = server_exchange
114
+ @client_exchange = client_exchange
115
+ @queue = queue
163
116
  end
164
-
165
- def name
166
- self.exchange.name
167
- end
168
-
169
- def exchange
170
- @client_exchange
171
- end
172
-
173
- # takes options available to MQ::Queue
174
- def queue(opts={})
175
- unless @queue
176
- # if key is not given, the queue name is
177
- # the same as the exchange name.
178
- @queue ||= MQ.queue("#{self.name}#{@key}",opts)
179
- @queue.bind(self.exchange,:routing_key => @key || self.name)
180
- end
181
- self # return self to allow chaining
182
- end
183
-
184
- # takes options available to MQ::Queue#subscribe
117
+
185
118
  def react(callback=nil,opts=nil,&block)
186
119
  if block
187
120
  opts = callback
188
121
  callback = block
189
122
  end
190
123
  opts = {} if opts.nil?
191
-
192
- @callback = build_callback(callback)
124
+
125
+ @callback_klass = build_callback_klass(callback)
193
126
  @ack = opts[:ack]
194
- # ensure queue is set
195
- self.queue unless @queue
196
127
  @queue.subscribe(opts) do |info,payload|
197
128
  payload = ::Marshal.load(payload)
198
- obj = prepare_callback(@callback,info,payload)
199
- obj.send(payload[:method],payload[:data])
129
+ callback(info,payload)
200
130
  info.ack if @ack
201
131
  end
202
132
  self
203
133
  end
204
-
205
- # note that we can redirect the result to some
206
- # place else by setting :key and :reply_to
134
+
207
135
  def call(method,data=nil,meta=nil,opts={})
208
136
  # opts passed to publish
209
137
  # if no routing key is given, use receiver's name as the routing key.
@@ -215,199 +143,70 @@ module ASS
215
143
  :version => version
216
144
  }
217
145
 
218
- @server.exchange.publish Marshal.dump(payload), {
219
- # opts[:routing_key] will override :key in MQ::Exchange#publish
220
- :key => (@key ? @key : self.name),
221
- :reply_to => self.name
146
+ # set it up s.t. server would respond to
147
+ # private queue if key is given, otherwise
148
+ # the server would respond to public queue.
149
+ key = opts.delete(:key)
150
+ @server_exchange.publish Marshal.dump(payload), {
151
+ :key => (key ? key : @server_exchange.name),
152
+ :reply_to => @client_exchange.name
222
153
  }.merge(opts)
223
154
  end
224
-
155
+
225
156
  # for casting, just null the reply_to field, so server doesn't respond.
226
157
  def cast(method,data=nil,meta=nil,opts={})
227
158
  self.call(method,data,meta,opts.merge({:reply_to => nil}))
228
159
  end
229
160
 
230
- def inspect
231
- "#<#{self.class} #{self.name}>"
232
- end
161
+
233
162
  end
234
163
 
235
- # assumes server initializes it with an exclusive and auto_delete queue.
236
- # TODO timeout
237
- class RPC
238
- require 'thread'
239
-
240
- # i don't want deferrable. I want actual blockage when waiting.
241
- ## subscribe prolly run in a different thread.
242
- # hmmm. I guess deferrable is a better idea.
243
- class Future
244
- attr_reader :message_id
245
- attr_accessor :header, :data, :meta, :timeout
246
- def initialize(rpc,message_id)
247
- @message_id = message_id
248
- @rpc = rpc
249
- @timeout = false
250
- @done = false
251
- end
252
-
253
- def wait(timeout=nil,&block)
254
- # TODO timeout with eventmachine
255
- @rpc.wait(self,timeout,&block) # synchronous call that will block
256
- # EM.cancel_timer(ticket)
257
- end
258
-
259
- def done!
260
- @done = true
261
- end
262
-
263
- def done?
264
- @done
265
- end
266
-
267
- def timeout?
268
- @timeout
269
- end
270
-
271
- def inspect
272
- "#<#{self.class} #{message_id}>"
273
- end
274
- end
275
-
276
- class Reactor
277
- # want to minimize name conflicts here.
278
- def initialize(rpc)
279
- @rpc = rpc
280
- end
281
-
282
- def method_missing(_method,data)
283
- @rpc.buffer << [header,data,meta]
284
- end
285
- end
164
+ class Server
165
+ include Callback
286
166
 
287
- attr_reader :buffer, :futures, :ready
288
- def initialize(server,opts={})
289
- @server = server
290
- @seq = 0
291
- # queue is used be used to synchronize RPC
292
- # user thread and the AMQP eventmachine thread.
293
- @buffer = Queue.new
294
- @ready = {} # the ready results not yet waited
295
- @futures = {} # all futures not yet waited for.
296
- @reactor = Reactor.new(self)
297
- # Creates an exclusive queue to serve the RPC client.
298
- @client = @server.client(:key => "rpc.#{rand(999_999_999_999)}").
299
- queue(:exclusive => true).react(@reactor,opts)
167
+ def initialize(server_exchange,q)
168
+ @queue = q
169
+ @server_exchange = server_exchange
300
170
  end
301
171
 
302
- def call(method,data,meta=nil,opts={})
303
- message_id = @seq.to_s # message gotta be unique for this RPC client.
304
- @client.call method, data, meta, opts.merge(:message_id => message_id)
305
- @seq += 1
306
- @futures[message_id] = Future.new(self,message_id)
172
+ attr_reader :queue
173
+ def exchange
174
+ @server_exchange
307
175
  end
308
176
 
309
- # the idea is to block on a synchronized queue
310
- # until we get the future we want.
311
- #
312
- # WARNING: blocks forever if the thread
313
- # calling wait is the same as the EventMachine
314
- # thread.
315
- def wait(future,timeout=nil)
316
- return future.data if future.done? # future was waited before
317
- timer = nil
318
- if timeout
319
- timer = EM.add_timer(timeout) {
320
- @buffer << [:timeout,future.message_id,nil]
321
- }
177
+ def react(callback=nil,opts=nil,&block)
178
+ if block
179
+ opts = callback
180
+ callback = block
322
181
  end
323
- ready_future = nil
324
- if @ready.has_key? future.message_id
325
- @ready.delete future.message_id
326
- ready_future = future
327
- else
328
- while true
329
- header,data,meta = data = @buffer.pop # synchronize. like erlang's mailbox select.
330
- if header == :timeout # timeout the future we are waiting for.
331
- message_id = data
332
- # if we got a timeout from previous wait. throw it away.
333
- next if future.message_id != message_id
334
- future.timeout = true
335
- future.done!
336
- @futures.delete future.message_id
337
- return yield # return the value of timeout block
338
- end
339
- some_future = @futures[header.message_id]
340
- # If we didn't find the future among the
341
- # future, it must have timedout. Just
342
- # throw result away and keep processing.
343
- next unless some_future
344
- some_future.header = header
345
- some_future.data = data
346
- some_future.meta = meta
347
- if some_future == future
348
- # The future we are waiting for
349
- EM.cancel_timer(timer)
350
- ready_future = future
351
- break
352
- else
353
- # Ready, but we are not waiting for it. Save for later.
354
- @ready[some_future.message_id] = some_future
355
- end
182
+ opts = {} if opts.nil?
183
+
184
+ @callback_klass = build_callback_klass(callback)
185
+ @ack = opts[:ack]
186
+ @queue.subscribe(opts) do |info,payload|
187
+ payload = ::Marshal.load(payload)
188
+ #p [info,info.reply_to,payload]
189
+ data2 = callback(info,payload)
190
+ payload2 = payload.merge :data => data2
191
+ if info.routing_key == @server_exchange.name
192
+ # addressed to the server's public
193
+ # queue, respond to the routing_key of
194
+ # the client's public queue.
195
+ key = info.reply_to
196
+ else
197
+ # addressed to the private queue
198
+ key = info.routing_key
356
199
  end
200
+ MQ.direct(info.reply_to).publish(::Marshal.dump(payload2),:routing_key => key) if info.reply_to
201
+ info.ack if @ack
357
202
  end
358
- ready_future.done!
359
- @futures.delete ready_future.message_id
360
- return ready_future.data
361
- end
362
-
363
- def waitall
364
- @futures.values.map { |k,v|
365
- wait(v)
366
- }
367
- end
368
-
369
- def inspect
370
- "#<#{self.class} #{self.name}>"
203
+ self
371
204
  end
372
205
  end
373
206
 
374
- # TODO should prolly have the option of using
375
- # non auto-delete queues. This would be useful
376
- # for logger. Maybe if a peeper name is given,
377
- # then create queues with options.
378
207
  class Peeper
379
- include Callback
380
- attr_reader :server_name
381
- def initialize(server_name,callback)
382
- @server_name = server_name
383
- @clients = {}
384
- @callback = build_callback(callback)
385
-
386
- uid = "#{@server_name}.peeper.#{rand 999_999_999_999}"
387
- q = MQ.queue uid, :auto_delete => true
388
- q.bind(@server_name) # messages to the server would be duplicated here.
389
- q.subscribe { |info,payload|
390
- payload = ::Marshal.load(payload)
391
- # sets context, but doesn't make the call
392
- obj = prepare_callback(@callback,info,payload)
393
- # there is a specific method we want to call.
394
- obj.server(payload[:method],payload[:data])
395
-
396
- # bind to peep client message queue if we've not seen it before.
397
- unless @clients.has_key? info.routing_key
398
- @clients[info.routing_key] = true
399
- client_q = MQ.queue "#{uid}--#{info.routing_key}",
400
- :auto_delete => true
401
- # messages to the client would be duplicated here.
402
- client_q.bind("#{server_name}--", :routing_key => info.routing_key)
403
- client_q.subscribe { |info,payload|
404
- payload = ::Marshal.load(payload)
405
- obj = prepare_callback(@callback,info,payload)
406
- obj.client(payload[:method],payload[:data])
407
- }
408
- end
409
- }
208
+ def initialize(exchange,callback)
209
+ # create a temporary queue that binds to an exchange
410
210
  end
411
211
  end
412
-
413
212
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hayeah-ASS
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Howard Yeh
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-17 00:00:00 -07:00
12
+ date: 2009-08-13 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: amqp
16
+ name: mq
17
17
  type: :runtime
18
18
  version_requirement:
19
19
  version_requirements: !ruby/object:Gem::Requirement
@@ -32,7 +32,6 @@ extra_rdoc_files:
32
32
  - LICENSE
33
33
  - README.textile
34
34
  files:
35
- - ASS.gemspec
36
35
  - LICENSE
37
36
  - README.textile
38
37
  - Rakefile
data/ASS.gemspec DELETED
@@ -1,48 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- Gem::Specification.new do |s|
4
- s.name = %q{ASS}
5
- s.version = "0.0.2"
6
-
7
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Howard Yeh"]
9
- s.date = %q{2009-08-17}
10
- s.email = %q{hayeah@gmail.com}
11
- s.extra_rdoc_files = [
12
- "LICENSE",
13
- "README.textile"
14
- ]
15
- s.files = [
16
- "ASS.gemspec",
17
- "LICENSE",
18
- "README.textile",
19
- "Rakefile",
20
- "VERSION.yml",
21
- "lib/ass.rb",
22
- "test/ass_test.rb",
23
- "test/test_helper.rb"
24
- ]
25
- s.has_rdoc = true
26
- s.homepage = %q{http://github.com/hayeah/ass}
27
- s.rdoc_options = ["--charset=UTF-8"]
28
- s.require_paths = ["lib"]
29
- s.rubygems_version = %q{1.3.1}
30
- s.summary = %q{Asynchronous Service Stages for Distributed Services}
31
- s.test_files = [
32
- "test/ass_test.rb",
33
- "test/test_helper.rb"
34
- ]
35
-
36
- if s.respond_to? :specification_version then
37
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
38
- s.specification_version = 2
39
-
40
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
41
- s.add_runtime_dependency(%q<amqp>, [">= 0"])
42
- else
43
- s.add_dependency(%q<amqp>, [">= 0"])
44
- end
45
- else
46
- s.add_dependency(%q<amqp>, [">= 0"])
47
- end
48
- end