ruby_protobuf 0.0.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/History.txt +5 -0
- data/Manifest.txt +32 -0
- data/README.txt +50 -0
- data/Rakefile +18 -0
- data/bin/rprotoc +21 -0
- data/bin/ruby_protobuf +0 -0
- data/lib/protobuf/compiler.rb +90 -0
- data/lib/protobuf/decoder.rb +87 -0
- data/lib/protobuf/encoder.rb +36 -0
- data/lib/protobuf/enum.rb +13 -0
- data/lib/protobuf/extend.rb +8 -0
- data/lib/protobuf/field.rb +547 -0
- data/lib/protobuf/message.rb +133 -0
- data/lib/protobuf/parser.y +138 -0
- data/lib/protobuf/service.rb +7 -0
- data/lib/protobuf/wire_type.rb +10 -0
- data/lib/ruby_protobuf.rb +22 -0
- data/test/addressbook.proto +24 -0
- data/test/addressbook.rb +41 -0
- data/test/data/data.bin +3 -0
- data/test/data/data_source.py +14 -0
- data/test/data/types.bin +0 -0
- data/test/data/types_source.py +22 -0
- data/test/test_addressbook.rb +41 -0
- data/test/test_compiler.rb +39 -0
- data/test/test_message.rb +20 -0
- data/test/test_parse.rb +15 -0
- data/test/test_ruby_protobuf.rb +1 -0
- data/test/test_serialize.rb +27 -0
- data/test/test_types.rb +180 -0
- data/test/types.proto +17 -0
- data/test/types.rb +22 -0
- metadata +102 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'protobuf/decoder'
|
3
|
+
require 'protobuf/encoder'
|
4
|
+
require 'protobuf/field'
|
5
|
+
|
6
|
+
module Protobuf
|
7
|
+
OPTIONS = {}
|
8
|
+
|
9
|
+
class Message
|
10
|
+
class <<self
|
11
|
+
attr_reader :fields
|
12
|
+
|
13
|
+
def extensions(range)
|
14
|
+
raise NotImplementedError('TODO')
|
15
|
+
end
|
16
|
+
|
17
|
+
def required(type, name, tag, opts={})
|
18
|
+
define_field :required, type, name, tag, opts
|
19
|
+
end
|
20
|
+
|
21
|
+
def optional(type, name, tag, opts={})
|
22
|
+
define_field :optional, type, name, tag, opts
|
23
|
+
end
|
24
|
+
|
25
|
+
def repeated(type, name, tag, opts={})
|
26
|
+
define_field :repeated, type, name, tag, opts
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_field(rule, type, name, tag, opts={})
|
30
|
+
(@fields ||= {})[tag] = Protobuf::Field.build self, rule, type, name, tag, opts
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_field_by_name(name)
|
34
|
+
@fields.values.find {|field| field.name == name.to_sym}
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_field_by_tag(tag)
|
38
|
+
@fields[tag]
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_field(tag_or_name)
|
42
|
+
case tag_or_name
|
43
|
+
when Integer
|
44
|
+
get_field_by_tag tag_or_name
|
45
|
+
when String, Symbol
|
46
|
+
get_field_by_name tag_or_name
|
47
|
+
else
|
48
|
+
raise TypeError
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
fields.each do |tag, field|
|
55
|
+
field.define_accessor self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_from_string(string)
|
60
|
+
parse_from StringIO.new(string)
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_from_file(filename)
|
64
|
+
if filename.is_a? File
|
65
|
+
parse_from filename
|
66
|
+
else
|
67
|
+
File.open(filename, 'r') do |f|
|
68
|
+
parse_from f
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_from(stream)
|
74
|
+
Protobuf::Decoder.decode stream, self
|
75
|
+
end
|
76
|
+
|
77
|
+
def serialize_to_string(string='')
|
78
|
+
io = StringIO.new string
|
79
|
+
serialize_to io
|
80
|
+
io.string
|
81
|
+
end
|
82
|
+
|
83
|
+
def serialize_to_file(filename)
|
84
|
+
if filename.is_a? File
|
85
|
+
serialize_to filename
|
86
|
+
else
|
87
|
+
File.open(filename, 'w') do |f|
|
88
|
+
serialize_to f
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def serialize_to(stream)
|
94
|
+
Protobuf::Encoder.encode stream, self
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_field(tag, bytes)
|
98
|
+
get_field_by_tag(tag).set self, bytes
|
99
|
+
end
|
100
|
+
|
101
|
+
def merge_field(tag, value)
|
102
|
+
# TODO
|
103
|
+
#get_field_by_tag(tag).merge self, bytes
|
104
|
+
end
|
105
|
+
|
106
|
+
def [](tag_or_name)
|
107
|
+
if field = get_field(tag_or_name)
|
108
|
+
send field.name
|
109
|
+
else
|
110
|
+
raise NoMethodError.new("No such method: #{tag_or_name}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def []=(tag_or_name, value)
|
115
|
+
if field = get_field(tag_or_name) and not field.repeated?
|
116
|
+
send "#{field.name}=", value
|
117
|
+
else
|
118
|
+
raise NoMethodError.new("No such method: #{tag_or_name}=")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def fields; self.class.fields end
|
123
|
+
def get_field_by_name(name); self.class.get_field_by_name(name) end
|
124
|
+
def get_field_by_tag(tag); self.class.get_field_by_tag(tag) end
|
125
|
+
def get_field(tag_or_name); self.class.get_field(tag_or_name) end
|
126
|
+
|
127
|
+
def each_field(&block)
|
128
|
+
fields.to_a.sort{|(t1, f1), (t2, f2)| t1 <=> t2}.each do |tag, field|
|
129
|
+
block.call field, self[tag]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
class Protobuf::Parser
|
2
|
+
rule
|
3
|
+
stmt_list : stmt
|
4
|
+
| stmt_list stmt
|
5
|
+
|
6
|
+
stmt : package_stmt
|
7
|
+
| import_stmt
|
8
|
+
| option_stmt
|
9
|
+
| message_def
|
10
|
+
| extend_def
|
11
|
+
| enum_def
|
12
|
+
| service_def
|
13
|
+
|
14
|
+
package_stmt : PACKAGE fqcn_package ';'
|
15
|
+
|
16
|
+
fqcn_package : ident
|
17
|
+
| fqcn_package '.' ident
|
18
|
+
|
19
|
+
import_stmt : IMPORT STRING ';'
|
20
|
+
|
21
|
+
option_stmt : OPTION ident '=' STRING ';'
|
22
|
+
| OPTION ident '=' ident ';'
|
23
|
+
|
24
|
+
message_def : MESSAGE ident '{' message_item_list '}'
|
25
|
+
|
26
|
+
message_item_list : message_item
|
27
|
+
| message_item_list message_item
|
28
|
+
|
29
|
+
message_item : RULE type ident '=' INTEGER ';'
|
30
|
+
| RULE type ident '=' INTEGER '[' DEFAULT '=' ident ']' ';'
|
31
|
+
| RULE type ident '=' INTEGER '[' DEFAULT '=' number ']' ';'
|
32
|
+
| option_stmt
|
33
|
+
| extend_stmt
|
34
|
+
| message_def
|
35
|
+
| extend_def
|
36
|
+
| enum_def
|
37
|
+
|
38
|
+
type : TYPE | IDENT
|
39
|
+
|
40
|
+
number : INTEGER | FLOAT
|
41
|
+
|
42
|
+
extend_stmt | EXTENSIONS INTEGER TO INTEGER ';'
|
43
|
+
|
44
|
+
extend_def : EXTEND ident '{' message_item_list '}'
|
45
|
+
|
46
|
+
enum_def : ENUM ident '{' enum_item_list '}'
|
47
|
+
|
48
|
+
enum_item_list : enum_item
|
49
|
+
| enum_item_list enum_item
|
50
|
+
|
51
|
+
enum_item : ident '=' INTEGER ';'
|
52
|
+
|
53
|
+
service_def : SERVICE ident '{' RPC ident '(' ident ')' RETURNS '(' ident ')' '}' ';'
|
54
|
+
|
55
|
+
ident : PACKAGE | IDENT | MESSAGE | RULE | TYPE | EXTENSIONS | EXTEND | ENUM | SERVICE | RPC | RETURNS | TO | DEFAULT
|
56
|
+
|
57
|
+
---- inner
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@indent = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
RESERVED = {
|
64
|
+
'package' => :PACKAGE,
|
65
|
+
'message' => :MESSAGE,
|
66
|
+
'extensions' => :EXTENSIONS,
|
67
|
+
'extend' => :EXTEND,
|
68
|
+
'enum' => :ENUM,
|
69
|
+
'service' => :SERVICE,
|
70
|
+
'rpc' => :RPC,
|
71
|
+
'returns' => :RETURNS,
|
72
|
+
'to' => :TO,
|
73
|
+
'default' => :DEFAULT,
|
74
|
+
'required' => :RULE,
|
75
|
+
'optional' => :RULE,
|
76
|
+
'repeated' => :RULE,
|
77
|
+
'double' => :TYPE,
|
78
|
+
'float' => :TYPE,
|
79
|
+
'int32' => :TYPE,
|
80
|
+
'int64' => :TYPE,
|
81
|
+
'uint32' => :TYPE,
|
82
|
+
'uint64' => :TYPE,
|
83
|
+
'sint32' => :TYPE,
|
84
|
+
'sint64' => :TYPE,
|
85
|
+
'fixed32' => :TYPE,
|
86
|
+
'fixed64' => :TYPE,
|
87
|
+
'sfixed32' => :TYPE,
|
88
|
+
'sfixed64' => :TYPE,
|
89
|
+
'bool' => :TYPE,
|
90
|
+
'string' => :TYPE,
|
91
|
+
'bytes' => :TYPE,
|
92
|
+
}
|
93
|
+
|
94
|
+
def parse(f)
|
95
|
+
@q = []
|
96
|
+
lineno = 1
|
97
|
+
f.each do |line|
|
98
|
+
line.strip!
|
99
|
+
until line.empty? do
|
100
|
+
case line
|
101
|
+
when /\A\s+/, /\A\/\/.*/
|
102
|
+
;
|
103
|
+
when /\A[a-zA-Z_]\w*/
|
104
|
+
word = $&
|
105
|
+
@q.push [RESERVED[word] || :IDENT, [lineno, word.to_sym]]
|
106
|
+
when /\A\d+\.\d+/
|
107
|
+
@q.push [:FLOAT, [lineno, $&.to_f]]
|
108
|
+
when /\A\d+/
|
109
|
+
@q.push [:INTEGER, [lineno, $&.to_i]]
|
110
|
+
when /\A"(?:[^"\\]+|\\.)*"/, /\A'(?:[^'\\]+|\\.)*'/
|
111
|
+
@q.push [:STRING, [lineno, eval($&)]]
|
112
|
+
when /\A./
|
113
|
+
@q.push [$&, [lineno, $&]]
|
114
|
+
else
|
115
|
+
raise RuntimeError, 'must not happen'
|
116
|
+
end
|
117
|
+
line = $'
|
118
|
+
end
|
119
|
+
lineno += 1
|
120
|
+
end
|
121
|
+
@q.push [false, '$']
|
122
|
+
|
123
|
+
do_parse
|
124
|
+
end
|
125
|
+
|
126
|
+
def next_token
|
127
|
+
@q.shift
|
128
|
+
end
|
129
|
+
|
130
|
+
def on_error(t, v, values)
|
131
|
+
raise Racc::ParseError, "syntax error on #{v[1].inspect} at line.#{v[0]}"
|
132
|
+
end
|
133
|
+
|
134
|
+
---- footer
|
135
|
+
|
136
|
+
File.open(ARGV.shift, 'r') do |f|
|
137
|
+
Protobuf::Parser.new.parse f
|
138
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'protobuf/compiler'
|
2
|
+
|
3
|
+
class RubyProtobuf
|
4
|
+
VERSION = '0.0.1'
|
5
|
+
|
6
|
+
def start(proto_file, options)
|
7
|
+
unless File.exist?(proto_file)
|
8
|
+
if File.exist? "#{proto_file}.proto"
|
9
|
+
proto_file = "#{proto_file}.proto"
|
10
|
+
else
|
11
|
+
raise ArgumentError.new("#{proto_file} does not exist.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rb_filename = File.basename proto_file
|
15
|
+
rb_filename += '.rb' unless rb_filename.sub!(/.\w+$/, '.rb')
|
16
|
+
rb_filepath = "#{options[:out] || '.'}/#{rb_filename}"
|
17
|
+
puts "#{rb_filepath} writting..."
|
18
|
+
File.open(rb_filepath, 'w') do |f|
|
19
|
+
f.write Protobuf::Compiler.compile(proto_file)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
package tutorial;
|
2
|
+
|
3
|
+
message Person {
|
4
|
+
required string name = 1;
|
5
|
+
required int32 id = 2;
|
6
|
+
optional string email = 3;
|
7
|
+
|
8
|
+
enum PhoneType {
|
9
|
+
MOBILE = 0;
|
10
|
+
HOME = 1;
|
11
|
+
WORK = 2;
|
12
|
+
}
|
13
|
+
|
14
|
+
message PhoneNumber {
|
15
|
+
required string number = 1;
|
16
|
+
optional PhoneType type = 2 [default = HOME];
|
17
|
+
}
|
18
|
+
|
19
|
+
repeated PhoneNumber phone = 4;
|
20
|
+
}
|
21
|
+
|
22
|
+
message AddressBook {
|
23
|
+
repeated Person person = 1;
|
24
|
+
}
|
data/test/addressbook.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'protobuf/message'
|
2
|
+
require 'protobuf/enum'
|
3
|
+
require 'protobuf/service'
|
4
|
+
require 'protobuf/extend'
|
5
|
+
|
6
|
+
module Tutorial
|
7
|
+
class Person < Protobuf::Message
|
8
|
+
required :string, :name, 1
|
9
|
+
required :int32, :id, 2
|
10
|
+
optional :string, :email, 3
|
11
|
+
|
12
|
+
class PhoneType < Protobuf::Enum
|
13
|
+
MOBILE = 0
|
14
|
+
HOME = 1
|
15
|
+
WORK = 2
|
16
|
+
end
|
17
|
+
|
18
|
+
class PhoneNumber < Protobuf::Message
|
19
|
+
required :string, :number, 1
|
20
|
+
optional :PhoneType, :type, 2, {:default => :HOME}
|
21
|
+
end
|
22
|
+
|
23
|
+
repeated :PhoneNumber, :phone, 4
|
24
|
+
|
25
|
+
#extensions 100..199
|
26
|
+
#class Foo < Protobuf::Extend
|
27
|
+
# optional :int32, :bar, 126
|
28
|
+
#end
|
29
|
+
end
|
30
|
+
|
31
|
+
class AddressBook < Protobuf::Message
|
32
|
+
repeated :Person, :person, 1
|
33
|
+
end
|
34
|
+
|
35
|
+
#class SearchService < Protobuf::Service
|
36
|
+
# rpc :Search => :SearchRequest, :returns => :SearchResponse
|
37
|
+
#end
|
38
|
+
|
39
|
+
#Protobuf::OPTIONS[:optimize_for] = :SPEED
|
40
|
+
#Protobuf::OPTIONS[:java_package] = :'com.example.foo'
|
41
|
+
end
|
data/test/data/data.bin
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
import addressbook_pb2
|
2
|
+
import sys
|
3
|
+
|
4
|
+
person = addressbook_pb2.Person()
|
5
|
+
person.id = 1234
|
6
|
+
person.name = "John Doe"
|
7
|
+
person.email = "jdoe@example.com"
|
8
|
+
phone = person.phone.add()
|
9
|
+
phone.number = "555-4321"
|
10
|
+
phone.type = addressbook_pb2.Person.HOME
|
11
|
+
|
12
|
+
f = open('data.bin', 'wb')
|
13
|
+
f.write(person.SerializeToString())
|
14
|
+
f.close()
|
data/test/data/types.bin
ADDED
Binary file
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import types_pb2
|
2
|
+
import sys
|
3
|
+
|
4
|
+
types = types_pb2.TestTypes()
|
5
|
+
types.type1 = 0.01
|
6
|
+
types.type2 = 0.1
|
7
|
+
types.type3 = 1
|
8
|
+
types.type4 = 10
|
9
|
+
types.type5 = 100
|
10
|
+
types.type6 = 1000
|
11
|
+
types.type7 = -1
|
12
|
+
types.type8 = -10
|
13
|
+
types.type9 = 10000
|
14
|
+
types.type10 = 100000
|
15
|
+
types.type11 = False
|
16
|
+
types.type12 = 'hello all types'
|
17
|
+
# TODO test type13
|
18
|
+
#types.type13 =
|
19
|
+
|
20
|
+
f = open('types.bin', 'wb')
|
21
|
+
f.write(types.SerializeToString())
|
22
|
+
f.close()
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'protobuf/message'
|
3
|
+
require 'protobuf/enum'
|
4
|
+
require 'test/addressbook'
|
5
|
+
|
6
|
+
class AddressbookTest < Test::Unit::TestCase
|
7
|
+
def test_enum
|
8
|
+
phone_number = Tutorial::Person::PhoneNumber.new
|
9
|
+
phone_number.type = Tutorial::Person::PhoneType::MOBILE
|
10
|
+
assert_equal 0, phone_number.type
|
11
|
+
phone_number.type = Tutorial::Person::PhoneType::HOME
|
12
|
+
assert_equal 1, phone_number.type
|
13
|
+
phone_number.type = Tutorial::Person::PhoneType::WORK
|
14
|
+
assert_equal 2, phone_number.type
|
15
|
+
assert_raise TypeError do
|
16
|
+
phone_number.type = 3
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_initial_value
|
21
|
+
person = Tutorial::Person.new
|
22
|
+
assert_equal '', person.name
|
23
|
+
assert_equal 0, person.id
|
24
|
+
assert_equal [], person.phone
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_repeatable
|
28
|
+
address_book = Tutorial::AddressBook.new
|
29
|
+
assert_equal [], address_book.person
|
30
|
+
assert_instance_of Protobuf::Field::FieldArray, address_book.person
|
31
|
+
address_book.person << Tutorial::Person.new
|
32
|
+
assert_equal 1, address_book.person.size
|
33
|
+
assert_raise TypeError do
|
34
|
+
address_book.person << 1
|
35
|
+
end
|
36
|
+
assert_equal 1, address_book.person.size
|
37
|
+
address_book.person << Tutorial::Person.new
|
38
|
+
assert_equal 2, address_book.person.size
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|