protopuffs 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ module Protopuffs
4
+ class WireType
5
+ VARINT = 0
6
+ FIXED64 = 1
7
+ LENGTH_DELIMITED = 2
8
+ START_GROUP = 3
9
+ END_GROUP = 4
10
+ FIXED32 = 5
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ module Protopuffs
2
+
3
+ # The Protopuffs::Parser module holds wrapper classes that parse input
4
+ # strings using Treetop. They also set off the building of an abstract
5
+ # syntax tree from Treetop's parse tree.
6
+ module Parser
7
+
8
+ class ProtocolBufferDescriptor
9
+ def initialize
10
+ Treetop.load File.join(File.dirname(__FILE__), "protocol_buffer")
11
+ @parser = Protopuffs::ProtocolBufferParser.new
12
+ end
13
+
14
+ def parse(input)
15
+ parse_tree = @parser.parse(input)
16
+ parse_tree.build
17
+ parse_tree
18
+ rescue
19
+ raise ParseError
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,124 @@
1
+ module Protopuffs
2
+ grammar ProtocolBuffer
3
+
4
+ rule proto
5
+ s? proto_entries s? {
6
+ def messages
7
+ proto_entries.entries.select { |p| p.respond_to?(:message?) }
8
+ end
9
+
10
+ def build
11
+ messages.each { |message| message.build }
12
+ end
13
+ }
14
+ end
15
+
16
+ rule proto_entries
17
+ proto_entry rest:(s? proto_entry)* {
18
+ def entries
19
+ rest.elements.inject([proto_entry]) { |a, s_and_pe| a << s_and_pe.proto_entry }
20
+ end
21
+ }
22
+ end
23
+
24
+ rule proto_entry
25
+ message {
26
+ def message?
27
+ true
28
+ end
29
+ }
30
+ end
31
+
32
+ rule message
33
+ "message" s identifier s "{" s? body:message_body? s? "}" {
34
+ def name
35
+ identifier.text_value
36
+ end
37
+
38
+ def build
39
+ Message::Base.define_message_class(name, body.empty? ? [] : body.build)
40
+ end
41
+ }
42
+ end
43
+
44
+ rule message_body
45
+ field rest:(s? field)* {
46
+ def fields
47
+ rest.elements.inject([field]) { |a, s_and_field| a << s_and_field.field }
48
+ end
49
+
50
+ def build
51
+ fields.map { |field| field.build }
52
+ end
53
+ }
54
+ end
55
+
56
+ rule field
57
+ modifier s type s identifier s "=" s integer s? option:field_option? ";" {
58
+ def default
59
+ option.empty? ? nil : option.literal.obj
60
+ end
61
+
62
+ def build
63
+ MessageField.factory(type.text_value, modifier.text_value, identifier.text_value,
64
+ integer.text_value.to_i, default)
65
+ end
66
+ }
67
+ end
68
+
69
+ rule field_option
70
+ "[" s? "default" s? "=" s? literal s? "]"
71
+ end
72
+
73
+ rule modifier
74
+ "required" / "optional" / "repeated"
75
+ end
76
+
77
+ rule type
78
+ "double" / "float" / "int32" / "int64" / "uint32" / "uint64" /
79
+ "sint32" / "sint64" / "fixed32" / "fixed64" / "sfixed32" /
80
+ "sfixed64" / "bool" / "string" / "bytes" / identifier
81
+ end
82
+
83
+ rule literal
84
+ integer / string
85
+ end
86
+
87
+ rule integer
88
+ [\d]+ {
89
+ def obj
90
+ text_value.to_i
91
+ end
92
+ }
93
+ end
94
+
95
+ rule string
96
+ quote chars:[\w]* quote {
97
+ def obj
98
+ chars.text_value
99
+ end
100
+ }
101
+ end
102
+
103
+ rule quote
104
+ ["']
105
+ end
106
+
107
+ rule identifier
108
+ [A-Za-z_] [\w_]*
109
+ end
110
+
111
+ rule s
112
+ (whitespace / comment_to_eol)+
113
+ end
114
+
115
+ rule comment_to_eol
116
+ '//' (!"\n" .)*
117
+ end
118
+
119
+ rule whitespace
120
+ [ \t\n\r]
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{protopuffs}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris Kampmeier"]
12
+ s.date = %q{2009-09-21}
13
+ s.description = %q{A new implementation of Protocol Buffers in Ruby}
14
+ s.email = %q{chris@kampers.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ ".specification",
22
+ "LICENSE.txt",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION.yml",
26
+ "lib/protopuffs.rb",
27
+ "lib/protopuffs/message/base.rb",
28
+ "lib/protopuffs/message/field.rb",
29
+ "lib/protopuffs/message/wire_type.rb",
30
+ "lib/protopuffs/parser/parser.rb",
31
+ "lib/protopuffs/parser/protocol_buffer.treetop",
32
+ "protopuffs.gemspec",
33
+ "test/abstract_syntax_tree_test.rb",
34
+ "test/fixtures/proto/person.proto",
35
+ "test/message_base_test.rb",
36
+ "test/message_field_test.rb",
37
+ "test/parse_tree_test.rb",
38
+ "test/protopuffs_test.rb",
39
+ "test/test_helper.rb",
40
+ "test/text_format_test.rb",
41
+ "test/wire_format_test.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/chrisk/protopuffs}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubyforge_project = %q{protopuffs}
47
+ s.rubygems_version = %q{1.3.5}
48
+ s.summary = %q{Sex, drugs, and protocol buffers}
49
+ s.test_files = [
50
+ "test/abstract_syntax_tree_test.rb",
51
+ "test/message_base_test.rb",
52
+ "test/message_field_test.rb",
53
+ "test/parse_tree_test.rb",
54
+ "test/protopuffs_test.rb",
55
+ "test/test_helper.rb",
56
+ "test/text_format_test.rb",
57
+ "test/wire_format_test.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<treetop>, [">= 0"])
66
+ s.add_development_dependency(%q<mocha>, [">= 0"])
67
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
68
+ else
69
+ s.add_dependency(%q<treetop>, [">= 0"])
70
+ s.add_dependency(%q<mocha>, [">= 0"])
71
+ s.add_dependency(%q<shoulda>, [">= 0"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<treetop>, [">= 0"])
75
+ s.add_dependency(%q<mocha>, [">= 0"])
76
+ s.add_dependency(%q<shoulda>, [">= 0"])
77
+ end
78
+ end
@@ -0,0 +1,153 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class AbstractSyntaxTreeTest < Test::Unit::TestCase
6
+
7
+ context "a protocol buffer descriptor" do
8
+ setup do
9
+ @parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
10
+ end
11
+
12
+ context "with an empty Person message" do
13
+ setup do
14
+ @descriptor = "message Person {}"
15
+ end
16
+
17
+ should "create one message class" do
18
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", [])
19
+ @parser.parse(@descriptor)
20
+ end
21
+
22
+ should "not create any MessageFields" do
23
+ Protopuffs::MessageField.expects(:factory).never
24
+ @parser.parse(@descriptor)
25
+ end
26
+ end
27
+
28
+
29
+ context "with two empty messages Apple and Orange" do
30
+ setup do
31
+ @descriptor = <<-proto
32
+ message Apple {}
33
+ message Orange {}
34
+ proto
35
+ end
36
+
37
+ should "create two message classes" do
38
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Apple", []).in_sequence
39
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Orange", []).in_sequence
40
+ @parser.parse(@descriptor)
41
+ end
42
+
43
+ should "not create any MessageFields" do
44
+ Protopuffs::MessageField.expects(:factory).never
45
+ @parser.parse(@descriptor)
46
+ end
47
+ end
48
+
49
+
50
+ context "with a Person message including a name field" do
51
+ setup do
52
+ @descriptor = <<-proto
53
+ message Person {
54
+ required string name = 1;
55
+ }
56
+ proto
57
+ end
58
+
59
+ should "create one 'Person' message class with one field" do
60
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 1))
61
+ @parser.parse(@descriptor)
62
+ end
63
+
64
+ should "create one MessageField with correct options" do
65
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).
66
+ returns(stub(:tag => 1, :identifier => "name"))
67
+ @parser.parse(@descriptor)
68
+ end
69
+ end
70
+
71
+
72
+ context "with a Person message including three fields" do
73
+ setup do
74
+ @descriptor = <<-proto
75
+ message Person {
76
+ required string name = 1;
77
+ required int32 id = 2;
78
+ optional string email = 3;
79
+ }
80
+ proto
81
+ end
82
+
83
+ should "create one 'Person' message class with three fields" do
84
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
85
+ @parser.parse(@descriptor)
86
+ end
87
+
88
+ should "create three MessageFields with correct options" do
89
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).in_sequence.
90
+ returns(stub(:tag => 1, :identifier => "name"))
91
+ Protopuffs::MessageField.expects(:factory).once.with("int32", "required", "id", 2, nil).in_sequence.
92
+ returns(stub(:tag => 2, :identifier => "id"))
93
+ Protopuffs::MessageField.expects(:factory).once.with("string", "optional", "email", 3, nil).in_sequence.
94
+ returns(stub(:tag => 3, :identifier => "email"))
95
+ @parser.parse(@descriptor)
96
+ end
97
+ end
98
+
99
+
100
+ context "with a message including two fields with defaults and one without" do
101
+ setup do
102
+ @descriptor = <<-proto
103
+ message Person {
104
+ required string name = 1;
105
+ optional string language = 2 [default = "en"];
106
+ optional int32 account_code = 3 [default = 0];
107
+ }
108
+ proto
109
+ end
110
+
111
+ should "create one 'Person' message class with three fields" do
112
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 3))
113
+ @parser.parse(@descriptor)
114
+ end
115
+
116
+ should "create three MessageFields with correct options" do
117
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).in_sequence.
118
+ returns(stub(:tag => 1, :identifier => "name"))
119
+ Protopuffs::MessageField.expects(:factory).once.with("string", "optional", "language", 2, "en").in_sequence.
120
+ returns(stub(:tag => 2, :identifier => "language"))
121
+ Protopuffs::MessageField.expects(:factory).once.with("int32", "optional", "account_code", 3, 0).in_sequence.
122
+ returns(stub(:tag => 3, :identifier => "account_code"))
123
+ @parser.parse(@descriptor)
124
+ end
125
+ end
126
+
127
+
128
+ context "with a message including a user-typed field" do
129
+ setup do
130
+ @descriptor = <<-proto
131
+ message Person {
132
+ required string name = 1;
133
+ repeated Address addresses = 2;
134
+ }
135
+ proto
136
+ end
137
+
138
+ should "create one 'Person' message class with two fields" do
139
+ Protopuffs::Message::Base.expects(:define_message_class).once.with("Person", responds_with(:size, 2))
140
+ @parser.parse(@descriptor)
141
+ end
142
+
143
+ should "create two MessageFields with correct options" do
144
+ Protopuffs::MessageField.expects(:factory).once.with("string", "required", "name", 1, nil).in_sequence.
145
+ returns(stub(:tag => 1, :identifier => "name"))
146
+ Protopuffs::MessageField.expects(:factory).once.with("Address", "repeated", "addresses", 2, nil).in_sequence.
147
+ returns(stub(:tag => 2, :identifier => "addresses"))
148
+ @parser.parse(@descriptor)
149
+ end
150
+ end
151
+ end
152
+
153
+ end
@@ -0,0 +1,5 @@
1
+ message Person {
2
+ required string name = 1;
3
+ required int32 id = 2;
4
+ optional string email = 3;
5
+ }
@@ -0,0 +1,202 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class MessageBaseTest < Test::Unit::TestCase
6
+
7
+ context ".define_message_class using 'Person' and two fields" do
8
+ should "create a Message::Person class" do
9
+ fields = [Protopuffs::String.new("optional", "name", 1),
10
+ Protopuffs::String.new("optional", "address", 2)]
11
+ Protopuffs::Message::Base.define_message_class("Person", fields)
12
+ Protopuffs::Message::Person
13
+ end
14
+
15
+ should "create a class with accessors for each field" do
16
+ fields = [Protopuffs::String.new("optional", "name", 1),
17
+ Protopuffs::String.new("optional", "address", 2)]
18
+ Protopuffs::Message::Base.define_message_class("Person", fields)
19
+ person = Protopuffs::Message::Person.new
20
+ person.name = "Chris"
21
+ person.address = "61 Carmelita St."
22
+ assert_equal person.name, "Chris"
23
+ assert_equal person.address, "61 Carmelita St."
24
+ end
25
+ end
26
+
27
+
28
+ context ".define_message_class with fields that have duplicate tags" do
29
+ should "raise a Protopuffs::ParseError" do
30
+ fields = [Protopuffs::Int32.new("optional", "name", 1),
31
+ Protopuffs::String.new("optional", "address", 1)]
32
+ assert_raises Protopuffs::ParseError do
33
+ Protopuffs::Message::Base.define_message_class("Person", fields)
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+ context ".define_message_class with fields that have invalid tags" do
40
+ should "raise a Protopuffs::ParseError when a tag is too large" do
41
+ fields = [Protopuffs::Int32.new("optional", "name", 1),
42
+ Protopuffs::String.new("optional", "address", 536_870_912)]
43
+ assert_raises Protopuffs::ParseError do
44
+ Protopuffs::Message::Base.define_message_class("Person", fields)
45
+ end
46
+ end
47
+
48
+ should "raise a Protopuffs::ParseError when a tag is too small" do
49
+ fields = [Protopuffs::Int32.new("optional", "name", 0),
50
+ Protopuffs::String.new("optional", "address", 1)]
51
+ assert_raises Protopuffs::ParseError do
52
+ Protopuffs::Message::Base.define_message_class("Person", fields)
53
+ end
54
+ end
55
+
56
+ should "raise a Protopuffs::ParseError when a tag is reserved" do
57
+ fields = [Protopuffs::String.new("optional", "name", 19050)]
58
+ assert_raises Protopuffs::ParseError do
59
+ Protopuffs::Message::Base.define_message_class("Person", fields)
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ context ".define_message_class with a message name that's Camel_Scored" do
66
+ # violates the Google style guide, but the other implementations handle this
67
+ should "strip the underscore when creating the class" do
68
+ Protopuffs::Message::Base.define_message_class("User_Image", [])
69
+ assert !Protopuffs::Message.const_defined?("User_Image")
70
+ assert Protopuffs::Message.const_defined?("UserImage")
71
+ end
72
+
73
+ context "and an embedded-message field that's Camel_Scored" do
74
+ should "strip the underscore when instantiating the class of the field" do
75
+ child_fields = [Protopuffs::String.new("optional", "street", 1)]
76
+ Protopuffs::Message::Base.define_message_class("User_Address", child_fields)
77
+ parent_fields = [Protopuffs::Embedded.new("User_Address", "required", "user_address", 1)]
78
+ Protopuffs::Message::Base.define_message_class("User_Info", parent_fields)
79
+
80
+ child = Protopuffs::Message::UserAddress.new(:street => "400 2nd Street")
81
+ parent = Protopuffs::Message::UserInfo.new(child.to_wire_format)
82
+
83
+ assert_equal Protopuffs::Message::UserAddress, parent.user_address.class
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+ should "not allow you to instantiate Message::Base directly" do
90
+ assert_raises RuntimeError do
91
+ Protopuffs::Message::Base.new
92
+ end
93
+ end
94
+
95
+
96
+ context "comparing messages with #==" do
97
+ should "return false when the messages have different types" do
98
+ Protopuffs::Message::Base.define_message_class("Dog", [])
99
+ Protopuffs::Message::Base.define_message_class("Cat", [])
100
+ assert_not_equal Protopuffs::Message::Dog.new, Protopuffs::Message::Cat.new
101
+ end
102
+
103
+ should "return false when the messages' fields have different values" do
104
+ fields = [Protopuffs::String.new("optional", "name", 1)]
105
+ Protopuffs::Message::Base.define_message_class("Person", fields)
106
+ alice = Protopuffs::Message::Person.new
107
+ alice.name = "Alice"
108
+ bob = Protopuffs::Message::Person.new
109
+ bob.name = "Bob"
110
+ assert_not_equal alice, bob
111
+ end
112
+
113
+ should "return true when messages of the same type have the same field values" do
114
+ fields = [Protopuffs::String.new("optional", "name", 1)]
115
+ Protopuffs::Message::Base.define_message_class("Sheep", fields)
116
+ sheep = Protopuffs::Message::Sheep.new
117
+ sheep.name = "Dolly"
118
+ sheep2 = Protopuffs::Message::Sheep.new
119
+ sheep2.name = "Dolly"
120
+ assert_equal sheep, sheep2
121
+ end
122
+ end
123
+
124
+
125
+ context "instantiating a message class" do
126
+ setup do
127
+ fields = [Protopuffs::String.new("required", "title", 2)]
128
+ Protopuffs::Message::Base.define_message_class("Book", fields)
129
+ end
130
+
131
+ should "optionally accept a wire-format encoded IO-style object and populate the fields" do
132
+ input = StringIO.new([0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67].pack("C*"))
133
+ message = Protopuffs::Message::Book.new(input)
134
+ assert_equal "testing", message.title
135
+ end
136
+
137
+ should "optionally accept a wire-format encoded string and populate the fields" do
138
+ input = [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67].pack("C*")
139
+ message = Protopuffs::Message::Book.new(input)
140
+ assert_equal "testing", message.title
141
+ end
142
+
143
+ should "not populate the fields if no argument is present" do
144
+ message = Protopuffs::Message::Book.new
145
+ assert_nil message.title
146
+ end
147
+
148
+ should "optionally accept a hash of field values and mass-assign them" do
149
+ message = Protopuffs::Message::Book.new(:title => "testing")
150
+ assert_equal "testing", message.title
151
+ end
152
+ end
153
+
154
+
155
+ context "mass-assignment of field values via #attributes=" do
156
+ setup do
157
+ fields = [Protopuffs::String.new("required", "title", 1),
158
+ Protopuffs::String.new("required", "author", 2),
159
+ Protopuffs::Int32.new("optional", "edition", 3)]
160
+ Protopuffs::Message::Base.define_message_class("Book", fields)
161
+ end
162
+
163
+ should "set each field value to the corresponding entry in the argument hash" do
164
+ message = Protopuffs::Message::Book.new
165
+ message.attributes = {:title => "You Shall Know Our Velocity",
166
+ :author => "Dave Eggers",
167
+ :edition => 2}
168
+ assert_equal "You Shall Know Our Velocity", message.title
169
+ assert_equal "Dave Eggers", message.author
170
+ assert_equal 2, message.edition
171
+ end
172
+
173
+ should "ignore unknown fields in the argument hash" do
174
+ message = Protopuffs::Message::Book.new
175
+ message.attributes = {:title => "You Shall Know Our Velocity",
176
+ :author => "Dave Eggers",
177
+ :edition => 2,
178
+ :isbn => "0970335555"}
179
+ assert_equal "You Shall Know Our Velocity", message.title
180
+ assert_equal "Dave Eggers", message.author
181
+ assert_equal 2, message.edition
182
+ end
183
+
184
+ should "only assign fields present in the argument hash" do
185
+ message = Protopuffs::Message::Book.new
186
+ message.title = "You Shall Know Our Velocity"
187
+ message.attributes = {:author => "Dave Eggers"}
188
+ assert_equal "Dave Eggers", message.author
189
+ assert_equal "You Shall Know Our Velocity", message.title
190
+ assert_nil message.edition
191
+ end
192
+
193
+ should "not assign defaults for optional fields missing from the argument hash" do
194
+ message = Protopuffs::Message::Book.new
195
+ message.attributes = {:author => "Dave Eggers"}
196
+ assert_equal "Dave Eggers", message.author
197
+ assert_nil message.title
198
+ assert_nil message.edition
199
+ end
200
+ end
201
+
202
+ end