protopuffs 0.3.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/.gitignore +4 -0
- data/.specification +108 -0
- data/LICENSE.txt +18 -0
- data/README.rdoc +74 -0
- data/Rakefile +49 -0
- data/VERSION.yml +4 -0
- data/lib/protopuffs.rb +31 -0
- data/lib/protopuffs/message/base.rb +124 -0
- data/lib/protopuffs/message/field.rb +226 -0
- data/lib/protopuffs/message/wire_type.rb +12 -0
- data/lib/protopuffs/parser/parser.rb +24 -0
- data/lib/protopuffs/parser/protocol_buffer.treetop +124 -0
- data/protopuffs.gemspec +78 -0
- data/test/abstract_syntax_tree_test.rb +153 -0
- data/test/fixtures/proto/person.proto +5 -0
- data/test/message_base_test.rb +202 -0
- data/test/message_field_test.rb +78 -0
- data/test/parse_tree_test.rb +208 -0
- data/test/protopuffs_test.rb +38 -0
- data/test/test_helper.rb +90 -0
- data/test/text_format_test.rb +23 -0
- data/test/wire_format_test.rb +293 -0
- metadata +113 -0
data/.gitignore
ADDED
data/.specification
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protopuffs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Kampmeier
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-19 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: treetop
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: shoulda
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
description: A new implementation of Protocol Buffers in Ruby
|
46
|
+
email: chris@kampers.net
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.rdoc
|
53
|
+
- LICENSE.txt
|
54
|
+
files:
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.rdoc
|
57
|
+
- VERSION.yml
|
58
|
+
- lib/protopuffs
|
59
|
+
- lib/protopuffs/message
|
60
|
+
- lib/protopuffs/message/base.rb
|
61
|
+
- lib/protopuffs/message/field.rb
|
62
|
+
- lib/protopuffs/message/wire_type.rb
|
63
|
+
- lib/protopuffs/parser
|
64
|
+
- lib/protopuffs/parser/parser.rb
|
65
|
+
- lib/protopuffs/parser/protocol_buffer.treetop
|
66
|
+
- lib/protopuffs.rb
|
67
|
+
- test/abstract_syntax_tree_test.rb
|
68
|
+
- test/fixtures
|
69
|
+
- test/fixtures/proto
|
70
|
+
- test/fixtures/proto/person.proto
|
71
|
+
- test/message_base_test.rb
|
72
|
+
- test/message_field_test.rb
|
73
|
+
- test/parse_tree_test.rb
|
74
|
+
- test/protopuffs_test.rb
|
75
|
+
- test/test_helper.rb
|
76
|
+
- test/text_format_test.rb
|
77
|
+
- test/wire_format_test.rb
|
78
|
+
has_rdoc: true
|
79
|
+
homepage: http://github.com/chrisk/protopuffs
|
80
|
+
licenses: []
|
81
|
+
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options:
|
84
|
+
- --inline-source
|
85
|
+
- --charset=UTF-8
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
version:
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.4
|
104
|
+
signing_key:
|
105
|
+
specification_version: 2
|
106
|
+
summary: Sex, drugs, and protocol buffers
|
107
|
+
test_files: []
|
108
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
2
|
+
a copy of this software and associated documentation files (the
|
3
|
+
"Software"), to deal in the Software without restriction, including
|
4
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
5
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
6
|
+
permit persons to whom the Software is furnished to do so, subject to
|
7
|
+
the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be
|
10
|
+
included in all copies or substantial portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
13
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
14
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
15
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
16
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
17
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
18
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
= Protopuffs!
|
2
|
+
|
3
|
+
A new implementation of Protocol Buffers in Ruby.
|
4
|
+
|
5
|
+
If you're not familiar with Protocol Buffers, start with Google's homepage:
|
6
|
+
http://code.google.com/apis/protocolbuffers
|
7
|
+
|
8
|
+
Protocol buffers are Google's language-neutral, platform-neutral, extensible
|
9
|
+
mechanism for serializing structured data -- think XML, but smaller, faster,
|
10
|
+
and simpler.
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
Rubyforge is cuckoo for protopuffs.
|
15
|
+
|
16
|
+
sudo gem install protopuffs
|
17
|
+
|
18
|
+
== Usage
|
19
|
+
|
20
|
+
Start with a +proto+ file, say, <tt>proto/animals.proto</tt>:
|
21
|
+
|
22
|
+
message Bird {
|
23
|
+
required string name = 1;
|
24
|
+
optional string species = 2;
|
25
|
+
}
|
26
|
+
|
27
|
+
First, require Protopuffs and tell it where your +proto+ files are:
|
28
|
+
|
29
|
+
require 'protopuffs'
|
30
|
+
Protopuffs.proto_load_path << "proto"
|
31
|
+
Protopuffs.load_message_classes
|
32
|
+
|
33
|
+
That makes the Bird message dynamically available in Ruby. Everything's
|
34
|
+
namespaced under <tt>Protopuffs::Message</tt>, which should help with your OCD.
|
35
|
+
|
36
|
+
bird = Protopuffs::Message::Bird.new
|
37
|
+
bird.name = "Sonny"
|
38
|
+
bird.species = "Cuculus canorus"
|
39
|
+
|
40
|
+
# encode this message to the super-efficient binary wire format
|
41
|
+
binary_bird = bird.to_wire_format
|
42
|
+
|
43
|
+
# or encode to the human-friendly text format, for debugging
|
44
|
+
puts bird.inspect
|
45
|
+
|
46
|
+
You can also decode incoming binary wire-format messages:
|
47
|
+
|
48
|
+
decoded_bird = Protopuffs::Message::Bird.new
|
49
|
+
decoded_bird.from_wire_format(binary_bird)
|
50
|
+
decoded_bird.name # => "Sonny"
|
51
|
+
|
52
|
+
=== Mass-assignment
|
53
|
+
|
54
|
+
TODO: explain <tt>Message::Base.new</tt> with strings containing the wire format
|
55
|
+
or hashes, as well as <tt>#attributes=</tt>
|
56
|
+
|
57
|
+
== Missing functionality
|
58
|
+
|
59
|
+
Protopuffs currently only supports a base set of the <tt>.proto</tt> file
|
60
|
+
syntax. Here's what's missing:
|
61
|
+
|
62
|
+
* the sfixed64 type
|
63
|
+
* sint32 and sint64 types (due to lack of support for ZigZag encoding)
|
64
|
+
* packed repeated fields (the <tt>[packed=true]</tt> option)
|
65
|
+
* enumerations
|
66
|
+
* importing definitions
|
67
|
+
* nested message types
|
68
|
+
* extensions
|
69
|
+
* nested extensions
|
70
|
+
* packages
|
71
|
+
* services
|
72
|
+
* built-in options
|
73
|
+
* custom options
|
74
|
+
* groups (deprecated)
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rcov/rcovtask'
|
7
|
+
begin require 'metric_fu'; rescue LoadError; end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |s|
|
12
|
+
s.name = "protopuffs"
|
13
|
+
s.rubyforge_project = "protopuffs"
|
14
|
+
s.summary = %Q{Sex, drugs, and protocol buffers}
|
15
|
+
s.email = "chris@kampers.net"
|
16
|
+
s.homepage = "http://github.com/chrisk/protopuffs"
|
17
|
+
s.description = "A new implementation of Protocol Buffers in Ruby"
|
18
|
+
s.authors = ["Chris Kampmeier"]
|
19
|
+
s.add_dependency "treetop"
|
20
|
+
s.add_development_dependency "mocha"
|
21
|
+
s.add_development_dependency "shoulda"
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::TestTask.new do |t|
|
28
|
+
t.libs << 'lib'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
Rake::RDocTask.new do |rdoc|
|
34
|
+
rdoc.rdoc_dir = 'rdoc'
|
35
|
+
rdoc.title = 'Protopuffs'
|
36
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
37
|
+
rdoc.rdoc_files.include('README*')
|
38
|
+
rdoc.rdoc_files.include('LICENSE*')
|
39
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
40
|
+
end
|
41
|
+
|
42
|
+
Rcov::RcovTask.new do |t|
|
43
|
+
t.libs << 'test'
|
44
|
+
t.rcov_opts << '--exclude "gems/*"'
|
45
|
+
t.test_files = FileList['test/**/*_test.rb']
|
46
|
+
t.verbose = true
|
47
|
+
end
|
48
|
+
|
49
|
+
task :default => :test
|
data/VERSION.yml
ADDED
data/lib/protopuffs.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "treetop"
|
5
|
+
require "protopuffs/message/base"
|
6
|
+
require "protopuffs/message/field"
|
7
|
+
require "protopuffs/message/wire_type"
|
8
|
+
require "protopuffs/parser/parser"
|
9
|
+
|
10
|
+
|
11
|
+
module Protopuffs
|
12
|
+
class ParseError < StandardError; end
|
13
|
+
|
14
|
+
def self.proto_load_path
|
15
|
+
@proto_load_path ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.proto_load_path=(paths)
|
19
|
+
@proto_load_path = paths
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.load_message_classes
|
23
|
+
parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
|
24
|
+
|
25
|
+
@proto_load_path.each do |path|
|
26
|
+
Dir.glob(File.join(path, "**/*.proto")) do |descriptor_path|
|
27
|
+
parser.parse(File.read(descriptor_path))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Protopuffs
|
4
|
+
module Message
|
5
|
+
|
6
|
+
class Base
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :fields
|
10
|
+
|
11
|
+
def define_message_class(name, fields)
|
12
|
+
name = name.delete("_")
|
13
|
+
self.check_fields_for_errors(name, fields)
|
14
|
+
Message.send(:remove_const, name) if Message.const_defined?(name)
|
15
|
+
klass = Message.const_set(name, Class.new(self))
|
16
|
+
klass.instance_variable_set(:@fields, fields)
|
17
|
+
fields.each do |field|
|
18
|
+
klass.send(:attr_accessor, field.identifier)
|
19
|
+
end
|
20
|
+
klass
|
21
|
+
end
|
22
|
+
|
23
|
+
def check_fields_for_errors(name, fields)
|
24
|
+
tags = fields.map { |field| field.tag }
|
25
|
+
if tags.uniq.size != fields.size
|
26
|
+
raise ParseError, "A tag was reused in the descriptor for message #{name}. Please check that each tag is unique."
|
27
|
+
end
|
28
|
+
|
29
|
+
if tags.any? { |tag| tag < 1 || tag > 536_870_911 }
|
30
|
+
raise ParseError, "A tag is out of range in the descriptor for message #{name}. Please check that each tag is in the range 1 <= x <= 536,870,911."
|
31
|
+
end
|
32
|
+
|
33
|
+
if tags.any? { |tag| (19000..19999).include?(tag) }
|
34
|
+
raise ParseError, "A tag is invalid in the descriptor for message #{name}. Please check that each tag is not in the reserved range 19,000 <= x <= 19,999."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :buffer
|
40
|
+
|
41
|
+
def initialize(field_values = nil)
|
42
|
+
if self.class == Base
|
43
|
+
raise "#{self.class} should not be instantiated directly. Use the factory #{self.class}.define_message_class instead."
|
44
|
+
end
|
45
|
+
|
46
|
+
if field_values.nil?
|
47
|
+
@buffer = StringIO.new
|
48
|
+
@buffer.set_encoding("BINARY") if @buffer.respond_to?(:set_encoding)
|
49
|
+
elsif field_values.respond_to?(:each_pair)
|
50
|
+
@buffer = StringIO.new
|
51
|
+
@buffer.set_encoding("BINARY") if @buffer.respond_to?(:set_encoding)
|
52
|
+
self.attributes = field_values
|
53
|
+
else
|
54
|
+
from_wire_format(field_values)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_wire_format
|
59
|
+
self.class.fields.sort_by { |f| f.tag }.each do |field|
|
60
|
+
value = send(field.identifier)
|
61
|
+
@buffer.write field.to_wire_format_with_value(value) unless value.nil?
|
62
|
+
end
|
63
|
+
@buffer.string
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the protocol buffer text format, which is useful for debugging
|
67
|
+
def inspect
|
68
|
+
type = self.class.name.split("::").last.downcase
|
69
|
+
field_strings = self.class.fields.map { |f| " #{f.identifier}: #{send(f.identifier).inspect}\n" }
|
70
|
+
"#{type} {\n#{field_strings.join}}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def from_wire_format(buffer)
|
74
|
+
if !buffer.respond_to?(:read)
|
75
|
+
buffer.force_encoding("BINARY") if buffer.respond_to?(:force_encoding)
|
76
|
+
@buffer = StringIO.new(buffer)
|
77
|
+
else
|
78
|
+
@buffer = buffer
|
79
|
+
end
|
80
|
+
@buffer.set_encoding("BINARY") if @buffer.respond_to?(:set_encoding)
|
81
|
+
|
82
|
+
until @buffer.eof?
|
83
|
+
tag = MessageField.shift_tag(@buffer)
|
84
|
+
field = self.class.fields.find { |f| f.tag == tag }
|
85
|
+
next if field.nil?
|
86
|
+
value_bytes = field.class.shift(@buffer)
|
87
|
+
|
88
|
+
value = field.decode(value_bytes)
|
89
|
+
if field.repeated? && send(field.identifier).nil?
|
90
|
+
send("#{field.identifier}=", [value])
|
91
|
+
elsif field.repeated?
|
92
|
+
send(field.identifier) << value
|
93
|
+
else
|
94
|
+
send("#{field.identifier}=", value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
set_values_for_missing_optional_fields
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_values_for_missing_optional_fields
|
102
|
+
self.class.fields.select { |field| field.optional? }.each do |field|
|
103
|
+
send("#{field.identifier}=", field.default) if send(field.identifier).nil?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def attributes=(attrs = {})
|
108
|
+
attrs.each_pair do |name, value|
|
109
|
+
self.send("#{name}=", value) if respond_to?("#{name}=")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def ==(other)
|
114
|
+
return false if self.class != other.class
|
115
|
+
self.class.fields.each do |field|
|
116
|
+
return false if send(field.identifier) != other.send(field.identifier)
|
117
|
+
end
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Protopuffs
|
4
|
+
|
5
|
+
class MessageField
|
6
|
+
attr_reader :identifier, :tag, :default
|
7
|
+
|
8
|
+
def self.factory(type, *args)
|
9
|
+
case type
|
10
|
+
when "int32" then Int32.new(*args)
|
11
|
+
when "int64" then Int64.new(*args)
|
12
|
+
when "uint32" then UInt32.new(*args)
|
13
|
+
when "uint64" then UInt64.new(*args)
|
14
|
+
when "bool" then Bool.new(*args)
|
15
|
+
when "double" then Double.new(*args)
|
16
|
+
when "fixed64" then Fixed64.new(*args)
|
17
|
+
when "string" then String.new(*args)
|
18
|
+
when "bytes" then Bytes.new(*args)
|
19
|
+
when "float" then Float.new(*args)
|
20
|
+
when "fixed32" then Fixed32.new(*args)
|
21
|
+
when "sfixed32" then SFixed32.new(*args)
|
22
|
+
else Embedded.new(type, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(modifier, identifier, tag, default)
|
27
|
+
raise "MessageField is an abstract base class" if self.class == MessageField
|
28
|
+
raise ArgumentError.new("Invalid modifier '#{modifier}'") unless
|
29
|
+
["optional", "required", "repeated"].include?(modifier)
|
30
|
+
@modifier = modifier
|
31
|
+
@identifier = identifier
|
32
|
+
@tag = tag
|
33
|
+
@default = default
|
34
|
+
end
|
35
|
+
private :initialize
|
36
|
+
|
37
|
+
def key
|
38
|
+
(@tag << 3) | self.class.wire_type
|
39
|
+
end
|
40
|
+
|
41
|
+
def repeated?
|
42
|
+
@modifier == "repeated"
|
43
|
+
end
|
44
|
+
|
45
|
+
def optional?
|
46
|
+
@modifier == "optional"
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_wire_format_with_value(value)
|
50
|
+
field_encoder = lambda { |val| VarInt.encode(key) + self.class.encode(val) }
|
51
|
+
if repeated?
|
52
|
+
value.map(&field_encoder).join
|
53
|
+
else
|
54
|
+
field_encoder.call(value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.shift_tag(buffer)
|
59
|
+
bits = 0
|
60
|
+
bytes = VarInt.shift(buffer)
|
61
|
+
bytes.each_with_index do |byte, index|
|
62
|
+
byte &= 0b01111111
|
63
|
+
bits |= byte << (7 * index)
|
64
|
+
end
|
65
|
+
bits >> 3
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Bool < MessageField
|
70
|
+
def initialize(modifier, identifier, tag, default = nil)
|
71
|
+
super(modifier, identifier, tag, default || false)
|
72
|
+
end
|
73
|
+
def self.wire_type; WireType::VARINT end
|
74
|
+
def self.shift(buffer); VarInt.shift(buffer) end
|
75
|
+
def decode(value_bytes)
|
76
|
+
value = VarInt.decode(value_bytes)
|
77
|
+
value = true if value == 1
|
78
|
+
value = false if value == 0
|
79
|
+
value
|
80
|
+
end
|
81
|
+
def self.encode(value); VarInt.encode(value ? 1 : 0) end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Numeric < MessageField
|
85
|
+
def initialize(modifier, identifier, tag, default = nil)
|
86
|
+
super(modifier, identifier, tag, default || 0)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class VarInt < Numeric
|
91
|
+
def self.wire_type; WireType::VARINT end
|
92
|
+
def self.shift(buffer)
|
93
|
+
bytes = []
|
94
|
+
begin
|
95
|
+
# Use #readbyte in Ruby 1.9, and #readchar in Ruby 1.8
|
96
|
+
byte = buffer.send(buffer.respond_to?(:readbyte) ? :readbyte : :readchar)
|
97
|
+
bytes << (byte & 0b01111111)
|
98
|
+
end while byte >> 7 == 1
|
99
|
+
bytes
|
100
|
+
end
|
101
|
+
def self.decode(bytes)
|
102
|
+
value = 0
|
103
|
+
bytes.each_with_index do |byte, index|
|
104
|
+
value |= byte << (7 * index)
|
105
|
+
end
|
106
|
+
value
|
107
|
+
end
|
108
|
+
def decode(bytes)
|
109
|
+
VarInt.decode(bytes)
|
110
|
+
end
|
111
|
+
def self.encode(value)
|
112
|
+
return [0].pack('C') if value.zero?
|
113
|
+
bytes = []
|
114
|
+
until value.zero?
|
115
|
+
byte = 0
|
116
|
+
7.times do |i|
|
117
|
+
byte |= (value & 1) << i
|
118
|
+
value >>= 1
|
119
|
+
end
|
120
|
+
byte |= 0b10000000
|
121
|
+
bytes << byte
|
122
|
+
end
|
123
|
+
bytes[-1] &= 0b01111111
|
124
|
+
bytes.pack('C*')
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Int32 < VarInt; end
|
129
|
+
class Int64 < VarInt; end
|
130
|
+
class UInt32 < VarInt; end
|
131
|
+
class UInt64 < VarInt; end
|
132
|
+
|
133
|
+
class Fixed32Base < Numeric
|
134
|
+
def self.wire_type; WireType::FIXED32 end
|
135
|
+
def self.shift(buffer); buffer.read(4) end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Fixed32 < Fixed32Base
|
139
|
+
def decode(bytes); bytes.unpack('V').first end
|
140
|
+
def self.encode(value); [value].pack('V') end
|
141
|
+
end
|
142
|
+
|
143
|
+
class SFixed32 < Fixed32Base
|
144
|
+
def decode(bytes)
|
145
|
+
value = bytes.unpack('V').first
|
146
|
+
value >= 2**31 ? value -= 2**32 : value
|
147
|
+
end
|
148
|
+
def self.encode(value); [value].pack('V') end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Float < Fixed32Base
|
152
|
+
def decode(bytes); bytes.unpack('e').first end
|
153
|
+
def self.encode(value); [value].pack('e') end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Fixed64Base < Numeric
|
157
|
+
def self.wire_type; WireType::FIXED64 end
|
158
|
+
def self.shift(buffer); buffer.read(8) end
|
159
|
+
end
|
160
|
+
|
161
|
+
class Fixed64 < Fixed64Base
|
162
|
+
def decode(bytes); bytes.unpack('Q').first end
|
163
|
+
def self.encode(value); [value].pack('Q') end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Double < Fixed64Base
|
167
|
+
def decode(bytes); bytes.unpack('E').first end
|
168
|
+
def self.encode(value); [value].pack('E') end
|
169
|
+
end
|
170
|
+
|
171
|
+
class LengthDelimited < MessageField
|
172
|
+
def self.wire_type; WireType::LENGTH_DELIMITED end
|
173
|
+
def self.shift(buffer)
|
174
|
+
bytes = VarInt.shift(buffer)
|
175
|
+
value_length = VarInt.decode(bytes)
|
176
|
+
buffer.read(value_length)
|
177
|
+
end
|
178
|
+
def decode(bytes); bytes end
|
179
|
+
end
|
180
|
+
|
181
|
+
class String < LengthDelimited
|
182
|
+
def initialize(modifier, identifier, tag, default = nil)
|
183
|
+
super(modifier, identifier, tag, default || "")
|
184
|
+
end
|
185
|
+
def self.encode(value)
|
186
|
+
value = value.to_s
|
187
|
+
value.force_encoding("UTF-8") if value.respond_to?(:force_encoding)
|
188
|
+
# Use #bytesize in Ruby 1.9, and #size in Ruby 1.8
|
189
|
+
size = value.respond_to?(:bytesize) ? value.bytesize : value.size
|
190
|
+
VarInt.encode(size) + value
|
191
|
+
end
|
192
|
+
def decode(bytes)
|
193
|
+
bytes.respond_to?(:force_encoding) ? bytes.force_encoding("UTF-8") : bytes
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class Bytes < LengthDelimited
|
198
|
+
def initialize(modifier, identifier, tag, default = nil)
|
199
|
+
super
|
200
|
+
end
|
201
|
+
def self.encode(value)
|
202
|
+
VarInt.encode(value.size) + value
|
203
|
+
end
|
204
|
+
def decode(bytes)
|
205
|
+
bytes.respond_to?(:force_encoding) ? bytes.force_encoding("BINARY") : bytes
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class Embedded < LengthDelimited
|
210
|
+
def initialize(type, modifier, identifier, tag, default = nil)
|
211
|
+
@type = type
|
212
|
+
super(modifier, identifier, tag, default)
|
213
|
+
end
|
214
|
+
def decode(bytes)
|
215
|
+
bytes.force_encoding("BINARY") if bytes.respond_to?(:force_encoding)
|
216
|
+
value = Message.const_get(@type.delete("_")).new
|
217
|
+
value.from_wire_format(StringIO.new(bytes))
|
218
|
+
end
|
219
|
+
def self.encode(value)
|
220
|
+
embedded_bytes = value.to_wire_format
|
221
|
+
VarInt.encode(embedded_bytes.size) + embedded_bytes
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|