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,4 @@
1
+ .DS_Store
2
+ /coverage/
3
+ /pkg/
4
+ /rdoc/
@@ -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
+
@@ -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.
@@ -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)
@@ -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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 3
4
+ :patch: 0
@@ -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
+