ruby_protobuf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 @@
1
+ module Protobuf
2
+ class Service
3
+ def self.rpc(hash)
4
+ raise NotImplementedError('TODO')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Protobuf
2
+ class WireType
3
+ VARINT = 0
4
+ FIXED64 = 1
5
+ LENGTH_DELIMITED = 2
6
+ START_GROUP = 3
7
+ END_GROUP = 4
8
+ FIXED32 = 5
9
+ end
10
+ 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
+ }
@@ -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
@@ -0,0 +1,3 @@
1
+
2
+ John Doe� jdoe@example.com"
3
+ 555-4321
@@ -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()
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
+