1 | // Copyright 2010 The Go Authors. All rights reserved.
|
---|
2 | // Use of this source code is governed by a BSD-style
|
---|
3 | // license that can be found in the LICENSE file.
|
---|
4 |
|
---|
5 | package proto
|
---|
6 |
|
---|
7 | import (
|
---|
8 | "bytes"
|
---|
9 | "encoding"
|
---|
10 | "fmt"
|
---|
11 | "io"
|
---|
12 | "math"
|
---|
13 | "sort"
|
---|
14 | "strings"
|
---|
15 |
|
---|
16 | "google.golang.org/protobuf/encoding/prototext"
|
---|
17 | "google.golang.org/protobuf/encoding/protowire"
|
---|
18 | "google.golang.org/protobuf/proto"
|
---|
19 | "google.golang.org/protobuf/reflect/protoreflect"
|
---|
20 | "google.golang.org/protobuf/reflect/protoregistry"
|
---|
21 | )
|
---|
22 |
|
---|
23 | const wrapTextMarshalV2 = false
|
---|
24 |
|
---|
25 | // TextMarshaler is a configurable text format marshaler.
|
---|
26 | type TextMarshaler struct {
|
---|
27 | Compact bool // use compact text format (one line)
|
---|
28 | ExpandAny bool // expand google.protobuf.Any messages of known types
|
---|
29 | }
|
---|
30 |
|
---|
31 | // Marshal writes the proto text format of m to w.
|
---|
32 | func (tm *TextMarshaler) Marshal(w io.Writer, m Message) error {
|
---|
33 | b, err := tm.marshal(m)
|
---|
34 | if len(b) > 0 {
|
---|
35 | if _, err := w.Write(b); err != nil {
|
---|
36 | return err
|
---|
37 | }
|
---|
38 | }
|
---|
39 | return err
|
---|
40 | }
|
---|
41 |
|
---|
42 | // Text returns a proto text formatted string of m.
|
---|
43 | func (tm *TextMarshaler) Text(m Message) string {
|
---|
44 | b, _ := tm.marshal(m)
|
---|
45 | return string(b)
|
---|
46 | }
|
---|
47 |
|
---|
48 | func (tm *TextMarshaler) marshal(m Message) ([]byte, error) {
|
---|
49 | mr := MessageReflect(m)
|
---|
50 | if mr == nil || !mr.IsValid() {
|
---|
51 | return []byte("<nil>"), nil
|
---|
52 | }
|
---|
53 |
|
---|
54 | if wrapTextMarshalV2 {
|
---|
55 | if m, ok := m.(encoding.TextMarshaler); ok {
|
---|
56 | return m.MarshalText()
|
---|
57 | }
|
---|
58 |
|
---|
59 | opts := prototext.MarshalOptions{
|
---|
60 | AllowPartial: true,
|
---|
61 | EmitUnknown: true,
|
---|
62 | }
|
---|
63 | if !tm.Compact {
|
---|
64 | opts.Indent = " "
|
---|
65 | }
|
---|
66 | if !tm.ExpandAny {
|
---|
67 | opts.Resolver = (*protoregistry.Types)(nil)
|
---|
68 | }
|
---|
69 | return opts.Marshal(mr.Interface())
|
---|
70 | } else {
|
---|
71 | w := &textWriter{
|
---|
72 | compact: tm.Compact,
|
---|
73 | expandAny: tm.ExpandAny,
|
---|
74 | complete: true,
|
---|
75 | }
|
---|
76 |
|
---|
77 | if m, ok := m.(encoding.TextMarshaler); ok {
|
---|
78 | b, err := m.MarshalText()
|
---|
79 | if err != nil {
|
---|
80 | return nil, err
|
---|
81 | }
|
---|
82 | w.Write(b)
|
---|
83 | return w.buf, nil
|
---|
84 | }
|
---|
85 |
|
---|
86 | err := w.writeMessage(mr)
|
---|
87 | return w.buf, err
|
---|
88 | }
|
---|
89 | }
|
---|
90 |
|
---|
91 | var (
|
---|
92 | defaultTextMarshaler = TextMarshaler{}
|
---|
93 | compactTextMarshaler = TextMarshaler{Compact: true}
|
---|
94 | )
|
---|
95 |
|
---|
96 | // MarshalText writes the proto text format of m to w.
|
---|
97 | func MarshalText(w io.Writer, m Message) error { return defaultTextMarshaler.Marshal(w, m) }
|
---|
98 |
|
---|
99 | // MarshalTextString returns a proto text formatted string of m.
|
---|
100 | func MarshalTextString(m Message) string { return defaultTextMarshaler.Text(m) }
|
---|
101 |
|
---|
102 | // CompactText writes the compact proto text format of m to w.
|
---|
103 | func CompactText(w io.Writer, m Message) error { return compactTextMarshaler.Marshal(w, m) }
|
---|
104 |
|
---|
105 | // CompactTextString returns a compact proto text formatted string of m.
|
---|
106 | func CompactTextString(m Message) string { return compactTextMarshaler.Text(m) }
|
---|
107 |
|
---|
108 | var (
|
---|
109 | newline = []byte("\n")
|
---|
110 | endBraceNewline = []byte("}\n")
|
---|
111 | posInf = []byte("inf")
|
---|
112 | negInf = []byte("-inf")
|
---|
113 | nan = []byte("nan")
|
---|
114 | )
|
---|
115 |
|
---|
116 | // textWriter is an io.Writer that tracks its indentation level.
|
---|
117 | type textWriter struct {
|
---|
118 | compact bool // same as TextMarshaler.Compact
|
---|
119 | expandAny bool // same as TextMarshaler.ExpandAny
|
---|
120 | complete bool // whether the current position is a complete line
|
---|
121 | indent int // indentation level; never negative
|
---|
122 | buf []byte
|
---|
123 | }
|
---|
124 |
|
---|
125 | func (w *textWriter) Write(p []byte) (n int, _ error) {
|
---|
126 | newlines := bytes.Count(p, newline)
|
---|
127 | if newlines == 0 {
|
---|
128 | if !w.compact && w.complete {
|
---|
129 | w.writeIndent()
|
---|
130 | }
|
---|
131 | w.buf = append(w.buf, p...)
|
---|
132 | w.complete = false
|
---|
133 | return len(p), nil
|
---|
134 | }
|
---|
135 |
|
---|
136 | frags := bytes.SplitN(p, newline, newlines+1)
|
---|
137 | if w.compact {
|
---|
138 | for i, frag := range frags {
|
---|
139 | if i > 0 {
|
---|
140 | w.buf = append(w.buf, ' ')
|
---|
141 | n++
|
---|
142 | }
|
---|
143 | w.buf = append(w.buf, frag...)
|
---|
144 | n += len(frag)
|
---|
145 | }
|
---|
146 | return n, nil
|
---|
147 | }
|
---|
148 |
|
---|
149 | for i, frag := range frags {
|
---|
150 | if w.complete {
|
---|
151 | w.writeIndent()
|
---|
152 | }
|
---|
153 | w.buf = append(w.buf, frag...)
|
---|
154 | n += len(frag)
|
---|
155 | if i+1 < len(frags) {
|
---|
156 | w.buf = append(w.buf, '\n')
|
---|
157 | n++
|
---|
158 | }
|
---|
159 | }
|
---|
160 | w.complete = len(frags[len(frags)-1]) == 0
|
---|
161 | return n, nil
|
---|
162 | }
|
---|
163 |
|
---|
164 | func (w *textWriter) WriteByte(c byte) error {
|
---|
165 | if w.compact && c == '\n' {
|
---|
166 | c = ' '
|
---|
167 | }
|
---|
168 | if !w.compact && w.complete {
|
---|
169 | w.writeIndent()
|
---|
170 | }
|
---|
171 | w.buf = append(w.buf, c)
|
---|
172 | w.complete = c == '\n'
|
---|
173 | return nil
|
---|
174 | }
|
---|
175 |
|
---|
176 | func (w *textWriter) writeName(fd protoreflect.FieldDescriptor) {
|
---|
177 | if !w.compact && w.complete {
|
---|
178 | w.writeIndent()
|
---|
179 | }
|
---|
180 | w.complete = false
|
---|
181 |
|
---|
182 | if fd.Kind() != protoreflect.GroupKind {
|
---|
183 | w.buf = append(w.buf, fd.Name()...)
|
---|
184 | w.WriteByte(':')
|
---|
185 | } else {
|
---|
186 | // Use message type name for group field name.
|
---|
187 | w.buf = append(w.buf, fd.Message().Name()...)
|
---|
188 | }
|
---|
189 |
|
---|
190 | if !w.compact {
|
---|
191 | w.WriteByte(' ')
|
---|
192 | }
|
---|
193 | }
|
---|
194 |
|
---|
195 | func requiresQuotes(u string) bool {
|
---|
196 | // When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
---|
197 | for _, ch := range u {
|
---|
198 | switch {
|
---|
199 | case ch == '.' || ch == '/' || ch == '_':
|
---|
200 | continue
|
---|
201 | case '0' <= ch && ch <= '9':
|
---|
202 | continue
|
---|
203 | case 'A' <= ch && ch <= 'Z':
|
---|
204 | continue
|
---|
205 | case 'a' <= ch && ch <= 'z':
|
---|
206 | continue
|
---|
207 | default:
|
---|
208 | return true
|
---|
209 | }
|
---|
210 | }
|
---|
211 | return false
|
---|
212 | }
|
---|
213 |
|
---|
214 | // writeProto3Any writes an expanded google.protobuf.Any message.
|
---|
215 | //
|
---|
216 | // It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
---|
217 | // required messages are not linked in).
|
---|
218 | //
|
---|
219 | // It returns (true, error) when sv was written in expanded format or an error
|
---|
220 | // was encountered.
|
---|
221 | func (w *textWriter) writeProto3Any(m protoreflect.Message) (bool, error) {
|
---|
222 | md := m.Descriptor()
|
---|
223 | fdURL := md.Fields().ByName("type_url")
|
---|
224 | fdVal := md.Fields().ByName("value")
|
---|
225 |
|
---|
226 | url := m.Get(fdURL).String()
|
---|
227 | mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
|
---|
228 | if err != nil {
|
---|
229 | return false, nil
|
---|
230 | }
|
---|
231 |
|
---|
232 | b := m.Get(fdVal).Bytes()
|
---|
233 | m2 := mt.New()
|
---|
234 | if err := proto.Unmarshal(b, m2.Interface()); err != nil {
|
---|
235 | return false, nil
|
---|
236 | }
|
---|
237 | w.Write([]byte("["))
|
---|
238 | if requiresQuotes(url) {
|
---|
239 | w.writeQuotedString(url)
|
---|
240 | } else {
|
---|
241 | w.Write([]byte(url))
|
---|
242 | }
|
---|
243 | if w.compact {
|
---|
244 | w.Write([]byte("]:<"))
|
---|
245 | } else {
|
---|
246 | w.Write([]byte("]: <\n"))
|
---|
247 | w.indent++
|
---|
248 | }
|
---|
249 | if err := w.writeMessage(m2); err != nil {
|
---|
250 | return true, err
|
---|
251 | }
|
---|
252 | if w.compact {
|
---|
253 | w.Write([]byte("> "))
|
---|
254 | } else {
|
---|
255 | w.indent--
|
---|
256 | w.Write([]byte(">\n"))
|
---|
257 | }
|
---|
258 | return true, nil
|
---|
259 | }
|
---|
260 |
|
---|
261 | func (w *textWriter) writeMessage(m protoreflect.Message) error {
|
---|
262 | md := m.Descriptor()
|
---|
263 | if w.expandAny && md.FullName() == "google.protobuf.Any" {
|
---|
264 | if canExpand, err := w.writeProto3Any(m); canExpand {
|
---|
265 | return err
|
---|
266 | }
|
---|
267 | }
|
---|
268 |
|
---|
269 | fds := md.Fields()
|
---|
270 | for i := 0; i < fds.Len(); {
|
---|
271 | fd := fds.Get(i)
|
---|
272 | if od := fd.ContainingOneof(); od != nil {
|
---|
273 | fd = m.WhichOneof(od)
|
---|
274 | i += od.Fields().Len()
|
---|
275 | } else {
|
---|
276 | i++
|
---|
277 | }
|
---|
278 | if fd == nil || !m.Has(fd) {
|
---|
279 | continue
|
---|
280 | }
|
---|
281 |
|
---|
282 | switch {
|
---|
283 | case fd.IsList():
|
---|
284 | lv := m.Get(fd).List()
|
---|
285 | for j := 0; j < lv.Len(); j++ {
|
---|
286 | w.writeName(fd)
|
---|
287 | v := lv.Get(j)
|
---|
288 | if err := w.writeSingularValue(v, fd); err != nil {
|
---|
289 | return err
|
---|
290 | }
|
---|
291 | w.WriteByte('\n')
|
---|
292 | }
|
---|
293 | case fd.IsMap():
|
---|
294 | kfd := fd.MapKey()
|
---|
295 | vfd := fd.MapValue()
|
---|
296 | mv := m.Get(fd).Map()
|
---|
297 |
|
---|
298 | type entry struct{ key, val protoreflect.Value }
|
---|
299 | var entries []entry
|
---|
300 | mv.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
|
---|
301 | entries = append(entries, entry{k.Value(), v})
|
---|
302 | return true
|
---|
303 | })
|
---|
304 | sort.Slice(entries, func(i, j int) bool {
|
---|
305 | switch kfd.Kind() {
|
---|
306 | case protoreflect.BoolKind:
|
---|
307 | return !entries[i].key.Bool() && entries[j].key.Bool()
|
---|
308 | case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
|
---|
309 | return entries[i].key.Int() < entries[j].key.Int()
|
---|
310 | case protoreflect.Uint32Kind, protoreflect.Fixed32Kind, protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
|
---|
311 | return entries[i].key.Uint() < entries[j].key.Uint()
|
---|
312 | case protoreflect.StringKind:
|
---|
313 | return entries[i].key.String() < entries[j].key.String()
|
---|
314 | default:
|
---|
315 | panic("invalid kind")
|
---|
316 | }
|
---|
317 | })
|
---|
318 | for _, entry := range entries {
|
---|
319 | w.writeName(fd)
|
---|
320 | w.WriteByte('<')
|
---|
321 | if !w.compact {
|
---|
322 | w.WriteByte('\n')
|
---|
323 | }
|
---|
324 | w.indent++
|
---|
325 | w.writeName(kfd)
|
---|
326 | if err := w.writeSingularValue(entry.key, kfd); err != nil {
|
---|
327 | return err
|
---|
328 | }
|
---|
329 | w.WriteByte('\n')
|
---|
330 | w.writeName(vfd)
|
---|
331 | if err := w.writeSingularValue(entry.val, vfd); err != nil {
|
---|
332 | return err
|
---|
333 | }
|
---|
334 | w.WriteByte('\n')
|
---|
335 | w.indent--
|
---|
336 | w.WriteByte('>')
|
---|
337 | w.WriteByte('\n')
|
---|
338 | }
|
---|
339 | default:
|
---|
340 | w.writeName(fd)
|
---|
341 | if err := w.writeSingularValue(m.Get(fd), fd); err != nil {
|
---|
342 | return err
|
---|
343 | }
|
---|
344 | w.WriteByte('\n')
|
---|
345 | }
|
---|
346 | }
|
---|
347 |
|
---|
348 | if b := m.GetUnknown(); len(b) > 0 {
|
---|
349 | w.writeUnknownFields(b)
|
---|
350 | }
|
---|
351 | return w.writeExtensions(m)
|
---|
352 | }
|
---|
353 |
|
---|
354 | func (w *textWriter) writeSingularValue(v protoreflect.Value, fd protoreflect.FieldDescriptor) error {
|
---|
355 | switch fd.Kind() {
|
---|
356 | case protoreflect.FloatKind, protoreflect.DoubleKind:
|
---|
357 | switch vf := v.Float(); {
|
---|
358 | case math.IsInf(vf, +1):
|
---|
359 | w.Write(posInf)
|
---|
360 | case math.IsInf(vf, -1):
|
---|
361 | w.Write(negInf)
|
---|
362 | case math.IsNaN(vf):
|
---|
363 | w.Write(nan)
|
---|
364 | default:
|
---|
365 | fmt.Fprint(w, v.Interface())
|
---|
366 | }
|
---|
367 | case protoreflect.StringKind:
|
---|
368 | // NOTE: This does not validate UTF-8 for historical reasons.
|
---|
369 | w.writeQuotedString(string(v.String()))
|
---|
370 | case protoreflect.BytesKind:
|
---|
371 | w.writeQuotedString(string(v.Bytes()))
|
---|
372 | case protoreflect.MessageKind, protoreflect.GroupKind:
|
---|
373 | var bra, ket byte = '<', '>'
|
---|
374 | if fd.Kind() == protoreflect.GroupKind {
|
---|
375 | bra, ket = '{', '}'
|
---|
376 | }
|
---|
377 | w.WriteByte(bra)
|
---|
378 | if !w.compact {
|
---|
379 | w.WriteByte('\n')
|
---|
380 | }
|
---|
381 | w.indent++
|
---|
382 | m := v.Message()
|
---|
383 | if m2, ok := m.Interface().(encoding.TextMarshaler); ok {
|
---|
384 | b, err := m2.MarshalText()
|
---|
385 | if err != nil {
|
---|
386 | return err
|
---|
387 | }
|
---|
388 | w.Write(b)
|
---|
389 | } else {
|
---|
390 | w.writeMessage(m)
|
---|
391 | }
|
---|
392 | w.indent--
|
---|
393 | w.WriteByte(ket)
|
---|
394 | case protoreflect.EnumKind:
|
---|
395 | if ev := fd.Enum().Values().ByNumber(v.Enum()); ev != nil {
|
---|
396 | fmt.Fprint(w, ev.Name())
|
---|
397 | } else {
|
---|
398 | fmt.Fprint(w, v.Enum())
|
---|
399 | }
|
---|
400 | default:
|
---|
401 | fmt.Fprint(w, v.Interface())
|
---|
402 | }
|
---|
403 | return nil
|
---|
404 | }
|
---|
405 |
|
---|
406 | // writeQuotedString writes a quoted string in the protocol buffer text format.
|
---|
407 | func (w *textWriter) writeQuotedString(s string) {
|
---|
408 | w.WriteByte('"')
|
---|
409 | for i := 0; i < len(s); i++ {
|
---|
410 | switch c := s[i]; c {
|
---|
411 | case '\n':
|
---|
412 | w.buf = append(w.buf, `\n`...)
|
---|
413 | case '\r':
|
---|
414 | w.buf = append(w.buf, `\r`...)
|
---|
415 | case '\t':
|
---|
416 | w.buf = append(w.buf, `\t`...)
|
---|
417 | case '"':
|
---|
418 | w.buf = append(w.buf, `\"`...)
|
---|
419 | case '\\':
|
---|
420 | w.buf = append(w.buf, `\\`...)
|
---|
421 | default:
|
---|
422 | if isPrint := c >= 0x20 && c < 0x7f; isPrint {
|
---|
423 | w.buf = append(w.buf, c)
|
---|
424 | } else {
|
---|
425 | w.buf = append(w.buf, fmt.Sprintf(`\%03o`, c)...)
|
---|
426 | }
|
---|
427 | }
|
---|
428 | }
|
---|
429 | w.WriteByte('"')
|
---|
430 | }
|
---|
431 |
|
---|
432 | func (w *textWriter) writeUnknownFields(b []byte) {
|
---|
433 | if !w.compact {
|
---|
434 | fmt.Fprintf(w, "/* %d unknown bytes */\n", len(b))
|
---|
435 | }
|
---|
436 |
|
---|
437 | for len(b) > 0 {
|
---|
438 | num, wtyp, n := protowire.ConsumeTag(b)
|
---|
439 | if n < 0 {
|
---|
440 | return
|
---|
441 | }
|
---|
442 | b = b[n:]
|
---|
443 |
|
---|
444 | if wtyp == protowire.EndGroupType {
|
---|
445 | w.indent--
|
---|
446 | w.Write(endBraceNewline)
|
---|
447 | continue
|
---|
448 | }
|
---|
449 | fmt.Fprint(w, num)
|
---|
450 | if wtyp != protowire.StartGroupType {
|
---|
451 | w.WriteByte(':')
|
---|
452 | }
|
---|
453 | if !w.compact || wtyp == protowire.StartGroupType {
|
---|
454 | w.WriteByte(' ')
|
---|
455 | }
|
---|
456 | switch wtyp {
|
---|
457 | case protowire.VarintType:
|
---|
458 | v, n := protowire.ConsumeVarint(b)
|
---|
459 | if n < 0 {
|
---|
460 | return
|
---|
461 | }
|
---|
462 | b = b[n:]
|
---|
463 | fmt.Fprint(w, v)
|
---|
464 | case protowire.Fixed32Type:
|
---|
465 | v, n := protowire.ConsumeFixed32(b)
|
---|
466 | if n < 0 {
|
---|
467 | return
|
---|
468 | }
|
---|
469 | b = b[n:]
|
---|
470 | fmt.Fprint(w, v)
|
---|
471 | case protowire.Fixed64Type:
|
---|
472 | v, n := protowire.ConsumeFixed64(b)
|
---|
473 | if n < 0 {
|
---|
474 | return
|
---|
475 | }
|
---|
476 | b = b[n:]
|
---|
477 | fmt.Fprint(w, v)
|
---|
478 | case protowire.BytesType:
|
---|
479 | v, n := protowire.ConsumeBytes(b)
|
---|
480 | if n < 0 {
|
---|
481 | return
|
---|
482 | }
|
---|
483 | b = b[n:]
|
---|
484 | fmt.Fprintf(w, "%q", v)
|
---|
485 | case protowire.StartGroupType:
|
---|
486 | w.WriteByte('{')
|
---|
487 | w.indent++
|
---|
488 | default:
|
---|
489 | fmt.Fprintf(w, "/* unknown wire type %d */", wtyp)
|
---|
490 | }
|
---|
491 | w.WriteByte('\n')
|
---|
492 | }
|
---|
493 | }
|
---|
494 |
|
---|
495 | // writeExtensions writes all the extensions in m.
|
---|
496 | func (w *textWriter) writeExtensions(m protoreflect.Message) error {
|
---|
497 | md := m.Descriptor()
|
---|
498 | if md.ExtensionRanges().Len() == 0 {
|
---|
499 | return nil
|
---|
500 | }
|
---|
501 |
|
---|
502 | type ext struct {
|
---|
503 | desc protoreflect.FieldDescriptor
|
---|
504 | val protoreflect.Value
|
---|
505 | }
|
---|
506 | var exts []ext
|
---|
507 | m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
|
---|
508 | if fd.IsExtension() {
|
---|
509 | exts = append(exts, ext{fd, v})
|
---|
510 | }
|
---|
511 | return true
|
---|
512 | })
|
---|
513 | sort.Slice(exts, func(i, j int) bool {
|
---|
514 | return exts[i].desc.Number() < exts[j].desc.Number()
|
---|
515 | })
|
---|
516 |
|
---|
517 | for _, ext := range exts {
|
---|
518 | // For message set, use the name of the message as the extension name.
|
---|
519 | name := string(ext.desc.FullName())
|
---|
520 | if isMessageSet(ext.desc.ContainingMessage()) {
|
---|
521 | name = strings.TrimSuffix(name, ".message_set_extension")
|
---|
522 | }
|
---|
523 |
|
---|
524 | if !ext.desc.IsList() {
|
---|
525 | if err := w.writeSingularExtension(name, ext.val, ext.desc); err != nil {
|
---|
526 | return err
|
---|
527 | }
|
---|
528 | } else {
|
---|
529 | lv := ext.val.List()
|
---|
530 | for i := 0; i < lv.Len(); i++ {
|
---|
531 | if err := w.writeSingularExtension(name, lv.Get(i), ext.desc); err != nil {
|
---|
532 | return err
|
---|
533 | }
|
---|
534 | }
|
---|
535 | }
|
---|
536 | }
|
---|
537 | return nil
|
---|
538 | }
|
---|
539 |
|
---|
540 | func (w *textWriter) writeSingularExtension(name string, v protoreflect.Value, fd protoreflect.FieldDescriptor) error {
|
---|
541 | fmt.Fprintf(w, "[%s]:", name)
|
---|
542 | if !w.compact {
|
---|
543 | w.WriteByte(' ')
|
---|
544 | }
|
---|
545 | if err := w.writeSingularValue(v, fd); err != nil {
|
---|
546 | return err
|
---|
547 | }
|
---|
548 | w.WriteByte('\n')
|
---|
549 | return nil
|
---|
550 | }
|
---|
551 |
|
---|
552 | func (w *textWriter) writeIndent() {
|
---|
553 | if !w.complete {
|
---|
554 | return
|
---|
555 | }
|
---|
556 | for i := 0; i < w.indent*2; i++ {
|
---|
557 | w.buf = append(w.buf, ' ')
|
---|
558 | }
|
---|
559 | w.complete = false
|
---|
560 | }
|
---|