amq-protocol 0.0.1.pre → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  /*.gem
2
- .rvmrc
2
+ /.rvmrc
3
3
  tmp
4
4
  *.pyc
5
+ /vendor/bundle
6
+ /coverage
@@ -0,0 +1,7 @@
1
+ script: "bundle exec rspec spec"
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - ree
6
+ - rbx
7
+ - jruby
@@ -0,0 +1,2 @@
1
+ Jakub Šťastný aka Botanicus: 93 (5458 LOC)
2
+ Michael S. Klishin: 26 (792 LOC)
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ source "http://gemcutter.org"
4
+
5
+ group(:development) do
6
+ gem "nake", :platform => :ruby_19
7
+ gem "contributors", :platform => :ruby_19
8
+
9
+ # excludes Windows, Rubinius and JRuby
10
+ gem "perftools.rb", :platform => :mri
11
+ end
12
+
13
+ group(:test) do
14
+ gem "rspec", ">=2.0.0"
15
+ gem 'simplecov', :platform => :ruby_19
16
+ end
@@ -0,0 +1,31 @@
1
+ GEM
2
+ remote: http://gemcutter.org/
3
+ specs:
4
+ contributors (0.1.2)
5
+ diff-lcs (1.1.2)
6
+ nake (0.0.8)
7
+ term-ansicolor
8
+ perftools.rb (0.5.6)
9
+ rspec (2.5.0)
10
+ rspec-core (~> 2.5.0)
11
+ rspec-expectations (~> 2.5.0)
12
+ rspec-mocks (~> 2.5.0)
13
+ rspec-core (2.5.1)
14
+ rspec-expectations (2.5.0)
15
+ diff-lcs (~> 1.1.2)
16
+ rspec-mocks (2.5.0)
17
+ simplecov (0.4.2)
18
+ simplecov-html (~> 0.4.4)
19
+ simplecov-html (0.4.4)
20
+ term-ansicolor (1.0.5)
21
+
22
+ PLATFORMS
23
+ java
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ contributors
28
+ nake
29
+ perftools.rb
30
+ rspec (>= 2.0.0)
31
+ simplecov
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Jakub Šťastný aka Botanicus
1
+ Copyright (c) 2010 – 2011 Jakub Šťastný aka Botanicus
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,81 @@
1
+ # Profiling
2
+
3
+ To profile the encoding and decoding code use Aman Gupta's perftools.rb (https://github.com/tmm1/perftools.rb). It's really easy to use once you've set it up, and it generates graphs that make it pretty obvious where the hotspots are.
4
+
5
+ ## Setup
6
+
7
+ Start by installing perftools.rb:
8
+
9
+ gem install perftools.rb
10
+
11
+ I have it installed in my global gemset, which is handy since you can profile projects that uses any other gemset without having to add perftools.rb to its Gemfile:
12
+
13
+ rvm gemset use global
14
+ gem install perftools.rb
15
+
16
+ If you look at the README for perftools.rb the way to use it seems really icky, having to set environment variables and all that. I have the following script saved as `~/bin/profile` to encapsulate all that:
17
+
18
+ #!/bin/bash
19
+
20
+ profile_id=$(date +'%Y%m%d%H%M%S')
21
+ CPUPROFILE=/tmp/$profile_id RUBYOPT="-r`gem which perftools | tail -1`" $@
22
+ pprof.rb --pdf /tmp/$profile_id > /tmp/$profile_id.pdf
23
+ open /tmp/$profile_id.pdf
24
+
25
+ With this script you can prefix any command with "profile" and perftools.rb will be enabed, and a nice graph will be opened in Preview as soon as the program exits. This only works on OS X, obviously, but I'm sure it can be made to work on Linux too.
26
+
27
+ ## How to profile the code
28
+
29
+ Most of the time you want to profile a specific method or piece of code. The easiest thing to do is write a short script that runs that code over and over again. This will make sure that the profile graph isn't swamped by noise from other parts of the code, and running lots of iterations means that the numbers will be more stable. Perhaps most importantly it will also show the impact of the GC.
30
+
31
+ 100000.times do
32
+ strs.each do |str|
33
+ AMQ::Client::Framing::String::Frame.decode(str)
34
+ end
35
+ end
36
+
37
+ In the code above the `strs` variable holds a list of strings I've stolen from the amqp-benchmarks repository. I call the `decode` method of the `AMQ::Client::Framing::String::Frame` class over and over again and after 100K iterations (this takes around ten seconds to run) I get a graph that shows me which parts of the code take most time.
38
+
39
+ This is how you run the script above (assuming you fill in the `strs` list and add the necessary require's -- and that you've followed the instructions at the top of this document -- and that you've named it "benchmark_script.rb"):
40
+
41
+ profile ruby benchmark_script.rb
42
+
43
+ It will run for a few seconds and then open a graph. Next I'll explain how to interpret that graph.
44
+
45
+ ## Interpreting the profile graph
46
+
47
+ The graph generated by perftools.rb shows all the methods that have run, which methods they call, how much time was spent in each method, and a lot of other very interesting things.
48
+
49
+ At the top left of the picture it says mow many samples were taken. perftools.rb samples 100 times per second, so 1257 samples means that it ran for 12.57 seconds. The number of samples per second can be tweaked, but I've never had any reason to. The next three numbers just tell you that there are methods that were called not very often, and that these are not shown in the graph, to make it easier to read.
50
+
51
+ There are usually two, and sometimes three, nodes which don't have any arrows pointing into them. The first is the entry point into your application, the second the GC, and sometimes there is a third which usually is RubyGems or Bundler. There is no reason there can't be more top level nodes, but those are the ones I've seen.
52
+
53
+ Each node represents a method. The label is the class and method name, in classic Ruby style: ClassName#instance_method_name. Unfortunately it doesn't handle class methods very well, so all end up ass Class#method_name. This is bad for us, since most methods in amq-protocol are class methods. It also means that all class methods of the same name, regardless of which class then belong are represented as one node. That's really bad for us, since a lot of methods that we want to profile are called "decode" and "encode".
54
+
55
+ The numbers below the label are the self time and cumulative time measurements, first as samples and then, in parentheses as percentages of the whole. Self time means the time spent in the method, excluding other methods, and cumulative time means the total time spent in the method, and all methods below it in the call stack. In other words, if method a calls b and c, the cumulative time for a is its self time, plus the cumulative time of b and c (which is the self time of each of those, plus the cumulative time of the methods they call, and so on).
56
+
57
+ The number by the arrow from one node to another is the number of samples that saw the source node call the destination node. Looking at the numbers by the arrows into a node you can see roughly which method calls the method represented by that node the most.
58
+
59
+ That's more or less it. I find the percentages most useful to work with, as the number of samples can vary a bit from time to time, its basically the same problem as using `time` to benchmark. Percentages are often much more stable.
60
+
61
+ In the next section I'll explain how to act on the information that the graph gives.
62
+
63
+ ## Optimizing
64
+
65
+ The first thing to look at is the GC node. It will always be high (this is Ruby after all), but if it's above 20% this is what you should start with. The reason the GC takes so much time is because the code creates too many objects. It's not always obvious what is creating so many objects, but read the next section for how to remove the worst offenders. When you've picked the low hanging fruit and still have a high GC percentage, go over your code and try to see if there are places where you can memoize, cache, etc. If your objects are immutable you should never have to create two identical objects (on the other hand, it's easy to leak memory if you cache too much).
66
+
67
+ The next thing to look at is the node with the highest self time percentage. This is your first hotspot. Apply all the tricks in the next section and see if it makes any difference. Sometimes hotspots are hotspots because of design rather than slow code. If a method is called many, many times you may not be able to optimize it further (but try, each tiny thing will make a huge difference).
68
+
69
+ The third thing to look for are methods will high cumulative time. If the methods they call don't look too bad and can be optimized first, you may be able to rewrite the method to call them fewer times by caching values, for example.
70
+
71
+ While optimizing run the profiler for each change to make sure you're going in the right direction. It's not uncommon for something that you think should be faster to be slower because of something you couldn't foresee or some unexpected side effect. When the method you're working on is no longer the worst offender, move on to the next, and iterate.
72
+
73
+ ## Tips
74
+
75
+ These are some general tips I've gathered while optimizing Ruby code:
76
+
77
+ * Avoid string literals. In contrast to Java, strings in Ruby are mutable, so each literal string is a separate object (otherwise 'a'.upcase! would have very strange side effects). Move string literals to constants.
78
+ * Avoid range literals. Just as with strings, ranges are objects and they are created every time they are encountered. Ranges are most often used to extract parts of a string, like `str[1..5]`. This can always be rewritten as a slice instead: `str[1, 4]` (the second argument is a length, not an index, so make sure you get the right value). Code like `data[offset..(offset + 4)]` is especially wasteful since it would be much clearer with the slice syntax: `data[offset, 5]`.
79
+ * Avoid regular expressions. They are convenient, but for code that will be called thousands of times you need to rewrite them using String#index and String#slice/[]. Make sure that your rewrite doesn't create a lot of temporary string objects.
80
+ * When reading files don't read line by line, and absolutely never byte by byte. It's much faster to read a big chunk (as in megabytes) and use String#split to split into lines. The same goes for writing. Buffer the lines and write them in one big chunk. Avoid IO#tell and #seek unless you're trying to read a few bytes out of a gigabyte file.
81
+ * Never ever, ever, use Date or DateTime, or require 'date' (to get Time.parse, for example). Date is so slow that it's not even funny. If you use Date.parse you can be sure that whatever else you do that method will account for at least half of the running time. If you really, really need date parsing and date perform date calculations, use the home_run gem, but for almost everything you can use the basic methods on Time (but those aren't cheap either).
@@ -1,32 +1,49 @@
1
- h1. About
1
+ h1. About amq-protocol.
2
2
 
3
- This is an AMQP parser for AMQP 0.9.1. It isn't an AMQP client, just the parser, so if you want to write your own AMQP client without digging into the protocol, this gem can help you with that.
3
+ amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It is not an
4
+ AMQP client: amq-protocol only handles serialization and deserialization.
5
+ If you want to write your own AMQP client, this gem can help you with that.
4
6
 
5
- h1. Usage
7
+ h2. How does amq-protocol relate to amqp gem, amq-client and libraries like bunny?
6
8
 
7
- <pre>
8
- gem install amqp-parser
9
- </pre>
9
+ See "this page about AMQP gems family":https://github.com/ruby-amqp/amq-client/blob/master/README.textile
10
10
 
11
- Check the @examples@ directory for some examples.
12
11
 
13
- h1. Development
12
+ h2. Installation
13
+
14
+ If you use Bundler and want to use the very latest version, add this to your Gemfile:
14
15
 
15
16
  <pre>
16
- git clone git://github.com/botanicus/amq-protocol.git --recursive
17
+ gem 'amq-protocol', :git => 'https://github.com/ruby-amqp/amq-protocol.git'
17
18
  </pre>
18
19
 
19
- Or, if you are on an older version of Git:
20
+
21
+ h2. Development
22
+
23
+ h3. Nightly Builds
24
+
25
+ You can always find nightly builds at "gems.101ideas.cz":http://gems.101ideas.cz.
26
+
27
+ You can install them thusly:
20
28
 
21
29
  <pre>
22
- git clone git://github.com/botanicus/amq-protocol.git
23
- git submodule update --init
30
+ wget http://gems.101ideas.cz/amq-protocol-nightly.gem
31
+ gem install amq-protocol-nightly.gem
24
32
  </pre>
25
33
 
26
- If you want to change some code, don't do so in @lib/amq/protocol.rb@, but in @protocol.rb.pytemplate@ which is a template file for @codegen.py@. You can re-generate the code by @./tasks.rb generate@.
34
+ h3. Contributing
35
+
36
+ If you want to change some code, don't edit @lib/amq/protocol/client.rb@ —
37
+ that file is generated from @protocol.rb.pytemplate@
38
+ which is a template file used by @codegen.py@. You can re-generate by running
39
+ <pre>nake ./tasks.rb generate</pre>
40
+ Please note that nake gem only runs on Ruby 1.9.2.
27
41
 
28
- h1. Links
42
+ h2. Links
29
43
 
30
- * "RDoc.info API Docs":http://rdoc.info/github/botanicus/amq-protocol/master/frames
31
- * "Examples":https://github.com/botanicus/amq-protocol/tree/master/examples/
32
- * "Bug reporting":http://github.com/botanicus/amq-protocol/issues
44
+ * "API Documentation":http://rdoc.info/github/ruby-amqp/amq-protocol/master/frames
45
+ * "Examples":https://github.com/ruby-amqp/amq-protocol/tree/master/examples/
46
+ * "Jabber room for contributors":xmpp://amqp-dev@conf.netlab.cz
47
+ * "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp
48
+ * "Issue tracker":http://github.com/ruby-amqp/amq-protocol/issues
49
+ * "Continous integration server":http://travis-ci.org/#!/ruby-amqp/amq-protocol
data/TODO.todo CHANGED
@@ -1,3 +1,27 @@
1
1
  - YARD documentation
2
2
  - benchmarks
3
3
  - optimisations (see puka)
4
+ - Don't let users set the deprecated arguments.
5
+
6
+ OpenOk
7
+ if $DEBUG
8
+ if data[offset..(offset + 1)] != "\x00"
9
+ raise BadResponseError.new("known_hosts", "\x00", data)
10
+ end
11
+ end
12
+
13
+
14
+ DO NOT GENERATE THE FOLLOWING:
15
+ attr_reader :known_hosts
16
+ def initialize(known_hosts)
17
+ @known_hosts = known_hosts
18
+ end
19
+
20
+ arguments = Basic::Publish.method(:encode).parameters.map { |array| array.last }
21
+ arguments.should include(:frame_size)
22
+
23
+ arguments = Connection::Open.method(:encode).map { |array| array.last }
24
+ arguments.should_not include(:capabilities)
25
+ arguments.should_not include(:insist)
26
+
27
+ generate the spec file(s)?
File without changes
@@ -3,30 +3,35 @@
3
3
 
4
4
  require "base64"
5
5
 
6
+ require File.expand_path("../lib/amq/protocol/version", __FILE__)
7
+
6
8
  Gem::Specification.new do |s|
7
9
  s.name = "amq-protocol"
8
- s.version = "0.0.1"
9
- s.authors = ["Jakub Stastny"]
10
- s.homepage = "http://github.com/botanicus/amq-protocol"
10
+ s.version = AMQ::Protocol::VERSION
11
+ s.authors = ["Jakub Stastny", "Michael S. Klishin", "Theo Hultberg", "Mark Abramov"]
12
+ s.homepage = "http://github.com/ruby-amqp/amq-protocol"
11
13
  s.summary = "AMQP 0.9.1 encoder & decoder."
12
- s.description = "This is an AMQP encoder & decoder for AMQP 0.9.1. It isn't an AMQP client, just the parser, so if you want to write your own AMQP client without digging into the protocol, this gem can help you with that."
14
+ s.description = <<-DESC
15
+ amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It is not an
16
+ AMQP client: amq-protocol only handles serialization and deserialization.
17
+ If you want to write your own AMQP client, this gem can help you with that.
18
+ DESC
13
19
  s.cert_chain = nil
14
- s.email = Base64.decode64("c3Rhc3RueUAxMDFpZGVhcy5jeg==\n")
20
+ s.email = ["bWljaGFlbEBub3ZlbWJlcmFpbi5jb20=\n", "c3Rhc3RueUAxMDFpZGVhcy5jeg==\n"].map { |i| Base64.decode64(i) }
15
21
  s.has_rdoc = true
16
22
 
17
23
  # files
18
- s.files = `git ls-files`.split("\n")
24
+ s.files = `git ls-files`.split("\n").reject { |file| file =~ /^vendor\// }
19
25
  s.require_paths = ["lib"]
20
26
 
21
- # Ruby version
22
- s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
27
+ s.extra_rdoc_files = ["README.textile"] + Dir.glob("doc/*")
28
+
23
29
 
24
30
  begin
25
31
  require "changelog"
26
- rescue LoadError
27
- warn "You have to have changelog gem installed for post install message"
28
- else
29
32
  s.post_install_message = CHANGELOG.new.version_changes
33
+ rescue LoadError
34
+ # warn "You have to have changelog gem installed for post install message"
30
35
  end
31
36
 
32
37
  # RubyForge
@@ -1 +1 @@
1
- {"tx": {"select-ok": ["client"], "rollback": ["server"], "commit": ["server"], "rollback-ok": ["client"], "select": ["server"], "commit-ok": ["client"]}, "exchange": {"delete-ok": ["client"], "declare-ok": ["client"], "declare": ["server"], "delete": ["server"], "bind": ["server"], "bind-ok": ["client"], "unbind": ["server"], "unbind-ok": ["client"]}, "queue": {"unbind": ["server"], "unbind-ok": ["client"], "purge-ok": ["client"], "bind": ["server"], "purge": ["server"], "declare-ok": ["client"], "delete-ok": ["client"], "delete": ["server"], "declare": ["server"], "bind-ok": ["client"]}, "connection": {"secure": ["client"], "secure-ok": ["server"], "open-ok": ["client"], "close-ok": ["client", "server"], "start": ["client"], "tune": ["client"], "start-ok": ["server"], "close": ["client", "server"], "open": ["server"], "tune-ok": ["server"]}, "basic": {"qos": ["server"], "consume": ["server"], "reject": ["server"], "get": ["server"], "ack": ["server"], "get-ok": ["client"], "consume-ok": ["client"], "deliver": ["client"], "recover-ok": ["client"], "publish": ["server"], "cancel": ["server"], "recover-async": ["server"], "get-empty": ["client"], "qos-ok": ["client"], "return": ["client"], "recover": ["server"], "cancel-ok": ["client"]}, "channel": {"flow-ok": ["server", "client"], "flow": ["server", "client"], "open-ok": ["client"], "close-ok": ["client", "server"], "close": ["client", "server"], "open": ["server"]}}
1
+ {"tx": {"select-ok": ["client"], "rollback": ["server"], "commit": ["server"], "rollback-ok": ["client"], "select": ["server"], "commit-ok": ["client"]}, "exchange": {"delete-ok": ["client"], "declare-ok": ["client"], "declare": ["server"], "delete": ["server"], "bind": ["server"], "bind-ok": ["client"], "unbind": ["server"], "unbind-ok": ["client"]}, "queue": {"unbind": ["server"], "unbind-ok": ["client"], "purge-ok": ["client"], "bind": ["server"], "purge": ["server"], "declare-ok": ["client"], "delete-ok": ["client"], "delete": ["server"], "declare": ["server"], "bind-ok": ["client"]}, "connection": {"secure": ["client"], "secure-ok": ["server"], "open-ok": ["client"], "close-ok": ["client", "server"], "start": ["client"], "tune": ["client"], "start-ok": ["server"], "close": ["client", "server"], "open": ["server"], "tune-ok": ["server"]}, "basic": {"qos": ["server"], "consume": ["server"], "reject": ["server"], "get": ["server"], "ack": ["client", "server"], "get-ok": ["client"], "consume-ok": ["client"], "deliver": ["client"], "recover-ok": ["client"], "publish": ["server"], "cancel": ["server"], "recover-async": ["server"], "get-empty": ["client"], "qos-ok": ["client"], "return": ["client"], "recover": ["server"], "cancel-ok": ["client"]}, "channel": {"flow-ok": ["server", "client"], "flow": ["server", "client"], "open-ok": ["client"], "close-ok": ["client", "server"], "close": ["client", "server"], "open": ["server"]}}
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ echo -e "\n\n==== Setup ===="
4
+ source /etc/profile
5
+ git fetch && git reset origin/master --hard
6
+
7
+ echo -e "\n\n==== Ruby 1.9.2 Head ===="
8
+ rvm use 1.9.2-head@ruby-amqp
9
+ gem install bundler --no-ri --no-rdoc
10
+ bundle install --path vendor/bundle/1.9.2 --without development; echo
11
+ # bundle update --without development; echo
12
+ bundle exec rspec spec
13
+ return_status=$?
14
+
15
+ echo -e "\n\n==== Ruby 1.8.7 ===="
16
+ rvm use 1.8.7@ruby-amqp
17
+ gem install bundler --no-ri --no-rdoc
18
+ bundle install --path vendor/bundle/1.8.7 --without development; echo
19
+ bundle update; echo
20
+ bundle exec rspec spec
21
+ return_status=$(expr $return_status + $?)
22
+
23
+ test $return_status -eq 0
data/codegen.py CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
+ # Documentation for Mako templates:
5
+ # http://www.makotemplates.org/docs/syntax.html
6
+
4
7
  import os, sys, re
5
8
 
6
9
  sys.path.append(os.path.join("vendor", "rabbitmq-codegen"))
@@ -14,10 +17,9 @@ except ImportError:
14
17
 
15
18
  # main class
16
19
  class AmqpSpecObject(AmqpSpec):
17
- IGNORED_CLASSES = ["access", "tx"]
20
+ IGNORED_CLASSES = ["access"]
18
21
  IGNORED_FIELDS = {
19
22
  'ticket': 0,
20
- 'nowait': 0,
21
23
  'capabilities': '',
22
24
  'insist' : 0,
23
25
  'out_of_band': '',
@@ -30,10 +32,10 @@ class AmqpSpecObject(AmqpSpec):
30
32
  def extend_field(field):
31
33
  field.ruby_name = re.sub("[- ]", "_", field.name)
32
34
  field.type = self.resolveDomain(field.domain)
33
- field.banned = bool(field.name in self.__class__.IGNORED_FIELDS)
35
+ field.ignored = bool(field.name in self.__class__.IGNORED_FIELDS) # I. e. deprecated
34
36
 
35
37
  for klass in self.classes:
36
- klass.banned = bool(klass.name in self.__class__.IGNORED_CLASSES)
38
+ klass.ignored = bool(klass.name in self.__class__.IGNORED_CLASSES)
37
39
 
38
40
  for field in klass.fields:
39
41
  extend_field(field)
@@ -42,7 +44,7 @@ class AmqpSpecObject(AmqpSpec):
42
44
  for field in method.arguments:
43
45
  extend_field(field)
44
46
 
45
- self.classes = filter(lambda klass: not klass.banned, self.classes)
47
+ self.classes = filter(lambda klass: not klass.ignored, self.classes)
46
48
 
47
49
  # I know, I'm a bad, bad boy, but come on guys,
48
50
  # monkey-patching is just handy for this case.
@@ -73,16 +75,44 @@ def accepted_by(self, *receivers):
73
75
 
74
76
  AmqpMethod.accepted_by = accepted_by
75
77
 
78
+ def convert_value_to_ruby(value):
79
+ values = {None: "nil", False: "false", True: "true", "": "EMPTY_STRING"}
80
+
81
+ try:
82
+ return values[value]
83
+ except:
84
+ return value.__repr__()
85
+
76
86
  def convert_to_ruby(field):
77
87
  name = re.sub("-", "_", field.name) # TODO: use ruby_name
78
- if field.defaultvalue == None:
79
- return "%s = nil" % (name,)
80
- elif field.defaultvalue == False:
81
- return "%s = false" % (name,)
82
- elif field.defaultvalue == True:
83
- return "%s = true" % (name,)
88
+ if name == "ticket":
89
+ return "%s = %s" % (name, field.defaultvalue) # we want to keep it as an int, not as a boolean
84
90
  else:
85
- return "%s = %r" % (name, field.defaultvalue)
91
+ return "%s = %s" % (name, convert_value_to_ruby(field.defaultvalue))
92
+
93
+ def not_ignored_args(self):
94
+ if self.hasContent:
95
+ return ["payload", "user_headers"] + map(lambda argument: argument.ruby_name, filter(lambda argument: not argument.ignored, self.arguments)) + ["frame_size"]
96
+ else:
97
+ return map(lambda argument: argument.ruby_name, filter(lambda argument: not argument.ignored, self.arguments))
98
+
99
+ AmqpMethod.not_ignored_args = not_ignored_args
100
+
101
+ def ignored_args(self):
102
+ return filter(lambda argument: argument.ignored, self.arguments)
103
+
104
+ AmqpMethod.ignored_args = ignored_args
105
+
106
+ # helpers
107
+ def to_ruby_name(name):
108
+ return re.sub("[- ]", "_", name)
109
+
110
+ def to_ruby_class_name(name):
111
+ parts = re.split("[- ]", name)
112
+ ruby_class_name = ""
113
+ for part in parts:
114
+ ruby_class_name = ruby_class_name + part[0].upper() + part[1:].lower()
115
+ return ruby_class_name
86
116
 
87
117
  def params(self):
88
118
  buffer = []
@@ -113,9 +143,13 @@ def render(path, **context):
113
143
  template = Template(file.read())
114
144
  return template.render(**context)
115
145
 
116
- def main(json_spec_path):
117
- spec = AmqpSpecObject(json_spec_path)
118
- print render("protocol.rb.pytemplate", spec = spec)
146
+ def generateMain(type):
147
+ def main(json_spec_path):
148
+ spec = AmqpSpecObject(json_spec_path)
149
+ spec.type = type
150
+ print render("protocol.rb.pytemplate", spec = spec)
151
+
152
+ return main
119
153
 
120
154
  if __name__ == "__main__":
121
- do_main_dict({"spec": main})
155
+ do_main_dict({"client": generateMain("client"), "server": generateMain("server")})
@@ -4,72 +4,124 @@ def genSingleEncode(spec, cValue, unresolved_domain):
4
4
  buffer = []
5
5
  type = spec.resolveDomain(unresolved_domain)
6
6
  if type == 'shortstr':
7
- buffer.append("pieces << %s.bytesize.chr" % (cValue,))
8
- buffer.append("pieces << %s" % (cValue,))
7
+ buffer.append("buffer << %s.bytesize.chr" % (cValue,))
8
+ buffer.append("buffer << %s" % (cValue,))
9
9
  elif type == 'longstr':
10
- buffer.append("pieces << [%s.bytesize].pack('N')" % (cValue,))
11
- buffer.append("pieces << %s" % (cValue,))
10
+ buffer.append("buffer << [%s.bytesize].pack(PACK_UINT32)" % (cValue,))
11
+ buffer.append("buffer << %s" % (cValue,))
12
12
  elif type == 'octet':
13
- buffer.append("pieces << [%s].pack('B')" % (cValue,))
13
+ buffer.append("buffer << [%s].pack(PACK_CHAR)" % (cValue,))
14
14
  elif type == 'short':
15
- buffer.append("pieces << [%s].pack('n')" % (cValue,))
15
+ buffer.append("buffer << [%s].pack(PACK_UINT16)" % (cValue,))
16
16
  elif type == 'long':
17
- buffer.append("pieces << [%s].pack('N')" % (cValue,))
17
+ buffer.append("buffer << [%s].pack(PACK_UINT32)" % (cValue,))
18
18
  elif type == 'longlong':
19
- buffer.append("pieces << [%s].pack('>Q')" % (cValue,))
19
+ buffer.append("buffer << AMQ::Hacks.pack_64_big_endian(%s)" % (cValue,))
20
20
  elif type == 'timestamp':
21
- buffer.append("pieces << [%s].pack('>Q')" % (cValue,))
21
+ buffer.append("buffer << AMQ::Hacks.pack_64_big_endian(%s)" % (cValue,))
22
22
  elif type == 'bit':
23
23
  raise "Can't encode bit in genSingleEncode"
24
24
  elif type == 'table':
25
- buffer.append("pieces << AMQ::Protocol::Table.encode(%s)" % (cValue,))
25
+ buffer.append("buffer << AMQ::Protocol::Table.encode(%s)" % (cValue,))
26
26
  else:
27
27
  raise "Illegal domain in genSingleEncode", type
28
28
 
29
29
  return buffer
30
30
 
31
- def genSingleDecode(spec, cLvalue, unresolved_domain):
31
+ def genSingleDecode(spec, field):
32
+ cLvalue = field.ruby_name
33
+ unresolved_domain = field.domain
34
+
35
+ if cLvalue == "known_hosts":
36
+ import sys
37
+ print >> sys.stderr, field, field.ignored
38
+
32
39
  type = spec.resolveDomain(unresolved_domain)
33
40
  buffer = []
34
41
  if type == 'shortstr':
35
- buffer.append("length = data[offset..(offset + 1)].unpack('N')[0]")
42
+ buffer.append("length = data[offset, 1].unpack(PACK_CHAR).first")
36
43
  buffer.append("offset += 1")
37
- buffer.append("%s = data[offset..(offset + length)]" % (cLvalue,))
44
+ buffer.append("%s = data[offset, length]" % (cLvalue,))
38
45
  buffer.append("offset += length")
39
46
  elif type == 'longstr':
40
- buffer.append("length = data[offset..(offset + 4)].unpack('N').first")
47
+ buffer.append("length = data[offset, 4].unpack(PACK_UINT32).first")
41
48
  buffer.append("offset += 4")
42
- buffer.append("%s = data[offset..(offset + length)]" % (cLvalue,))
49
+ buffer.append("%s = data[offset, length]" % (cLvalue,))
43
50
  buffer.append("offset += length")
44
51
  elif type == 'octet':
45
- buffer.append("%s = data[offset...(offset + 1)].unpack('c').first" % (cLvalue,))
52
+ buffer.append("%s = data[offset, 1].unpack(PACK_CHAR).first" % (cLvalue,))
46
53
  buffer.append("offset += 1")
47
54
  elif type == 'short':
48
- buffer.append("%s = data[offset..(offset + 2)].unpack('n').first" % (cLvalue,))
55
+ buffer.append("%s = data[offset, 2].unpack(PACK_UINT16).first" % (cLvalue,))
49
56
  buffer.append("offset += 2")
50
57
  elif type == 'long':
51
- buffer.append("%s = data[offset..(offset + 4)].unpack('N').first" % (cLvalue,))
58
+ buffer.append("%s = data[offset, 4].unpack(PACK_UINT32).first" % (cLvalue,))
52
59
  buffer.append("offset += 4")
53
60
  elif type == 'longlong':
54
- buffer.append("%s = data[offset..(offset + 8)].unpack('N2').first" % (cLvalue,))
61
+ buffer.append("%s = AMQ::Hacks.unpack_64_big_endian(data[offset, 8]).first" % (cLvalue,))
55
62
  buffer.append("offset += 8")
56
63
  elif type == 'timestamp':
57
- buffer.append("%s = data[offset..(offset + 8)].unpack('N2').first" % (cLvalue,))
64
+ buffer.append("%s = data[offset, 8].unpack(PACK_UINT32_X2).first" % (cLvalue,))
58
65
  buffer.append("offset += 8")
59
66
  elif type == 'bit':
60
67
  raise "Can't decode bit in genSingleDecode"
61
68
  elif type == 'table':
62
- buffer.append("table_length = Table.length(data[offset..(offset + 4)])")
63
- buffer.append("%s = Table.decode(data[offset..table_length])" % (cLvalue,))
69
+ buffer.append("table_length = Table.length(data[offset, 4])")
70
+ buffer.append("%s = Table.decode(data[offset, table_length + 4])" % (cLvalue,))
71
+ buffer.append("offset += table_length + 4")
72
+ else:
73
+ raise StandardError("Illegal domain '" + type + "' in genSingleDecode")
74
+
75
+ return buffer
76
+
77
+
78
+
79
+ def genSingleSimpleDecode(spec, field):
80
+ cLvalue = field.ruby_name
81
+ unresolved_domain = field.domain
82
+
83
+ if cLvalue == "known_hosts":
84
+ import sys
85
+ print >> sys.stderr, field, field.ignored
86
+
87
+ type = spec.resolveDomain(unresolved_domain)
88
+ buffer = []
89
+ if type == 'shortstr':
90
+ # buffer.append("length = data.unpack(PACK_CHAR)[0]")
91
+ # buffer.append("result = data[offset..-1]")
92
+ # buffer.append("raise 'Bad size: #{length} expected, got #{result.bytesize}' if result.bytesize != length")
93
+ # buffer.append("result")
94
+ buffer.append("data")
95
+ elif type == 'longstr':
96
+ # buffer.append("length = data.unpack(PACK_CHAR)[0]")
97
+ # buffer.append("result = data[offset..-1]")
98
+ # buffer.append("raise 'Bad size: #{length} expected, got #{result.bytesize}' if result.bytesize != length")
99
+ # buffer.append("result")
100
+ buffer.append("data")
101
+ elif type == 'octet':
102
+ buffer.append("data.unpack(PACK_CHAR).first")
103
+ elif type == 'short':
104
+ buffer.append("data.unpack(PACK_UINT16).first")
105
+ elif type == 'long':
106
+ buffer.append("data.unpack(PACK_UINT32).first")
107
+ elif type == 'longlong':
108
+ buffer.append("AMQ::Hacks.unpack_64_big_endian(data).first")
109
+ elif type == 'timestamp':
110
+ buffer.append("Time.at(data.unpack(PACK_UINT32_X2).last)")
111
+ elif type == 'bit':
112
+ raise "Can't decode bit in genSingleDecode"
113
+ elif type == 'table':
114
+ buffer.append("Table.decode(data)")
64
115
  else:
65
- raise "Illegal domain in genSingleDecode", type
116
+ raise StandardError("Illegal domain '" + type + "' in genSingleSimpleDecode")
66
117
 
67
118
  return buffer
68
119
 
120
+
69
121
  def genEncodeMethodDefinition(spec, m):
70
122
  def finishBits():
71
123
  if bit_index is not None:
72
- return "pieces << [bit_buffer].pack('B')"
124
+ buffer.append("buffer << [bit_buffer].pack(PACK_CHAR)")
73
125
 
74
126
  bit_index = None
75
127
  buffer = []
@@ -103,11 +155,14 @@ def genDecodeMethodDefinition(spec, m):
103
155
  if bitindex >= 8:
104
156
  bitindex = 0
105
157
  if bitindex == 0:
106
- buffer.append("bit_buffer = data[offset..(offset + 1)].unpack('c').first")
158
+ buffer.append("bit_buffer = data[offset, 1].unpack(PACK_CHAR).first")
107
159
  buffer.append("offset += 1")
108
160
  buffer.append("%s = (bit_buffer & (1 << %d)) != 0" % (f.ruby_name, bitindex))
161
+ #### TODO: ADD bitindex TO THE buffer
162
+ else:
163
+ buffer.append("%s = (bit_buffer & (1 << %d)) != 0" % (f.ruby_name, bitindex))
109
164
  bitindex = bitindex + 1
110
165
  else:
111
166
  bitindex = None
112
- buffer += genSingleDecode(spec, f.ruby_name, f.domain)
167
+ buffer += genSingleDecode(spec, f)
113
168
  return buffer