amqp 0.7.5 → 0.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/.gitignore +3 -4
  2. data/.travis.yml +3 -5
  3. data/.yardopts +6 -0
  4. data/CHANGELOG +26 -7
  5. data/Gemfile +15 -7
  6. data/README.textile +216 -0
  7. data/Rakefile +0 -6
  8. data/amqp.gemspec +14 -4
  9. data/bin/jenkins.sh +27 -0
  10. data/bin/set_test_suite_realms_up.sh +16 -2
  11. data/docs/VendorSpecificExtensions.textile +32 -0
  12. data/examples/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +53 -0
  13. data/examples/hello_world.rb +29 -0
  14. data/examples/real-world/task-queue/README.textile +3 -0
  15. data/examples/real-world/task-queue/consumer.rb +27 -0
  16. data/examples/real-world/task-queue/producer.rb +22 -0
  17. data/examples/various/ack.rb +70 -0
  18. data/examples/various/automatic_binding_for_default_direct_exchange.rb +53 -0
  19. data/examples/various/basic_get.rb +65 -0
  20. data/examples/various/callbacks.rb +45 -0
  21. data/examples/various/clock.rb +74 -0
  22. data/examples/various/declare_a_queue_without_assignment.rb +46 -0
  23. data/examples/various/declare_an_exchange_without_assignment.rb +46 -0
  24. data/examples/various/hashtable.rb +60 -0
  25. data/examples/{logger.rb → various/logger.rb} +9 -7
  26. data/examples/{multiclock.rb → various/multiclock.rb} +15 -17
  27. data/examples/various/open_channel_without_assignment.rb +34 -0
  28. data/examples/various/pingpong.rb +53 -0
  29. data/examples/various/primes-simple.rb +29 -0
  30. data/examples/various/primes.rb +76 -0
  31. data/examples/various/pubsub.rb +43 -0
  32. data/examples/various/queue_status.rb +58 -0
  33. data/examples/various/stocks.rb +59 -0
  34. data/examples/various/weather_updates.rb +63 -0
  35. data/lib/amqp.rb +11 -2
  36. data/lib/amqp/basic_client.rb +23 -54
  37. data/lib/amqp/channel.rb +577 -805
  38. data/lib/amqp/client.rb +37 -275
  39. data/lib/amqp/connection.rb +165 -93
  40. data/lib/amqp/deprecated/fork.rb +15 -0
  41. data/lib/amqp/deprecated/logger.rb +99 -0
  42. data/lib/amqp/deprecated/mq.rb +20 -0
  43. data/lib/amqp/deprecated/rpc.rb +168 -0
  44. data/lib/amqp/exchange.rb +409 -281
  45. data/lib/amqp/extensions/rabbitmq.rb +1 -0
  46. data/lib/amqp/header.rb +41 -17
  47. data/lib/amqp/logger.rb +10 -84
  48. data/lib/amqp/queue.rb +457 -320
  49. data/lib/amqp/rpc.rb +11 -107
  50. data/lib/amqp/version.rb +1 -1
  51. data/lib/mq.rb +2 -1
  52. data/lib/mq/logger.rb +2 -0
  53. data/lib/mq/rpc.rb +2 -0
  54. data/spec/integration/authentication_spec.rb +36 -40
  55. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +3 -5
  56. data/spec/integration/basic_get_spec.rb +91 -0
  57. data/spec/integration/channel_close_spec.rb +5 -5
  58. data/spec/integration/exchange_declaration_spec.rb +6 -53
  59. data/spec/integration/extensions/basic_return_spec.rb +47 -0
  60. data/spec/integration/queue_declaration_spec.rb +14 -17
  61. data/spec/integration/queue_exclusivity_spec.rb +49 -48
  62. data/spec/integration/reply_queue_communication_spec.rb +6 -4
  63. data/spec/integration/store_and_forward_spec.rb +9 -36
  64. data/spec/integration/topic_subscription_spec.rb +1 -1
  65. data/spec/integration/workload_distribution_spec.rb +1 -0
  66. data/spec/spec_helper.rb +69 -43
  67. data/spec/unit/amqp/connection_spec.rb +27 -23
  68. data/tasks.rb +11 -0
  69. metadata +124 -95
  70. data/README.md +0 -156
  71. data/TODO +0 -30
  72. data/amqp.pre.gemspec +0 -6
  73. data/examples/ack.rb +0 -47
  74. data/examples/automatic_binding_for_default_direct_exchange.rb +0 -65
  75. data/examples/callbacks.rb +0 -40
  76. data/examples/clock.rb +0 -65
  77. data/examples/default_channel.rb +0 -19
  78. data/examples/hashtable.rb +0 -61
  79. data/examples/immediately_bind_a_server_named_queue.rb +0 -38
  80. data/examples/internal.rb +0 -51
  81. data/examples/issues/issue_75.rb +0 -21
  82. data/examples/issues/issue_94.rb +0 -23
  83. data/examples/pingpong.rb +0 -54
  84. data/examples/pop.rb +0 -45
  85. data/examples/primes-simple.rb +0 -21
  86. data/examples/primes.rb +0 -101
  87. data/examples/simple.rb +0 -81
  88. data/examples/stocks.rb +0 -67
  89. data/gemfiles/eventmachine-pre +0 -24
  90. data/lib/amqp/buffer.rb +0 -272
  91. data/lib/amqp/collection.rb +0 -60
  92. data/lib/amqp/frame.rb +0 -68
  93. data/lib/amqp/protocol.rb +0 -163
  94. data/lib/amqp/server.rb +0 -101
  95. data/lib/amqp/spec.rb +0 -832
  96. data/protocol/amqp-0.8.json +0 -617
  97. data/protocol/amqp-0.8.xml +0 -3908
  98. data/protocol/codegen.rb +0 -175
  99. data/protocol/doc.txt +0 -281
  100. data/research/api.rb +0 -52
  101. data/research/primes-forked.rb +0 -65
  102. data/research/primes-processes.rb +0 -137
  103. data/research/primes-threaded.rb +0 -51
  104. data/spec/integration/queue_status_spec.rb +0 -44
  105. data/spec/unit/amqp/buffer_spec.rb +0 -178
  106. data/spec/unit/amqp/client_spec.rb +0 -102
  107. data/spec/unit/amqp/collection_spec.rb +0 -144
  108. data/spec/unit/amqp/frame_spec.rb +0 -60
  109. data/spec/unit/amqp/protocol_spec.rb +0 -51
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
-
4
- require "bundler"
5
- Bundler.setup
6
-
7
- $:.unshift(File.expand_path("../../../lib", __FILE__))
8
-
9
- require "amqp"
10
-
11
- puts "Running amqp gem #{AMQP::VERSION}"
12
-
13
- AMQP.start(:host => '127.0.0.1') do |connection|
14
- channel = AMQP::Channel.new(connection)
15
- exchange = channel.direct("")
16
- queue = channel.queue("indexer_queue", { :durable => true })
17
-
18
- EM.add_periodic_timer(1) {
19
- queue.status do |num_messages, num_consumers|
20
- puts "msgs:#{num_messages}"
21
- end
22
- }
23
- end
@@ -1,54 +0,0 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift(File.expand_path("../../lib", __FILE__))
4
- require 'amqp'
5
-
6
- AMQP.start(:host => 'localhost') do |connection|
7
-
8
- # Send Connection.Close on Ctrl+C
9
- trap(:INT) do
10
- unless connection.closing?
11
- connection.close { exit! }
12
- end
13
- end
14
-
15
- def log(*args)
16
- p [ Time.now, *args ]
17
- end
18
-
19
- # AMQP.logging = true
20
-
21
- amq = AMQP::Channel.new
22
- EM.add_periodic_timer(1) {
23
- puts
24
-
25
- log :sending, 'ping'
26
- amq.queue('one').publish('ping')
27
- }
28
-
29
- amq = AMQP::Channel.new
30
- amq.queue('one').subscribe { |msg|
31
- log 'one', :received, msg, :sending, 'pong'
32
- amq.queue('two').publish('pong')
33
- }
34
-
35
- amq = AMQP::Channel.new
36
- amq.queue('two').subscribe { |msg|
37
- log 'two', :received, msg
38
- }
39
-
40
- end
41
-
42
- __END__
43
-
44
- [Sun Jul 20 03:52:24 -0700 2008, :sending, "ping"]
45
- [Sun Jul 20 03:52:24 -0700 2008, "one", :received, "ping", :sending, "pong"]
46
- [Sun Jul 20 03:52:24 -0700 2008, "two", :received, "pong"]
47
-
48
- [Sun Jul 20 03:52:25 -0700 2008, :sending, "ping"]
49
- [Sun Jul 20 03:52:25 -0700 2008, "one", :received, "ping", :sending, "pong"]
50
- [Sun Jul 20 03:52:25 -0700 2008, "two", :received, "pong"]
51
-
52
- [Sun Jul 20 03:52:26 -0700 2008, :sending, "ping"]
53
- [Sun Jul 20 03:52:26 -0700 2008, "one", :received, "ping", :sending, "pong"]
54
- [Sun Jul 20 03:52:26 -0700 2008, "two", :received, "pong"]
@@ -1,45 +0,0 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift(File.expand_path("../../lib", __FILE__))
4
- require 'amqp'
5
- require 'pp'
6
-
7
- Signal.trap('INT') { AMQP.stop { EM.stop } }
8
- Signal.trap('TERM') { AMQP.stop { EM.stop } }
9
-
10
- AMQP.start do
11
- queue = AMQP::Channel.queue('awesome')
12
-
13
- queue.publish('Totally rad 1')
14
- queue.publish('Totally rad 2')
15
- EM.add_timer(5) { queue.publish('Totally rad 3') }
16
-
17
- queue.pop { |msg|
18
- unless msg
19
- # queue was empty
20
- p [Time.now, :queue_empty!]
21
-
22
- # try again in 1 second
23
- EM.add_timer(1) { queue.pop }
24
- else
25
- # process this message
26
- p [Time.now, msg]
27
-
28
- # get the next message in the queue
29
- queue.pop
30
- end
31
- }
32
- end
33
-
34
- __END__
35
-
36
- [Wed Oct 15 15:24:30 -0700 2008, "Totally rad 1"]
37
- [Wed Oct 15 15:24:30 -0700 2008, "Totally rad 2"]
38
- [Wed Oct 15 15:24:30 -0700 2008, :queue_empty!]
39
- [Wed Oct 15 15:24:31 -0700 2008, :queue_empty!]
40
- [Wed Oct 15 15:24:32 -0700 2008, :queue_empty!]
41
- [Wed Oct 15 15:24:33 -0700 2008, :queue_empty!]
42
- [Wed Oct 15 15:24:34 -0700 2008, :queue_empty!]
43
- [Wed Oct 15 15:24:35 -0700 2008, "Totally rad 3"]
44
- [Wed Oct 15 15:24:35 -0700 2008, :queue_empty!]
45
- [Wed Oct 15 15:24:36 -0700 2008, :queue_empty!]
@@ -1,21 +0,0 @@
1
- # encoding: utf-8
2
-
3
- MAX = 1000
4
-
5
- class Fixnum
6
- def prime?
7
- ('1' * self) !~ /^1?$|^(11+?)\1+$/
8
- end
9
- end
10
-
11
- class PrimeChecker
12
- def is_prime? number
13
- number.prime?
14
- end
15
- end
16
-
17
- prime_checker = PrimeChecker.new
18
-
19
- (10_000...(10_000+MAX)).each do |n|
20
- prime_checker.is_prime? n
21
- end
@@ -1,101 +0,0 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift(File.expand_path("../../lib", __FILE__))
4
- require 'amqp'
5
-
6
- # check MAX numbers for prime-ness
7
- MAX = 1000
8
-
9
- # logging
10
- def log(*args)
11
- p args
12
- end
13
-
14
- # spawn workers
15
- workers = ARGV[0] ? (Integer(ARGV[0]) rescue 1) : 1
16
- AMQP.fork(workers) do
17
-
18
- log AMQP::Channel.id, :started
19
-
20
- class Fixnum
21
- def prime?
22
- ('1' * self) !~ /^1?$|^(11+?)\1+$/
23
- end
24
- end
25
-
26
- class PrimeChecker
27
- def is_prime? number
28
- log "prime checker #{AMQP::Channel.id}", :prime?, number
29
- number.prime?
30
- end
31
- end
32
-
33
- AMQP::Channel.rpc('prime checker', PrimeChecker.new)
34
-
35
- end
36
-
37
- # use workers to check which numbers are prime
38
- AMQP.start(:host => 'localhost') do
39
-
40
- prime_checker = AMQP::Channel.rpc('prime checker')
41
-
42
- (10_000...(10_000+MAX)).each do |num|
43
- log :checking, num
44
-
45
- prime_checker.is_prime?(num) { |is_prime|
46
- log :prime?, num, is_prime
47
- (@primes||=[]) << num if is_prime
48
-
49
- if (@responses = (@responses || 0) + 1) == MAX
50
- log :primes=, @primes
51
- EM.stop_event_loop
52
- end
53
- }
54
-
55
- end
56
-
57
- end
58
-
59
- __END__
60
-
61
- $ uname -a
62
- Linux gc 2.6.24-ARCH #1 SMP PREEMPT Sun Mar 30 10:50:22 CEST 2008 x86_64 Intel(R) Xeon(R) CPU X3220 @ 2.40GHz GenuineIntel GNU/Linux
63
-
64
- $ cat /proc/cpuinfo | grep processor | wc -l
65
- 4
66
-
67
- $ time ruby primes-simple.rb
68
-
69
- real 0m16.055s
70
- user 0m16.052s
71
- sys 0m0.000s
72
-
73
- $ time ruby primes.rb 1 >/dev/null
74
-
75
- real 0m18.278s
76
- user 0m0.993s
77
- sys 0m0.027s
78
-
79
- $ time ruby primes.rb 2 >/dev/null
80
-
81
- real 0m17.316s
82
- user 0m0.967s
83
- sys 0m0.053s
84
-
85
- $ time ruby primes.rb 4 >/dev/null
86
-
87
- real 0m8.229s
88
- user 0m1.010s
89
- sys 0m0.030s
90
-
91
- $ time ruby primes.rb 8 >/dev/null
92
-
93
- real 0m5.893s
94
- user 0m1.023s
95
- sys 0m0.050s
96
-
97
- $ time ruby primes.rb 16 >/dev/null
98
-
99
- real 0m5.601s
100
- user 0m0.990s
101
- sys 0m0.043s
@@ -1,81 +0,0 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift(File.expand_path("../../lib", __FILE__))
4
- require 'amqp'
5
-
6
- module SimpleClient
7
- def process_frame(frame)
8
- case frame
9
- when Frame::Body
10
- EM.stop_event_loop
11
-
12
- when Frame::Method
13
- case method = frame.payload
14
- when Protocol::Connection::Start
15
- send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
16
- :product => 'AMQP',
17
- :information => 'http://github.com/ruby-amqp/amqp',
18
- :version => '0.1.0'},
19
- 'AMQPLAIN',
20
- {:LOGIN => 'guest',
21
- :PASSWORD => 'guest'},
22
- 'en_US')
23
-
24
- when Protocol::Connection::Tune
25
- send Protocol::Connection::TuneOk.new(:channel_max => 0,
26
- :frame_max => 131072,
27
- :heartbeat => 0)
28
-
29
- send Protocol::Connection::Open.new(:virtual_host => '/',
30
- :capabilities => '',
31
- :insist => false)
32
-
33
- when Protocol::Connection::OpenOk
34
- send Protocol::Channel::Open.new, :channel => 1
35
-
36
- when Protocol::Channel::OpenOk
37
- send Protocol::Access::Request.new(:realm => '/data',
38
- :read => true,
39
- :write => true,
40
- :active => true), :channel => 1
41
-
42
- when Protocol::Access::RequestOk
43
- @ticket = method.ticket
44
- send Protocol::Queue::Declare.new(:ticket => @ticket,
45
- :queue => '',
46
- :exclusive => false,
47
- :auto_delete => true), :channel => 1
48
-
49
- when Protocol::Queue::DeclareOk
50
- @queue = method.queue
51
- send Protocol::Queue::Bind.new(:ticket => @ticket,
52
- :queue => @queue,
53
- :exchange => '',
54
- :routing_key => 'test_route'), :channel => 1
55
-
56
- when Protocol::Queue::BindOk
57
- send Protocol::Basic::Consume.new(:ticket => @ticket,
58
- :queue => @queue,
59
- :no_local => false,
60
- :no_ack => true), :channel => 1
61
-
62
- when Protocol::Basic::ConsumeOk
63
- data = "this is a test!"
64
-
65
- send Protocol::Basic::Publish.new(:ticket => @ticket,
66
- :exchange => '',
67
- :routing_key => 'test_route'), :channel => 1
68
- send Protocol::Header.new(Protocol::Basic, data.length, :content_type => 'application/octet-stream',
69
- :delivery_mode => 1,
70
- :priority => 0), :channel => 1
71
- send Frame::Body.new(data), :channel => 1
72
- end
73
- end
74
- end
75
- end
76
-
77
- EM.run {
78
- AMQP.logging = true
79
- AMQP.client = SimpleClient
80
- AMQP.start
81
- }
@@ -1,67 +0,0 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift(File.expand_path("../../lib", __FILE__))
4
- require 'amqp'
5
-
6
- AMQP.start(:host => 'localhost') do |connection|
7
-
8
- # Send Connection.Close on Ctrl+C
9
- trap(:INT) do
10
- unless connection.closing?
11
- connection.close { exit! }
12
- end
13
- end
14
-
15
- def log(*args)
16
- p [ Time.now, *args ]
17
- end
18
-
19
- def publish_stock_prices
20
- mq = AMQP::Channel.new
21
- EM.add_periodic_timer(1) {
22
- puts
23
-
24
- { :appl => 170+rand(1000)/100.0,
25
- :msft => 22+rand(500)/100.0
26
- }.each do |stock, price|
27
- stock = "usd.#{stock}"
28
-
29
- log :publishing, stock, price
30
- mq.topic('stocks').publish(price, :key => stock)
31
- end
32
- }
33
- end
34
-
35
- def watch_appl_stock
36
- mq = AMQP::Channel.new
37
- mq.queue('apple stock').bind(mq.topic('stocks'), :key => 'usd.appl').subscribe { |price|
38
- log 'apple stock', price
39
- }
40
- end
41
-
42
- def watch_us_stocks
43
- mq = AMQP::Channel.new
44
- mq.queue('us stocks').bind(mq.topic('stocks'), :key => 'usd.*').subscribe { |info, price|
45
- log 'us stock', info.routing_key, price
46
- }
47
- end
48
-
49
- publish_stock_prices
50
- watch_appl_stock
51
- watch_us_stocks
52
-
53
- end
54
-
55
- __END__
56
-
57
- [Fri Aug 15 01:39:00 -0700 2008, :publishing, "usd.appl", 173.45]
58
- [Fri Aug 15 01:39:00 -0700 2008, :publishing, "usd.msft", 26.98]
59
- [Fri Aug 15 01:39:00 -0700 2008, "apple stock", "173.45"]
60
- [Fri Aug 15 01:39:00 -0700 2008, "us stock", "usd.appl", "173.45"]
61
- [Fri Aug 15 01:39:00 -0700 2008, "us stock", "usd.msft", "26.98"]
62
-
63
- [Fri Aug 15 01:39:01 -0700 2008, :publishing, "usd.appl", 179.72]
64
- [Fri Aug 15 01:39:01 -0700 2008, :publishing, "usd.msft", 26.56]
65
- [Fri Aug 15 01:39:01 -0700 2008, "apple stock", "179.72"]
66
- [Fri Aug 15 01:39:01 -0700 2008, "us stock", "usd.appl", "179.72"]
67
- [Fri Aug 15 01:39:01 -0700 2008, "us stock", "usd.msft", "26.56"]
@@ -1,24 +0,0 @@
1
- # encoding: utf-8
2
-
3
- source :rubygems
4
-
5
- # Use local clones if possible.
6
- # If you want to use your local copy, just symlink it to vendor.
7
- def custom_gem(name, options = Hash.new)
8
- local_path = File.expand_path("../vendor/#{name}", __FILE__)
9
- if File.exist?(local_path)
10
- gem name, options.merge(:path => local_path).delete_if { |key, _| [:git, :branch].include?(key) }
11
- else
12
- gem name, options
13
- end
14
- end
15
-
16
-
17
- gem "eventmachine", "~> 1.0.0.beta3"
18
-
19
- group(:test) do
20
- gem "rspec", ">=2.0.0"
21
- gem "rake"
22
- # gem "amqp-spec", :git => "git://github.com/ruby-amqp/amqp-spec.git", :branch => "master"
23
- custom_gem "evented-spec", :git => "git://github.com/ruby-amqp/evented-spec.git", :branch => "master"
24
- end
@@ -1,272 +0,0 @@
1
- # encoding: utf-8
2
-
3
- if [].map.respond_to? :with_index
4
- class Array #:nodoc:
5
- def enum_with_index
6
- each.with_index
7
- end
8
- end
9
- else
10
- require 'enumerator'
11
- end
12
-
13
- module AMQP
14
- class Buffer #:nodoc: all
15
- class Overflow < StandardError; end
16
- class InvalidType < StandardError; end
17
-
18
- def initialize(data = '')
19
- @data = data
20
- @pos = 0
21
- end
22
-
23
- attr_reader :pos
24
-
25
- def data
26
- @data.clone
27
- end
28
- alias :contents :data
29
- alias :to_s :data
30
-
31
- def << data
32
- @data << data.to_s
33
- self
34
- end
35
-
36
- def length
37
- @data.bytesize
38
- end
39
-
40
- def empty?
41
- pos == length
42
- end
43
-
44
- def rewind
45
- @pos = 0
46
- end
47
-
48
- def read_properties(*types)
49
- types.shift if types.first == :properties
50
-
51
- i = 0
52
- values = []
53
-
54
- while props = read(:short)
55
- (0..14).each do |n|
56
- # no more property types
57
- break unless types[i]
58
-
59
- # if flag is set
60
- if props & (1 << (15-n)) != 0
61
- if types[i] == :bit
62
- # bit values exist in flags only
63
- values << true
64
- else
65
- # save type name for later reading
66
- values << types[i]
67
- end
68
- else
69
- # property not set or is false bit
70
- values << (types[i] == :bit ? false : nil)
71
- end
72
-
73
- i += 1
74
- end
75
-
76
- # bit(0) == 0 means no more property flags
77
- break unless props & 1 == 1
78
- end
79
-
80
- values.map do |value|
81
- value.is_a?(Symbol) ? read(value) : value
82
- end
83
- end
84
-
85
- def read(*types)
86
- if types.first == :properties
87
- return read_properties(*types)
88
- end
89
-
90
- values = types.map do |type|
91
- case type
92
- when :octet
93
- _read(1, 'C')
94
- when :short
95
- _read(2, 'n')
96
- when :long
97
- _read(4, 'N')
98
- when :longlong
99
- upper, lower = _read(8, 'NN')
100
- upper << 32 | lower
101
- when :shortstr
102
- _read read(:octet)
103
- when :longstr
104
- _read read(:long)
105
- when :timestamp
106
- Time.at read(:longlong)
107
- when :table
108
- t = Hash.new
109
-
110
- table = Buffer.new(read(:longstr))
111
- until table.empty?
112
- key, type = table.read(:shortstr, :octet)
113
- key = key.intern
114
- t[key] ||= case type
115
- when 83 # 'S'
116
- table.read(:longstr)
117
- when 73 # 'I'
118
- table.read(:long)
119
- when 68 # 'D'
120
- exp = table.read(:octet)
121
- num = table.read(:long)
122
- num / 10.0**exp
123
- when 84 # 'T'
124
- table.read(:timestamp)
125
- when 70 # 'F'
126
- table.read(:table)
127
- end
128
- end
129
-
130
- t
131
- when :bit
132
- if (@bits ||= []).empty?
133
- val = read(:octet)
134
- @bits = (0..7).map { |i| (val & 1 << i) != 0 }
135
- end
136
-
137
- @bits.shift
138
- else
139
- raise InvalidType, "Cannot read data of type #{type}"
140
- end
141
- end
142
-
143
- types.size == 1 ? values.first : values
144
- end
145
-
146
- def write(type, data)
147
- case type
148
- when :octet
149
- _write(data, 'C')
150
- when :short
151
- _write(data, 'n')
152
- when :long
153
- _write(data, 'N')
154
- when :longlong
155
- lower = data & 0xffffffff
156
- upper = (data & ~0xffffffff) >> 32
157
- _write([upper, lower], 'NN')
158
- when :shortstr
159
- data = (data || '').to_s
160
- _write([data.bytesize, data], 'Ca*')
161
- when :longstr
162
- if data.is_a? Hash
163
- write(:table, data)
164
- else
165
- data = (data || '').to_s
166
- _write([data.bytesize, data], 'Na*')
167
- end
168
- when :timestamp
169
- write(:longlong, data.to_i)
170
- when :table
171
- data ||= {}
172
- write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
173
- table.write(:shortstr, key.to_s)
174
-
175
- case value
176
- when String
177
- table.write(:octet, 83) # 'S'
178
- table.write(:longstr, value.to_s)
179
- when Fixnum
180
- table.write(:octet, 73) # 'I'
181
- table.write(:long, value)
182
- when Float
183
- table.write(:octet, 68) # 'D'
184
- # XXX there's gotta be a better way to do this..
185
- exp = value.to_s.split('.').last.bytesize
186
- num = value * 10**exp
187
- table.write(:octet, exp)
188
- table.write(:long, num)
189
- when Time
190
- table.write(:octet, 84) # 'T'
191
- table.write(:timestamp, value)
192
- when Hash
193
- table.write(:octet, 70) # 'F'
194
- table.write(:table, value)
195
- end
196
-
197
- table
198
- end)
199
- when :bit
200
- [*data].to_enum(:each_slice, 8).each { |bits|
201
- write(:octet, bits.enum_with_index.inject(0) { |byte, (bit, i)|
202
- byte |= 1 << i if bit
203
- byte
204
- })
205
- }
206
- when :properties
207
- values = []
208
- data.enum_with_index.inject(0) do |short, ((type, value), i)|
209
- n = i % 15
210
- last = i+1 == data.size
211
-
212
- if (n == 0 and i != 0) or last
213
- if data.size > i+1
214
- short |= 1 << 0
215
- elsif last and value
216
- values << [type, value]
217
- short |= 1 << (15-n)
218
- end
219
-
220
- write(:short, short)
221
- short = 0
222
- end
223
-
224
- if value and !last
225
- values << [type, value]
226
- short |= 1 << (15-n)
227
- end
228
-
229
- short
230
- end
231
-
232
- values.each do |type, value|
233
- write(type, value) unless type == :bit
234
- end
235
- else
236
- raise InvalidType, "Cannot write data of type #{type}"
237
- end
238
-
239
- self
240
- end
241
-
242
- def extract
243
- begin
244
- cur_data, cur_pos = @data.clone, @pos
245
- yield self
246
- rescue Overflow
247
- @data, @pos = cur_data, cur_pos
248
- nil
249
- end
250
- end
251
-
252
- def _read(size, pack = nil)
253
- if @pos + size > length
254
- raise Overflow
255
- else
256
- data = @data[@pos, size]
257
- @data[@pos, size] = ''
258
- if pack
259
- data = data.unpack(pack)
260
- data = data.pop if data.size == 1
261
- end
262
- data
263
- end
264
- end
265
-
266
- def _write(data, pack = nil)
267
- data = [*data].pack(pack) if pack
268
- @data[@pos, 0] = data
269
- @pos += data.bytesize
270
- end
271
- end
272
- end