hayeah-ASS 0.0.2 → 0.1.0

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