beefcake 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.
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.
@@ -0,0 +1,126 @@
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
+ # Caveats
71
+
72
+ Currently Beefcake doesn't parse `.proto` files. This is OK for now.
73
+ In the simple case, you can create message types by hand; This is Ruby
74
+ after all.
75
+
76
+ Example (for the above):
77
+
78
+ message Variety {
79
+ required int32 x = 1
80
+ required int32 y = 2
81
+ optional string tag = 3
82
+
83
+ ...
84
+ }
85
+
86
+ In the near future, a generator would be nice. I welcome anyone willing
87
+ to work on it to submit patches. The other ruby libs lacked things I would
88
+ like, such as an optional namespace param rather than installing on (main).
89
+
90
+ This library was built with EventMachine in mind. Not just blocking-IO.
91
+
92
+ # Dev
93
+
94
+ Source:
95
+
96
+ $ git clone git://github.com/bmizerany/beefcake
97
+
98
+ Testing:
99
+
100
+ $ gem install turn
101
+ $ cd /path/to/beefcake
102
+ $ turn
103
+
104
+ VMs:
105
+
106
+ Currently Beefcake is tested and working on:
107
+
108
+ Ruby 1.8.7
109
+ Ruby 1.9.2
110
+ JRuby 1.5.6
111
+
112
+ There is a bug in Rubinius preventing proper ZigZag encoding.
113
+ The team is aware and I'm sure they're working on a fix.
114
+
115
+ Nice to have:
116
+
117
+ * `.proto` -> Ruby generator
118
+ * Groups (would be nice for accessing older protos)
119
+
120
+ # Further Reading
121
+
122
+ http://code.google.com/apis/protocolbuffers/docs/encoding.html
123
+
124
+ # Thank You
125
+
126
+ Aman Gupta (tmm1) for help with cross VM support and performance enhancements.
@@ -0,0 +1,203 @@
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
+ Array(self[fld.name]).each do |val|
88
+ case fld.type
89
+ when Class # encodable
90
+ # TODO: raise error if type != val.class
91
+ buf.append(:string, val.encode, fn)
92
+ when Module # enum
93
+ if ! valid_enum?(fld.type, val)
94
+ raise InvalidValueError.new(fld.name, val)
95
+ end
96
+
97
+ buf.append(:int32, val, fn)
98
+ else
99
+ buf.append(fld.type, val, fn)
100
+ end
101
+ end
102
+
103
+ buf
104
+ end
105
+
106
+ def valid_enum?(mod, val)
107
+ mod.constants.any? {|name| mod.const_get(name) == val }
108
+ end
109
+
110
+ def validate!
111
+ fields.values.each do |fld|
112
+ if fld.rule == :required && self[fld.name].nil?
113
+ raise RequiredFieldNotSetError, fld.name
114
+ end
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+
121
+ module Decode
122
+ def decode(buf, o=self.new)
123
+ if ! buf.is_a?(Buffer)
124
+ buf = Buffer.new(buf)
125
+ end
126
+
127
+ # TODO: test for incomplete buffer
128
+ while buf.length > 0
129
+ fn, wire = buf.read_info
130
+
131
+ fld = fields[fn]
132
+
133
+ # We don't have a field for with index fn.
134
+ # Ignore this data and move on.
135
+ if fld.nil?
136
+ buf.skip(wire)
137
+ next
138
+ end
139
+
140
+ exp = Buffer.wire_for(fld.type)
141
+ if wire != exp
142
+ raise WrongTypeError.new(fld.name, exp, wire)
143
+ end
144
+
145
+ if fld.rule == :repeated && fld.opts[:packed]
146
+ len = buf.read_uint64
147
+ tmp = Buffer.new(buf.read(len))
148
+ o[fld.name] ||= []
149
+ while tmp.length > 0
150
+ o[fld.name] << tmp.read(fld.type)
151
+ end
152
+
153
+ next
154
+ end
155
+
156
+ val = buf.read(fld.type)
157
+ if fld.rule == :repeated
158
+ (o[fld.name] ||= []) << val
159
+ else
160
+ o[fld.name] = val
161
+ end
162
+
163
+ end
164
+
165
+ # Set defaults
166
+ fields.values.each do |f|
167
+ o[f.name] ||= f.opts[:default]
168
+ end
169
+
170
+ o.validate!
171
+
172
+ o
173
+ end
174
+ end
175
+
176
+
177
+ def self.included(o)
178
+ o.extend Dsl
179
+ o.extend Decode
180
+ o.send(:include, Encode)
181
+ end
182
+
183
+ def initialize(attrs={})
184
+ fields.values.each do |fld|
185
+ self[fld.name] = attrs[fld.name]
186
+ end
187
+ end
188
+
189
+ def fields
190
+ self.class.fields
191
+ end
192
+
193
+ def [](k)
194
+ __send__(k)
195
+ end
196
+
197
+ def []=(k, v)
198
+ __send__("#{k}=", v)
199
+ end
200
+
201
+ end
202
+
203
+ end
@@ -0,0 +1,2 @@
1
+ require 'beefcake/buffer/encode'
2
+ require 'beefcake/buffer/decode'
@@ -0,0 +1,92 @@
1
+ module Beefcake
2
+
3
+ class Buffer
4
+
5
+ MinUint32 = 0
6
+ MaxUint32 = (1<<32)-1
7
+ MinInt32 = -(1<<31)
8
+ MaxInt32 = (1<<31)-1
9
+
10
+ MinUint64 = 0
11
+ MaxUint64 = (1<<64)-1
12
+ MinInt64 = -(1<<63)
13
+ MaxInt64 = (1<<63)-1
14
+
15
+ def self.wire_for(type)
16
+ case type
17
+ when :int32, :uint32, :sint32, :int64, :uint64, :sint64, :bool
18
+ 0
19
+ when :fixed64, :sfixed64, :double
20
+ 1
21
+ when :string, :bytes
22
+ 2
23
+ when :fixed32, :sfixed32, :float
24
+ 5
25
+ else
26
+ if encodable?(type)
27
+ 2
28
+ else
29
+ raise UnknownType, type
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.encodable?(type)
35
+ return false if ! type.is_a?(Class)
36
+ pims = type.public_instance_methods
37
+ pims.include?(:encode) || pims.include?("encode")
38
+ end
39
+
40
+ attr_accessor :buf
41
+
42
+ alias :to_s :buf
43
+ alias :to_str :buf
44
+
45
+ class OutOfRangeError < StandardError
46
+ def initialize(n)
47
+ super("Value of of range: %d" % [n])
48
+ end
49
+ end
50
+
51
+ class BufferOverflowError < StandardError
52
+ def initialize(s)
53
+ super("Too many bytes read for %s" % [s])
54
+ end
55
+ end
56
+
57
+ class UnknownType < StandardError
58
+ def initialize(s)
59
+ super("Unknown type '%s'" % [s])
60
+ end
61
+ end
62
+
63
+ def initialize(buf="")
64
+ self.buf = buf
65
+ end
66
+
67
+ if ''.respond_to?(:force_encoding)
68
+ def buf=(buf)
69
+ @buf = buf.force_encoding('BINARY')
70
+ end
71
+ end
72
+
73
+ def length
74
+ @buf.respond_to?(:bytesize) ? @buf.bytesize : @buf.length
75
+ end
76
+
77
+ def <<(bytes)
78
+ buf << bytes
79
+ end
80
+
81
+ def read(n)
82
+ case n
83
+ when Symbol
84
+ __send__("read_#{n}")
85
+ else
86
+ buf.slice!(0, n)
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ end