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.
@@ -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