sa-carrot 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Amos Elliston
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.markdown ADDED
@@ -0,0 +1,34 @@
1
+ # Carrot
2
+
3
+ A synchronous amqp client. Based on Aman's amqp client:
4
+
5
+ [http://github.com/tmm1/amqp/tree/master] (http://github.com/tmm1/amqp/tree/master)
6
+
7
+ ## Motivation
8
+
9
+ This client does not use eventmachine so no background thread necessary. As a result, it is much easier to use from script/console and Passenger. It also solves the problem of buffering messages and ack responses. For more details see [this thread] (http://groups.google.com/group/ruby-amqp/browse_thread/thread/fdae324a0ebb1961/fa185fdce1841b68).
10
+
11
+ There is currently no way to prevent buffering using eventmachine. Support for prefetch is still unreliable.
12
+
13
+
14
+ ## Example
15
+
16
+ require 'carrot'
17
+
18
+ q = Carrot.queue('name')
19
+ 10.times do |num|
20
+ q.publish(num.to_s)
21
+ end
22
+
23
+ puts "Queued #{q.message_count} messages"
24
+ puts
25
+
26
+ while msg = q.pop(:ack => true)
27
+ puts "Popping: #{msg}"
28
+ q.ack
29
+ end
30
+ Carrot.stop
31
+
32
+ # LICENSE
33
+
34
+ Copyright (c) 2009 Amos Elliston, Geni.com; Published under The MIT License, see License
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rcov/rcovtask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = "sa-carrot"
10
+ s.summary = "A synchronous amqp client"
11
+ s.email = "amos@geni.com"
12
+ s.homepage = "http://github.com/sonian/carrot"
13
+ s.description = "A synchronous amqp client"
14
+ s.authors = ["Amos Elliston"]
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ Rake::TestTask.new do |t|
21
+ t.libs << 'lib'
22
+ t.pattern = 'test/**/*_test.rb'
23
+ t.verbose = false
24
+ end
25
+
26
+ Rake::RDocTask.new do |rdoc|
27
+ rdoc.rdoc_dir = 'rdoc'
28
+ rdoc.title = 'carrot'
29
+ rdoc.options << '--line-numbers' << '--inline-source'
30
+ rdoc.rdoc_files.include('README*')
31
+ rdoc.rdoc_files.include('lib/**/*.rb')
32
+ end
33
+
34
+ Rcov::RcovTask.new do |t|
35
+ t.libs << 'test'
36
+ t.test_files = FileList['test/**/*_test.rb']
37
+ t.verbose = true
38
+ end
39
+
40
+ task :default => :test
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 7
4
+ :patch: 1
@@ -0,0 +1,401 @@
1
+ if [].map.respond_to? :with_index
2
+ class Array #:nodoc:
3
+ def enum_with_index
4
+ each.with_index
5
+ end
6
+ end
7
+ else
8
+ require 'enumerator'
9
+ end
10
+
11
+ module Carrot::AMQP
12
+ class Buffer #:nodoc: all
13
+ class Overflow < StandardError; end
14
+ class InvalidType < StandardError; end
15
+
16
+ def initialize data = ''
17
+ @data = data
18
+ @pos = 0
19
+ end
20
+
21
+ attr_reader :pos
22
+
23
+ def data
24
+ @data.clone
25
+ end
26
+ alias :contents :data
27
+ alias :to_s :data
28
+
29
+ def << data
30
+ @data << data.to_s
31
+ self
32
+ end
33
+
34
+ def length
35
+ @data.length
36
+ end
37
+
38
+ def empty?
39
+ pos == length
40
+ end
41
+
42
+ def rewind
43
+ @pos = 0
44
+ end
45
+
46
+ def read_properties *types
47
+ types.shift if types.first == :properties
48
+
49
+ i = 0
50
+ values = []
51
+
52
+ while props = read(:short)
53
+ (0..14).each do |n|
54
+ # no more property types
55
+ break unless types[i]
56
+
57
+ # if flag is set
58
+ if props & (1<<(15-n)) != 0
59
+ if types[i] == :bit
60
+ # bit values exist in flags only
61
+ values << true
62
+ else
63
+ # save type name for later reading
64
+ values << types[i]
65
+ end
66
+ else
67
+ # property not set or is false bit
68
+ values << (types[i] == :bit ? false : nil)
69
+ end
70
+
71
+ i+=1
72
+ end
73
+
74
+ # bit(0) == 0 means no more property flags
75
+ break unless props & 1 == 1
76
+ end
77
+
78
+ values.map do |value|
79
+ value.is_a?(Symbol) ? read(value) : value
80
+ end
81
+ end
82
+
83
+ def read *types
84
+ if types.first == :properties
85
+ return read_properties(*types)
86
+ end
87
+
88
+ values = types.map do |type|
89
+ case type
90
+ when :octet
91
+ _read(1, 'C')
92
+ when :short
93
+ _read(2, 'n')
94
+ when :long
95
+ _read(4, 'N')
96
+ when :longlong
97
+ upper, lower = _read(8, 'NN')
98
+ upper << 32 | lower
99
+ when :shortstr
100
+ _read read(:octet)
101
+ when :longstr
102
+ _read read(:long)
103
+ when :timestamp
104
+ Time.at read(:longlong)
105
+ when :table
106
+ t = Hash.new
107
+
108
+ table = Buffer.new(read(:longstr))
109
+ until table.empty?
110
+ key, type = table.read(:shortstr, :octet)
111
+ key = key.intern
112
+ t[key] ||= case type
113
+ when 83 # 'S'
114
+ table.read(:longstr)
115
+ when 73 # 'I'
116
+ table.read(:long)
117
+ when 68 # 'D'
118
+ exp = table.read(:octet)
119
+ num = table.read(:long)
120
+ num / 10.0**exp
121
+ when 84 # 'T'
122
+ table.read(:timestamp)
123
+ when 70 # 'F'
124
+ table.read(:table)
125
+ end
126
+ end
127
+
128
+ t
129
+ when :bit
130
+ if (@bits ||= []).empty?
131
+ val = read(:octet)
132
+ @bits = (0..7).map{|i| (val & 1<<i) != 0 }
133
+ end
134
+
135
+ @bits.shift
136
+ else
137
+ raise InvalidType, "Cannot read data of type #{type}"
138
+ end
139
+ end
140
+
141
+ types.size == 1 ? values.first : values
142
+ end
143
+
144
+ def write type, data
145
+ case type
146
+ when :octet
147
+ _write(data, 'C')
148
+ when :short
149
+ _write(data, 'n')
150
+ when :long
151
+ _write(data, 'N')
152
+ when :longlong
153
+ lower = data & 0xffffffff
154
+ upper = (data & ~0xffffffff) >> 32
155
+ _write([upper, lower], 'NN')
156
+ when :shortstr
157
+ data = (data || '').to_s
158
+ _write([data.length, data], 'Ca*')
159
+ when :longstr
160
+ if data.is_a? Hash
161
+ write(:table, data)
162
+ else
163
+ data = (data || '').to_s
164
+ _write([data.length, data], 'Na*')
165
+ end
166
+ when :timestamp
167
+ write(:longlong, data.to_i)
168
+ when :table
169
+ data ||= {}
170
+ write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
171
+ table.write(:shortstr, key.to_s)
172
+
173
+ case value
174
+ when String
175
+ table.write(:octet, 83) # 'S'
176
+ table.write(:longstr, value.to_s)
177
+ when Fixnum
178
+ table.write(:octet, 73) # 'I'
179
+ table.write(:long, value)
180
+ when Float
181
+ table.write(:octet, 68) # 'D'
182
+ # XXX there's gotta be a better way to do this..
183
+ exp = value.to_s.split('.').last.length
184
+ num = value * 10**exp
185
+ table.write(:octet, exp)
186
+ table.write(:long, num)
187
+ when Time
188
+ table.write(:octet, 84) # 'T'
189
+ table.write(:timestamp, value)
190
+ when Hash
191
+ table.write(:octet, 70) # 'F'
192
+ table.write(:table, value)
193
+ end
194
+
195
+ table
196
+ end)
197
+ when :bit
198
+ [*data].to_enum(:each_slice, 8).each{|bits|
199
+ write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
200
+ byte |= 1<<i if bit
201
+ byte
202
+ })
203
+ }
204
+ when :properties
205
+ values = []
206
+ data.enum_with_index.inject(0) do |short, ((type, value), i)|
207
+ n = i % 15
208
+ last = i+1 == data.size
209
+
210
+ if (n == 0 and i != 0) or last
211
+ if data.size > i+1
212
+ short |= 1<<0
213
+ elsif last and value
214
+ values << [type,value]
215
+ short |= 1<<(15-n)
216
+ end
217
+
218
+ write(:short, short)
219
+ short = 0
220
+ end
221
+
222
+ if value and !last
223
+ values << [type,value]
224
+ short |= 1<<(15-n)
225
+ end
226
+
227
+ short
228
+ end
229
+
230
+ values.each do |type, value|
231
+ write(type, value) unless type == :bit
232
+ end
233
+ else
234
+ raise InvalidType, "Cannot write data of type #{type}"
235
+ end
236
+
237
+ self
238
+ end
239
+
240
+ def extract
241
+ begin
242
+ cur_data, cur_pos = @data.clone, @pos
243
+ yield self
244
+ rescue Overflow
245
+ @data, @pos = cur_data, cur_pos
246
+ nil
247
+ end
248
+ end
249
+
250
+ def _read(size, pack = nil)
251
+ if @data.is_a?(Server)
252
+ raw = @data.read(size)
253
+ return raw if raw.nil? or pack.nil?
254
+ return raw.unpack(pack).first
255
+ end
256
+
257
+ if @pos + size > length
258
+ raise Overflow
259
+ else
260
+ data = @data[@pos,size]
261
+ @data[@pos,size] = ''
262
+ if pack
263
+ data = data.unpack(pack)
264
+ data = data.pop if data.size == 1
265
+ end
266
+ data
267
+ end
268
+ end
269
+
270
+ def _write data, pack = nil
271
+ data = [*data].pack(pack) if pack
272
+ @data[@pos,0] = data
273
+ @pos += data.length
274
+ end
275
+ end
276
+ end
277
+
278
+ if $0 =~ /bacon/ or $0 == __FILE__
279
+ require 'bacon'
280
+ include AMQP
281
+
282
+ describe Buffer do
283
+ before do
284
+ @buf = Buffer.new
285
+ end
286
+
287
+ should 'have contents' do
288
+ @buf.contents.should == ''
289
+ end
290
+
291
+ should 'initialize with data' do
292
+ @buf = Buffer.new('abc')
293
+ @buf.contents.should == 'abc'
294
+ end
295
+
296
+ should 'append raw data' do
297
+ @buf << 'abc'
298
+ @buf << 'def'
299
+ @buf.contents.should == 'abcdef'
300
+ end
301
+
302
+ should 'append other buffers' do
303
+ @buf << Buffer.new('abc')
304
+ @buf.data.should == 'abc'
305
+ end
306
+
307
+ should 'have a position' do
308
+ @buf.pos.should == 0
309
+ end
310
+
311
+ should 'have a length' do
312
+ @buf.length.should == 0
313
+ @buf << 'abc'
314
+ @buf.length.should == 3
315
+ end
316
+
317
+ should 'know the end' do
318
+ @buf.empty?.should == true
319
+ end
320
+
321
+ should 'read and write data' do
322
+ @buf._write('abc')
323
+ @buf.rewind
324
+ @buf._read(2).should == 'ab'
325
+ @buf._read(1).should == 'c'
326
+ end
327
+
328
+ should 'raise on overflow' do
329
+ lambda{ @buf._read(1) }.should.raise Buffer::Overflow
330
+ end
331
+
332
+ should 'raise on invalid types' do
333
+ lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
334
+ lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
335
+ end
336
+
337
+ { :octet => 0b10101010,
338
+ :short => 100,
339
+ :long => 100_000_000,
340
+ :longlong => 666_555_444_333_222_111,
341
+ :shortstr => 'hello',
342
+ :longstr => 'bye'*500,
343
+ :timestamp => time = Time.at(Time.now.to_i),
344
+ :table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
345
+ :bit => true
346
+ }.each do |type, value|
347
+
348
+ should "read and write a #{type}" do
349
+ @buf.write(type, value)
350
+ @buf.rewind
351
+ @buf.read(type).should == value
352
+ @buf.should.be.empty
353
+ end
354
+
355
+ end
356
+
357
+ should 'read and write multiple bits' do
358
+ bits = [true, false, false, true, true, false, false, true, true, false]
359
+ @buf.write(:bit, bits)
360
+ @buf.write(:octet, 100)
361
+
362
+ @buf.rewind
363
+
364
+ bits.map do
365
+ @buf.read(:bit)
366
+ end.should == bits
367
+ @buf.read(:octet).should == 100
368
+ end
369
+
370
+ should 'read and write properties' do
371
+ properties = ([
372
+ [:octet, 1],
373
+ [:shortstr, 'abc'],
374
+ [:bit, true],
375
+ [:bit, false],
376
+ [:shortstr, nil],
377
+ [:timestamp, nil],
378
+ [:table, { :a => 'hash' }],
379
+ ]*5).sort_by{rand}
380
+
381
+ @buf.write(:properties, properties)
382
+ @buf.rewind
383
+ @buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
384
+ @buf.should.be.empty
385
+ end
386
+
387
+ should 'do transactional reads with #extract' do
388
+ @buf.write :octet, 8
389
+ orig = @buf.to_s
390
+
391
+ @buf.rewind
392
+ @buf.extract do |b|
393
+ b.read :octet
394
+ b.read :short
395
+ end
396
+
397
+ @buf.pos.should == 0
398
+ @buf.data.should == orig
399
+ end
400
+ end
401
+ end