protopuffs 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+