Skip to content

Commit e66fb73

Browse files
committed
Supports text formatting and parsing as the C++/Python/Java/Go
implementations do.
1 parent eaf1930 commit e66fb73

File tree

11 files changed

+1117
-1
lines changed

11 files changed

+1117
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ prof
1212
rdoc
1313
tests
1414
tmp
15+
/lib/protocol_buffers/runtime/text_parser.rb

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ end
142142
* passing on unknown fields when re-serializing a message
143143
* groups
144144
* RPC stubbing
145+
* formatting to and parsing from text format
145146

146147
### Unsupported Features
147148

Rakefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@ require "bundler/gem_tasks"
33
Dir['tasks/**/*.rake'].each { |t| load t }
44

55
task :default => [:spec]
6+
7+
file 'lib/protocol_buffers/runtime/text_parser.rb' => 'lib/protocol_buffers/runtime/text_parser.ry' do |t|
8+
sh 'racc', '-o', t.name, *t.prerequisites
9+
end
10+
11+
task :text_parser => 'lib/protocol_buffers/runtime/text_parser.rb'
12+
task :spec => :text_parser
13+
task :build => :text_parser

lib/protocol_buffers.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ def self.bin_sio(*args)
88
sio.external_encoding != Encoding::BINARY
99
sio
1010
end
11+
12+
def self.utf8_sio(*args)
13+
sio = StringIO.new(*args)
14+
sio.set_encoding('utf-8') if
15+
sio.respond_to?(:set_encoding) and
16+
sio.external_encoding != Encoding::UTF_8
17+
sio
18+
end
1119
end
1220

1321
require 'protocol_buffers/version'

lib/protocol_buffers/runtime/field.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ def deserialize(value)
242242
value
243243
end
244244

245+
def text_format(io, value, options = nil)
246+
io.write value.to_s
247+
end
248+
245249
module WireFormats
246250
module LENGTH_DELIMITED
247251
def wire_type
@@ -304,6 +308,11 @@ def default_value
304308
def deserialize(value)
305309
value.read
306310
end
311+
312+
def text_format(io, value, options = nil)
313+
value = value.unpack("C*").map { |b| "\\x%02x" % b }.join(nil)
314+
io.write "\"#{value}\""
315+
end
307316
end
308317

309318
class StringField < BytesField
@@ -332,6 +341,14 @@ def deserialize(value)
332341
read_value
333342
end
334343
end
344+
345+
def text_format(io, value, options = nil)
346+
if HAS_ENCODING
347+
io.write value.dup.force_encoding(Encoding::ASCII_8BIT).dump
348+
else
349+
io.write value.dump
350+
end
351+
end
335352
end
336353

337354
class NumericField < Field
@@ -583,9 +600,18 @@ def default_value
583600
@opts[:default] || @valid_values.first
584601
end
585602

603+
def value_from_name(name)
604+
@proxy_enum.name_to_value_map[name.to_sym]
605+
end
606+
586607
def inspect_value(value)
587608
"#{@value_to_name[value]}(#{value})"
588609
end
610+
611+
def text_format(io, value, options = nil)
612+
formatted = @value_to_name[value] || value.to_s
613+
io.write formatted
614+
end
589615
end
590616

591617
class AggregateField < Field
@@ -614,6 +640,24 @@ def serialize(value)
614640
def deserialize(io)
615641
@proxy_class.parse(io)
616642
end
643+
644+
def text_format(io, value, options = nil)
645+
options = options.dup
646+
options[:nest] ||= 0
647+
if options[:short]
648+
indent = ""
649+
newline = " "
650+
else
651+
indent = " " * options[:nest]
652+
newline = "\n"
653+
end
654+
options[:nest] += 1
655+
656+
io.write "{#{newline}"
657+
value.text_format(io, options)
658+
io.write " " if options[:short]
659+
io.write "#{indent}}"
660+
end
617661
end
618662

619663
class MessageField < AggregateField

lib/protocol_buffers/runtime/message.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
require 'protocol_buffers/runtime/field'
33
require 'protocol_buffers/runtime/encoder'
44
require 'protocol_buffers/runtime/decoder'
5+
require 'protocol_buffers/runtime/text_formatter'
6+
require 'protocol_buffers/runtime/text_parser'
57

68
module ProtocolBuffers
79

@@ -257,6 +259,19 @@ def serialize_to_string
257259
end
258260
alias_method :to_s, :serialize_to_string
259261

262+
# Format this message into the given IO stream using the text format of Protocol Buffers.
263+
def text_format(io, options = nil)
264+
formatter = TextFormatter.new(options)
265+
formatter.format(io, self)
266+
end
267+
268+
# Format this message into a text and return it.
269+
def text_format_to_string(options = nil)
270+
sio = ProtocolBuffers.utf8_sio
271+
text_format(sio, options)
272+
return sio.string
273+
end
274+
260275
def to_hash
261276
self.class.to_hash(self)
262277
end
@@ -296,6 +311,19 @@ def self.parse(io)
296311
self.new.parse(io)
297312
end
298313

314+
# Parse the text as a text representation of this class, and merge the parsed fields
315+
# into the current message.
316+
def parse_from_text(text)
317+
parser = TextParser.new
318+
parser.parse_text(text, self)
319+
return self
320+
end
321+
322+
# Shortcut, simply calls self.new.parse_from_text(text)
323+
def self.parse_from_text(text)
324+
self.new.parse_from_text(text)
325+
end
326+
299327
# Merge the attribute values from +obj+ into this Message, which must be of
300328
# the same class.
301329
#
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
module ProtocolBuffers
2+
class TextFormatter
3+
def initialize(options = nil)
4+
@options = options || {}
5+
end
6+
7+
def format(io, message, options = nil)
8+
message.validate!
9+
options ||= {}
10+
options = options.merge(@options)
11+
options[:nest] ||= 0
12+
13+
if options[:short]
14+
indent = ""
15+
newline = " "
16+
else
17+
indent = " " * options[:nest]
18+
newline = "\n"
19+
end
20+
21+
sep = ""
22+
message.fields.each do |tag, field|
23+
next unless message.value_for_tag?(tag)
24+
value = message.value_for_tag(tag)
25+
if field.repeated?
26+
next if value.size == 0
27+
value.each do |v|
28+
io.write sep; sep = newline
29+
30+
format_field(io, field, v, indent, newline, options)
31+
end
32+
else
33+
io.write sep; sep = newline
34+
35+
format_field(io, field, value, indent, newline, options)
36+
end
37+
end
38+
39+
message.each_unknown_field do |tag_int, value|
40+
io.write sep; sep = newline
41+
42+
wire_type = tag_int & 0x7
43+
id = tag_int >> 3
44+
format_unknown_field(io, wire_type, id, value, options)
45+
end
46+
47+
io.write sep if !options[:short]
48+
49+
io
50+
end
51+
52+
def format_field(io, field, value, indent, newline, options)
53+
if field.kind_of? Field::GroupField
54+
name = value.class.name.sub(/\A.*::/, '')
55+
else
56+
name = field.name
57+
end
58+
59+
io.write "#{indent}#{name}"
60+
if field.kind_of? Field::AggregateField
61+
io.write " "
62+
else
63+
io.write ": "
64+
end
65+
field.text_format(io, value, options)
66+
end
67+
68+
def format_unknown_field(io, wire_type, id, value, options)
69+
options = options.dup
70+
options[:nest] ||= 0
71+
72+
if options[:short]
73+
indent = ""
74+
newline = " "
75+
else
76+
indent = " " * options[:nest]
77+
newline = "\n"
78+
end
79+
80+
if wire_type == 3
81+
options[:nest] += 1
82+
83+
io.write "#{indent}#{id} {#{newline}"
84+
else
85+
io.write "#{indent}#{id}: "
86+
end
87+
88+
case wire_type
89+
when 0 # VARINT
90+
io.write "#{value}"
91+
92+
when 1 # FIXED64
93+
lo, hi = value.unpack("V2")
94+
io.write "0x%016x" % (hi << 32 | lo)
95+
96+
when 5 # FIXED32
97+
io.write "0x%08x" % value.unpack("V")
98+
99+
when 2 # LENGTH_DELIMITED
100+
value = value.unpack("C*").map { |b| "\\x%02x" % b }.join(nil)
101+
io.write "\"#{value}\""
102+
103+
when 3 # START_GROUP
104+
format(io, value, options)
105+
106+
when 4 # END_GROUP: never appear
107+
raise(EncodeError, "Unexpected wire type END_GROUP")
108+
else
109+
raise(EncodeError, "unknown wire type: #{wire_type}")
110+
end
111+
if wire_type == 3
112+
io.write "#{indent}}"
113+
end
114+
end
115+
end
116+
end

0 commit comments

Comments
 (0)