beefcake-spanx 0.3.4.1
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.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +16 -0
- data/LICENSE +22 -0
- data/README.md +116 -0
- data/Rakefile +2 -0
- data/beefcake.gemspec +22 -0
- data/bench/simple.rb +90 -0
- data/bin/protoc-gen-beefcake +11 -0
- data/dat/code_generator_request.dat +0 -0
- data/lib/beefcake.rb +246 -0
- data/lib/beefcake/buffer.rb +2 -0
- data/lib/beefcake/buffer/base.rb +98 -0
- data/lib/beefcake/buffer/decode.rb +107 -0
- data/lib/beefcake/buffer/encode.rb +116 -0
- data/lib/beefcake/generator.rb +288 -0
- data/lib/beefcake/version.rb +3 -0
- data/test/buffer_decode_test.rb +114 -0
- data/test/buffer_encode_test.rb +222 -0
- data/test/buffer_test.rb +44 -0
- data/test/generator_test.rb +51 -0
- data/test/message_test.rb +371 -0
- metadata +92 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Blake Mizerany
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# Beefcake (A sane Google Protocol Buffers library for Ruby)
|
2
|
+
## It's all about being Buf; ProtoBuf.
|
3
|
+
|
4
|
+
# Install
|
5
|
+
|
6
|
+
$ gem install beefcake
|
7
|
+
|
8
|
+
# Example
|
9
|
+
|
10
|
+
require 'beefcake'
|
11
|
+
|
12
|
+
class Variety
|
13
|
+
include Beefcake::Message
|
14
|
+
|
15
|
+
# Required
|
16
|
+
required :x, :int32, 1
|
17
|
+
required :y, :int32, 2
|
18
|
+
|
19
|
+
# Optional
|
20
|
+
optional :tag, :string, 3
|
21
|
+
|
22
|
+
# Repeated
|
23
|
+
repeated :ary, :fixed64, 4
|
24
|
+
repeated :pary, :fixed64, 5, :packed => true
|
25
|
+
|
26
|
+
# Enums - Simply use a Module (NOTE: defaults are optional)
|
27
|
+
module Foonum
|
28
|
+
A = 1
|
29
|
+
B = 2
|
30
|
+
end
|
31
|
+
|
32
|
+
# As per the spec, defaults are only set at the end
|
33
|
+
# of decoding a message, not on object creation.
|
34
|
+
optional :foo, Foonum, 6, :default => Foonum::B
|
35
|
+
end
|
36
|
+
|
37
|
+
x = Variety.new :x => 1, :y => 2
|
38
|
+
# or
|
39
|
+
x = Variety.new
|
40
|
+
x.x = 1
|
41
|
+
x.y = 2
|
42
|
+
|
43
|
+
## Encoding
|
44
|
+
|
45
|
+
Any object responding to `<<` can accept encoding
|
46
|
+
|
47
|
+
s = ""
|
48
|
+
x.encode(s)
|
49
|
+
p [:s, s]
|
50
|
+
# or (because encode returns the string/stream)
|
51
|
+
p [:s, x.encode]
|
52
|
+
# or
|
53
|
+
open("x.dat") do |f|
|
54
|
+
x.encode(f)
|
55
|
+
end
|
56
|
+
|
57
|
+
# decode
|
58
|
+
encoded = x.encode
|
59
|
+
decoded = Variety.decode(encoded)
|
60
|
+
p [:x, decoded]
|
61
|
+
|
62
|
+
# decode merge
|
63
|
+
Variety.decoded(more_data, decoded)
|
64
|
+
|
65
|
+
# Why?
|
66
|
+
|
67
|
+
Ruby deserves and needs first-class ProtoBuf support.
|
68
|
+
Other libs didn't feel very "Ruby" to me and were hard to parse.
|
69
|
+
|
70
|
+
# Generate code from `.proto` file
|
71
|
+
|
72
|
+
$ protoc --beefcake_out output/path -I path/to/proto/files/dir path/to/proto/file
|
73
|
+
|
74
|
+
You can set the BEEFCAKE_NAMESPACE variable to generate the classes under a
|
75
|
+
desired namespace. (i.e. App::Foo::Bar)
|
76
|
+
|
77
|
+
# Misc
|
78
|
+
|
79
|
+
This library was built with EventMachine in mind. Not just blocking-IO.
|
80
|
+
|
81
|
+
# Dev
|
82
|
+
|
83
|
+
Source:
|
84
|
+
|
85
|
+
$ git clone git://github.com/bmizerany/beefcake
|
86
|
+
|
87
|
+
## Testing:
|
88
|
+
|
89
|
+
$ gem install turn
|
90
|
+
$ cd /path/to/beefcake
|
91
|
+
$ turn
|
92
|
+
|
93
|
+
## VMs:
|
94
|
+
|
95
|
+
Currently Beefcake is tested and working on:
|
96
|
+
|
97
|
+
* Ruby 1.8.6
|
98
|
+
* Ruby 1.8.7
|
99
|
+
* Ruby 1.9.2
|
100
|
+
* JRuby 1.5.6
|
101
|
+
* Rubinius edge
|
102
|
+
|
103
|
+
## Future
|
104
|
+
|
105
|
+
Nice to have:
|
106
|
+
|
107
|
+
* Groups (would be nice for accessing older protos)
|
108
|
+
|
109
|
+
# Further Reading
|
110
|
+
|
111
|
+
http://code.google.com/apis/protocolbuffers/docs/encoding.html
|
112
|
+
|
113
|
+
# Thank You
|
114
|
+
|
115
|
+
Keith Rarick (kr) for help with encoding/decoding.
|
116
|
+
Aman Gupta (tmm1) for help with cross VM support and performance enhancements.
|
data/Rakefile
ADDED
data/beefcake.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "beefcake/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "beefcake-spanx"
|
7
|
+
s.version = Beefcake::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Blake Mizerany"]
|
10
|
+
s.email = ["blake.mizerany@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{A sane protobuf library for Ruby}
|
13
|
+
s.description = %q{A sane protobuf library for Ruby}
|
14
|
+
|
15
|
+
s.rubyforge_project = "beefcake"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.add_development_dependency "rake"
|
22
|
+
end
|
data/bench/simple.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'beefcake'
|
3
|
+
|
4
|
+
class MyMessage
|
5
|
+
include Beefcake::Message
|
6
|
+
required :number, :int32, 1
|
7
|
+
required :chars, :string, 2
|
8
|
+
required :raw, :bytes, 3
|
9
|
+
required :bool, :bool, 4
|
10
|
+
required :float, :float, 5
|
11
|
+
end
|
12
|
+
|
13
|
+
if ARGV[0] == 'pprof'
|
14
|
+
# profile message creation/encoding/decoding w/ perftools.rb
|
15
|
+
# works on 1.8 and 1.9
|
16
|
+
# ruby bench/simple.rb pprof
|
17
|
+
# open bench/beefcake.prof.gif
|
18
|
+
|
19
|
+
ENV['CPUPROFILE_FREQUENCY'] = '4000'
|
20
|
+
require 'rubygems'
|
21
|
+
require 'perftools'
|
22
|
+
PerfTools::CpuProfiler.start(File.expand_path("../beefcake.prof", __FILE__)) do
|
23
|
+
100_000.times do
|
24
|
+
str = MyMessage.new(
|
25
|
+
:number => 12345,
|
26
|
+
:chars => 'hello',
|
27
|
+
:raw => 'world',
|
28
|
+
:bool => true,
|
29
|
+
:float => 1.2345
|
30
|
+
).encode
|
31
|
+
MyMessage.decode(str)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
Dir.chdir(File.dirname(__FILE__)) do
|
35
|
+
`pprof.rb beefcake.prof --gif > beefcake.prof.gif`
|
36
|
+
end
|
37
|
+
|
38
|
+
else
|
39
|
+
# benchmark message creation/encoding/decoding
|
40
|
+
# rvm install 1.8.7 1.9.2 jruby rbx
|
41
|
+
# rvm 1.8.7,1.9.2,jruby,rbx ruby bench/simple.rb
|
42
|
+
|
43
|
+
require 'benchmark'
|
44
|
+
|
45
|
+
ITERS = 100_000
|
46
|
+
|
47
|
+
Benchmark.bmbm do |x|
|
48
|
+
x.report 'object creation' do
|
49
|
+
ITERS.times do
|
50
|
+
Object.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
x.report 'message creation' do
|
54
|
+
ITERS.times do
|
55
|
+
MyMessage.new(
|
56
|
+
:number => 12345,
|
57
|
+
:chars => 'hello',
|
58
|
+
:raw => 'world',
|
59
|
+
:bool => true,
|
60
|
+
:float => 1.2345
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
x.report 'message encoding' do
|
65
|
+
m = MyMessage.new(
|
66
|
+
:number => 12345,
|
67
|
+
:chars => 'hello',
|
68
|
+
:raw => 'world',
|
69
|
+
:bool => true,
|
70
|
+
:float => 1.2345
|
71
|
+
)
|
72
|
+
ITERS.times do
|
73
|
+
m.encode
|
74
|
+
end
|
75
|
+
end
|
76
|
+
x.report 'message decoding' do
|
77
|
+
str = MyMessage.new(
|
78
|
+
:number => 12345,
|
79
|
+
:chars => 'hello',
|
80
|
+
:raw => 'world',
|
81
|
+
:bool => true,
|
82
|
+
:float => 1.2345
|
83
|
+
).encode.to_s
|
84
|
+
ITERS.times do
|
85
|
+
MyMessage.decode(str.dup)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'beefcake/generator'
|
4
|
+
|
5
|
+
ns = (ENV["BEEFCAKE_NAMESPACE"] || "").split("::")
|
6
|
+
|
7
|
+
req = CodeGeneratorRequest.decode(STDIN.read)
|
8
|
+
res = Beefcake::Generator.compile(ns, req)
|
9
|
+
|
10
|
+
# Send it out!
|
11
|
+
STDOUT.print(res.encode)
|
Binary file
|
data/lib/beefcake.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'beefcake/buffer'
|
2
|
+
|
3
|
+
module Beefcake
|
4
|
+
module Message
|
5
|
+
|
6
|
+
class WrongTypeError < StandardError
|
7
|
+
def initialize(name, exp, got)
|
8
|
+
super("Wrong type `#{got}` given for (#{name}). Expected #{exp}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
class InvalidValueError < StandardError
|
14
|
+
def initialize(name, val)
|
15
|
+
super("Invalid Value given for `#{name}`: #{val.inspect}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class RequiredFieldNotSetError < StandardError
|
21
|
+
def initialize(name)
|
22
|
+
super("Field #{name} is required but nil")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
class Field < Struct.new(:rule, :name, :type, :fn, :opts)
|
28
|
+
def <=>(o)
|
29
|
+
fn <=> o.fn
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
module Dsl
|
35
|
+
def required(name, type, fn, opts={})
|
36
|
+
field(:required, name, type, fn, opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
def repeated(name, type, fn, opts={})
|
40
|
+
field(:repeated, name, type, fn, opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def optional(name, type, fn, opts={})
|
44
|
+
field(:optional, name, type, fn, opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
def field(rule, name, type, fn, opts)
|
48
|
+
fields[fn] = Field.new(rule, name, type, fn, opts)
|
49
|
+
attr_accessor name
|
50
|
+
end
|
51
|
+
|
52
|
+
def fields
|
53
|
+
@fields ||= {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module Encode
|
58
|
+
|
59
|
+
def encode(buf = Buffer.new)
|
60
|
+
validate!
|
61
|
+
|
62
|
+
if ! buf.respond_to?(:<<)
|
63
|
+
raise ArgumentError, "buf doesn't respond to `<<`"
|
64
|
+
end
|
65
|
+
|
66
|
+
if ! buf.is_a?(Buffer)
|
67
|
+
buf = Buffer.new(buf)
|
68
|
+
end
|
69
|
+
|
70
|
+
# TODO: Error if any required fields at nil
|
71
|
+
|
72
|
+
fields.values.sort.each do |fld|
|
73
|
+
if fld.opts[:packed]
|
74
|
+
bytes = encode!(Buffer.new, fld, 0)
|
75
|
+
buf.append_info(fld.fn, Buffer.wire_for(fld.type))
|
76
|
+
buf.append_uint64(bytes.length)
|
77
|
+
buf << bytes
|
78
|
+
else
|
79
|
+
encode!(buf, fld, fld.fn)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
buf
|
84
|
+
end
|
85
|
+
|
86
|
+
def encode!(buf, fld, fn)
|
87
|
+
v = self[fld.name]
|
88
|
+
v = v.is_a?(Array) ? v : [v]
|
89
|
+
|
90
|
+
v.compact.each do |val|
|
91
|
+
case fld.type
|
92
|
+
when Class # encodable
|
93
|
+
# TODO: raise error if type != val.class
|
94
|
+
buf.append(:string, val.encode, fn)
|
95
|
+
when Module # enum
|
96
|
+
if ! valid_enum?(fld.type, val)
|
97
|
+
raise InvalidValueError.new(fld.name, val)
|
98
|
+
end
|
99
|
+
|
100
|
+
buf.append(:int32, val, fn)
|
101
|
+
else
|
102
|
+
buf.append(fld.type, val, fn)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
buf
|
107
|
+
end
|
108
|
+
|
109
|
+
def valid_enum?(mod, val)
|
110
|
+
!!name_for(mod, val)
|
111
|
+
end
|
112
|
+
|
113
|
+
def name_for(mod, val)
|
114
|
+
mod.constants.each do |name|
|
115
|
+
if mod.const_get(name) == val
|
116
|
+
return name
|
117
|
+
end
|
118
|
+
end
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def validate!
|
123
|
+
fields.values.each do |fld|
|
124
|
+
if fld.rule == :required && self[fld.name].nil?
|
125
|
+
raise RequiredFieldNotSetError, fld.name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
module Decode
|
134
|
+
def decode(buf, o=self.new)
|
135
|
+
if ! buf.is_a?(Buffer)
|
136
|
+
buf = Buffer.new(buf)
|
137
|
+
end
|
138
|
+
|
139
|
+
# TODO: test for incomplete buffer
|
140
|
+
while buf.length > 0
|
141
|
+
fn, wire = buf.read_info
|
142
|
+
|
143
|
+
fld = fields[fn]
|
144
|
+
|
145
|
+
# We don't have a field for with index fn.
|
146
|
+
# Ignore this data and move on.
|
147
|
+
if fld.nil?
|
148
|
+
buf.skip(wire)
|
149
|
+
next
|
150
|
+
end
|
151
|
+
|
152
|
+
exp = Buffer.wire_for(fld.type)
|
153
|
+
if wire != exp
|
154
|
+
raise WrongTypeError.new(fld.name, exp, wire)
|
155
|
+
end
|
156
|
+
|
157
|
+
if fld.rule == :repeated && fld.opts[:packed]
|
158
|
+
len = buf.read_uint64
|
159
|
+
tmp = Buffer.new(buf.read(len))
|
160
|
+
o[fld.name] ||= []
|
161
|
+
while tmp.length > 0
|
162
|
+
o[fld.name] << tmp.read(fld.type)
|
163
|
+
end
|
164
|
+
elsif fld.rule == :repeated
|
165
|
+
val = buf.read(fld.type)
|
166
|
+
(o[fld.name] ||= []) << val
|
167
|
+
else
|
168
|
+
val = buf.read(fld.type)
|
169
|
+
o[fld.name] = val
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Set defaults
|
174
|
+
fields.values.each do |f|
|
175
|
+
next if o[f.name] == false
|
176
|
+
o[f.name] ||= f.opts[:default]
|
177
|
+
end
|
178
|
+
|
179
|
+
o.validate!
|
180
|
+
|
181
|
+
o
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def self.included(o)
|
187
|
+
o.extend Dsl
|
188
|
+
o.extend Decode
|
189
|
+
o.send(:include, Encode)
|
190
|
+
end
|
191
|
+
|
192
|
+
def initialize(attrs={})
|
193
|
+
fields.values.each do |fld|
|
194
|
+
self[fld.name] = attrs[fld.name]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def fields
|
199
|
+
self.class.fields
|
200
|
+
end
|
201
|
+
|
202
|
+
def [](k)
|
203
|
+
__send__(k)
|
204
|
+
end
|
205
|
+
|
206
|
+
def []=(k, v)
|
207
|
+
__send__("#{k}=", v)
|
208
|
+
end
|
209
|
+
|
210
|
+
def ==(o)
|
211
|
+
return false if o == nil
|
212
|
+
fields.values.all? {|fld| self[fld.name] == o[fld.name] }
|
213
|
+
end
|
214
|
+
|
215
|
+
def inspect
|
216
|
+
set = fields.values.select {|fld| self[fld.name] != nil }
|
217
|
+
|
218
|
+
flds = set.map do |fld|
|
219
|
+
val = self[fld.name]
|
220
|
+
|
221
|
+
case fld.type
|
222
|
+
when Class
|
223
|
+
"#{fld.name}: #{val.inspect}"
|
224
|
+
when Module
|
225
|
+
title = name_for(fld.type, val) || "-NA-"
|
226
|
+
"#{fld.name}: #{title}(#{val.inspect})"
|
227
|
+
else
|
228
|
+
"#{fld.name}: #{val.inspect}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
"<#{self.class.name} #{flds.join(", ")}>"
|
233
|
+
end
|
234
|
+
|
235
|
+
def to_hash
|
236
|
+
fields.values.inject({}) do |h, fld|
|
237
|
+
if v = self[fld.name]
|
238
|
+
h[fld.name] = v
|
239
|
+
end
|
240
|
+
h
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|