74
|
1 //
|
|
2 // Copyright (c) 2011-2019 Canonical Ltd
|
|
3 //
|
|
4 // Licensed under the Apache License, Version 2.0 (the "License");
|
|
5 // you may not use this file except in compliance with the License.
|
|
6 // You may obtain a copy of the License at
|
|
7 //
|
|
8 // http://www.apache.org/licenses/LICENSE-2.0
|
|
9 //
|
|
10 // Unless required by applicable law or agreed to in writing, software
|
|
11 // distributed under the License is distributed on an "AS IS" BASIS,
|
|
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13 // See the License for the specific language governing permissions and
|
|
14 // limitations under the License.
|
|
15
|
|
16 package yaml
|
|
17
|
|
18 import (
|
|
19 "encoding"
|
|
20 "fmt"
|
|
21 "io"
|
|
22 "reflect"
|
|
23 "regexp"
|
|
24 "sort"
|
|
25 "strconv"
|
|
26 "strings"
|
|
27 "time"
|
|
28 "unicode/utf8"
|
|
29 )
|
|
30
|
|
31 type encoder struct {
|
|
32 emitter yaml_emitter_t
|
|
33 event yaml_event_t
|
|
34 out []byte
|
|
35 flow bool
|
|
36 indent int
|
|
37 doneInit bool
|
|
38 }
|
|
39
|
|
40 func newEncoder() *encoder {
|
|
41 e := &encoder{}
|
|
42 yaml_emitter_initialize(&e.emitter)
|
|
43 yaml_emitter_set_output_string(&e.emitter, &e.out)
|
|
44 yaml_emitter_set_unicode(&e.emitter, true)
|
|
45 return e
|
|
46 }
|
|
47
|
|
48 func newEncoderWithWriter(w io.Writer) *encoder {
|
|
49 e := &encoder{}
|
|
50 yaml_emitter_initialize(&e.emitter)
|
|
51 yaml_emitter_set_output_writer(&e.emitter, w)
|
|
52 yaml_emitter_set_unicode(&e.emitter, true)
|
|
53 return e
|
|
54 }
|
|
55
|
|
56 func (e *encoder) init() {
|
|
57 if e.doneInit {
|
|
58 return
|
|
59 }
|
|
60 if e.indent == 0 {
|
|
61 e.indent = 4
|
|
62 }
|
|
63 e.emitter.best_indent = e.indent
|
|
64 yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
|
|
65 e.emit()
|
|
66 e.doneInit = true
|
|
67 }
|
|
68
|
|
69 func (e *encoder) finish() {
|
|
70 e.emitter.open_ended = false
|
|
71 yaml_stream_end_event_initialize(&e.event)
|
|
72 e.emit()
|
|
73 }
|
|
74
|
|
75 func (e *encoder) destroy() {
|
|
76 yaml_emitter_delete(&e.emitter)
|
|
77 }
|
|
78
|
|
79 func (e *encoder) emit() {
|
|
80 // This will internally delete the e.event value.
|
|
81 e.must(yaml_emitter_emit(&e.emitter, &e.event))
|
|
82 }
|
|
83
|
|
84 func (e *encoder) must(ok bool) {
|
|
85 if !ok {
|
|
86 msg := e.emitter.problem
|
|
87 if msg == "" {
|
|
88 msg = "unknown problem generating YAML content"
|
|
89 }
|
|
90 failf("%s", msg)
|
|
91 }
|
|
92 }
|
|
93
|
|
94 func (e *encoder) marshalDoc(tag string, in reflect.Value) {
|
|
95 e.init()
|
|
96 var node *Node
|
|
97 if in.IsValid() {
|
|
98 node, _ = in.Interface().(*Node)
|
|
99 }
|
|
100 if node != nil && node.Kind == DocumentNode {
|
|
101 e.nodev(in)
|
|
102 } else {
|
|
103 yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
|
104 e.emit()
|
|
105 e.marshal(tag, in)
|
|
106 yaml_document_end_event_initialize(&e.event, true)
|
|
107 e.emit()
|
|
108 }
|
|
109 }
|
|
110
|
|
111 func (e *encoder) marshal(tag string, in reflect.Value) {
|
|
112 tag = shortTag(tag)
|
|
113 if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
|
|
114 e.nilv()
|
|
115 return
|
|
116 }
|
|
117 iface := in.Interface()
|
|
118 switch value := iface.(type) {
|
|
119 case *Node:
|
|
120 e.nodev(in)
|
|
121 return
|
|
122 case Node:
|
|
123 if !in.CanAddr() {
|
|
124 var n = reflect.New(in.Type()).Elem()
|
|
125 n.Set(in)
|
|
126 in = n
|
|
127 }
|
|
128 e.nodev(in.Addr())
|
|
129 return
|
|
130 case time.Time:
|
|
131 e.timev(tag, in)
|
|
132 return
|
|
133 case *time.Time:
|
|
134 e.timev(tag, in.Elem())
|
|
135 return
|
|
136 case time.Duration:
|
|
137 e.stringv(tag, reflect.ValueOf(value.String()))
|
|
138 return
|
|
139 case Marshaler:
|
|
140 v, err := value.MarshalYAML()
|
|
141 if err != nil {
|
|
142 fail(err)
|
|
143 }
|
|
144 if v == nil {
|
|
145 e.nilv()
|
|
146 return
|
|
147 }
|
|
148 e.marshal(tag, reflect.ValueOf(v))
|
|
149 return
|
|
150 case encoding.TextMarshaler:
|
|
151 text, err := value.MarshalText()
|
|
152 if err != nil {
|
|
153 fail(err)
|
|
154 }
|
|
155 in = reflect.ValueOf(string(text))
|
|
156 case nil:
|
|
157 e.nilv()
|
|
158 return
|
|
159 }
|
|
160 switch in.Kind() {
|
|
161 case reflect.Interface:
|
|
162 e.marshal(tag, in.Elem())
|
|
163 case reflect.Map:
|
|
164 e.mapv(tag, in)
|
|
165 case reflect.Ptr:
|
|
166 e.marshal(tag, in.Elem())
|
|
167 case reflect.Struct:
|
|
168 e.structv(tag, in)
|
|
169 case reflect.Slice, reflect.Array:
|
|
170 e.slicev(tag, in)
|
|
171 case reflect.String:
|
|
172 e.stringv(tag, in)
|
|
173 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
174 e.intv(tag, in)
|
|
175 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
176 e.uintv(tag, in)
|
|
177 case reflect.Float32, reflect.Float64:
|
|
178 e.floatv(tag, in)
|
|
179 case reflect.Bool:
|
|
180 e.boolv(tag, in)
|
|
181 default:
|
|
182 panic("cannot marshal type: " + in.Type().String())
|
|
183 }
|
|
184 }
|
|
185
|
|
186 func (e *encoder) mapv(tag string, in reflect.Value) {
|
|
187 e.mappingv(tag, func() {
|
|
188 keys := keyList(in.MapKeys())
|
|
189 sort.Sort(keys)
|
|
190 for _, k := range keys {
|
|
191 e.marshal("", k)
|
|
192 e.marshal("", in.MapIndex(k))
|
|
193 }
|
|
194 })
|
|
195 }
|
|
196
|
|
197 func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
|
|
198 for _, num := range index {
|
|
199 for {
|
|
200 if v.Kind() == reflect.Ptr {
|
|
201 if v.IsNil() {
|
|
202 return reflect.Value{}
|
|
203 }
|
|
204 v = v.Elem()
|
|
205 continue
|
|
206 }
|
|
207 break
|
|
208 }
|
|
209 v = v.Field(num)
|
|
210 }
|
|
211 return v
|
|
212 }
|
|
213
|
|
214 func (e *encoder) structv(tag string, in reflect.Value) {
|
|
215 sinfo, err := getStructInfo(in.Type())
|
|
216 if err != nil {
|
|
217 panic(err)
|
|
218 }
|
|
219 e.mappingv(tag, func() {
|
|
220 for _, info := range sinfo.FieldsList {
|
|
221 var value reflect.Value
|
|
222 if info.Inline == nil {
|
|
223 value = in.Field(info.Num)
|
|
224 } else {
|
|
225 value = e.fieldByIndex(in, info.Inline)
|
|
226 if !value.IsValid() {
|
|
227 continue
|
|
228 }
|
|
229 }
|
|
230 if info.OmitEmpty && isZero(value) {
|
|
231 continue
|
|
232 }
|
|
233 e.marshal("", reflect.ValueOf(info.Key))
|
|
234 e.flow = info.Flow
|
|
235 e.marshal("", value)
|
|
236 }
|
|
237 if sinfo.InlineMap >= 0 {
|
|
238 m := in.Field(sinfo.InlineMap)
|
|
239 if m.Len() > 0 {
|
|
240 e.flow = false
|
|
241 keys := keyList(m.MapKeys())
|
|
242 sort.Sort(keys)
|
|
243 for _, k := range keys {
|
|
244 if _, found := sinfo.FieldsMap[k.String()]; found {
|
|
245 panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
|
|
246 }
|
|
247 e.marshal("", k)
|
|
248 e.flow = false
|
|
249 e.marshal("", m.MapIndex(k))
|
|
250 }
|
|
251 }
|
|
252 }
|
|
253 })
|
|
254 }
|
|
255
|
|
256 func (e *encoder) mappingv(tag string, f func()) {
|
|
257 implicit := tag == ""
|
|
258 style := yaml_BLOCK_MAPPING_STYLE
|
|
259 if e.flow {
|
|
260 e.flow = false
|
|
261 style = yaml_FLOW_MAPPING_STYLE
|
|
262 }
|
|
263 yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
|
|
264 e.emit()
|
|
265 f()
|
|
266 yaml_mapping_end_event_initialize(&e.event)
|
|
267 e.emit()
|
|
268 }
|
|
269
|
|
270 func (e *encoder) slicev(tag string, in reflect.Value) {
|
|
271 implicit := tag == ""
|
|
272 style := yaml_BLOCK_SEQUENCE_STYLE
|
|
273 if e.flow {
|
|
274 e.flow = false
|
|
275 style = yaml_FLOW_SEQUENCE_STYLE
|
|
276 }
|
|
277 e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
|
278 e.emit()
|
|
279 n := in.Len()
|
|
280 for i := 0; i < n; i++ {
|
|
281 e.marshal("", in.Index(i))
|
|
282 }
|
|
283 e.must(yaml_sequence_end_event_initialize(&e.event))
|
|
284 e.emit()
|
|
285 }
|
|
286
|
|
287 // isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
|
288 //
|
|
289 // The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
|
290 // in YAML 1.2 and by this package, but these should be marshalled quoted for
|
|
291 // the time being for compatibility with other parsers.
|
|
292 func isBase60Float(s string) (result bool) {
|
|
293 // Fast path.
|
|
294 if s == "" {
|
|
295 return false
|
|
296 }
|
|
297 c := s[0]
|
|
298 if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
|
299 return false
|
|
300 }
|
|
301 // Do the full match.
|
|
302 return base60float.MatchString(s)
|
|
303 }
|
|
304
|
|
305 // From http://yaml.org/type/float.html, except the regular expression there
|
|
306 // is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
|
307 var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
|
308
|
|
309 // isOldBool returns whether s is bool notation as defined in YAML 1.1.
|
|
310 //
|
|
311 // We continue to force strings that YAML 1.1 would interpret as booleans to be
|
|
312 // rendered as quotes strings so that the marshalled output valid for YAML 1.1
|
|
313 // parsing.
|
|
314 func isOldBool(s string) (result bool) {
|
|
315 switch s {
|
|
316 case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
|
|
317 "n", "N", "no", "No", "NO", "off", "Off", "OFF":
|
|
318 return true
|
|
319 default:
|
|
320 return false
|
|
321 }
|
|
322 }
|
|
323
|
|
324 func (e *encoder) stringv(tag string, in reflect.Value) {
|
|
325 var style yaml_scalar_style_t
|
|
326 s := in.String()
|
|
327 canUsePlain := true
|
|
328 switch {
|
|
329 case !utf8.ValidString(s):
|
|
330 if tag == binaryTag {
|
|
331 failf("explicitly tagged !!binary data must be base64-encoded")
|
|
332 }
|
|
333 if tag != "" {
|
|
334 failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
|
335 }
|
|
336 // It can't be encoded directly as YAML so use a binary tag
|
|
337 // and encode it as base64.
|
|
338 tag = binaryTag
|
|
339 s = encodeBase64(s)
|
|
340 case tag == "":
|
|
341 // Check to see if it would resolve to a specific
|
|
342 // tag when encoded unquoted. If it doesn't,
|
|
343 // there's no need to quote it.
|
|
344 rtag, _ := resolve("", s)
|
|
345 canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
|
|
346 }
|
|
347 // Note: it's possible for user code to emit invalid YAML
|
|
348 // if they explicitly specify a tag and a string containing
|
|
349 // text that's incompatible with that tag.
|
|
350 switch {
|
|
351 case strings.Contains(s, "\n"):
|
|
352 if e.flow {
|
|
353 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
|
354 } else {
|
|
355 style = yaml_LITERAL_SCALAR_STYLE
|
|
356 }
|
|
357 case canUsePlain:
|
|
358 style = yaml_PLAIN_SCALAR_STYLE
|
|
359 default:
|
|
360 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
|
361 }
|
|
362 e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
|
|
363 }
|
|
364
|
|
365 func (e *encoder) boolv(tag string, in reflect.Value) {
|
|
366 var s string
|
|
367 if in.Bool() {
|
|
368 s = "true"
|
|
369 } else {
|
|
370 s = "false"
|
|
371 }
|
|
372 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
|
373 }
|
|
374
|
|
375 func (e *encoder) intv(tag string, in reflect.Value) {
|
|
376 s := strconv.FormatInt(in.Int(), 10)
|
|
377 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
|
378 }
|
|
379
|
|
380 func (e *encoder) uintv(tag string, in reflect.Value) {
|
|
381 s := strconv.FormatUint(in.Uint(), 10)
|
|
382 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
|
383 }
|
|
384
|
|
385 func (e *encoder) timev(tag string, in reflect.Value) {
|
|
386 t := in.Interface().(time.Time)
|
|
387 s := t.Format(time.RFC3339Nano)
|
|
388 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
|
389 }
|
|
390
|
|
391 func (e *encoder) floatv(tag string, in reflect.Value) {
|
|
392 // Issue #352: When formatting, use the precision of the underlying value
|
|
393 precision := 64
|
|
394 if in.Kind() == reflect.Float32 {
|
|
395 precision = 32
|
|
396 }
|
|
397
|
|
398 s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
|
|
399 switch s {
|
|
400 case "+Inf":
|
|
401 s = ".inf"
|
|
402 case "-Inf":
|
|
403 s = "-.inf"
|
|
404 case "NaN":
|
|
405 s = ".nan"
|
|
406 }
|
|
407 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
|
408 }
|
|
409
|
|
410 func (e *encoder) nilv() {
|
|
411 e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
|
|
412 }
|
|
413
|
|
414 func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
|
|
415 // TODO Kill this function. Replace all initialize calls by their underlining Go literals.
|
|
416 implicit := tag == ""
|
|
417 if !implicit {
|
|
418 tag = longTag(tag)
|
|
419 }
|
|
420 e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
|
421 e.event.head_comment = head
|
|
422 e.event.line_comment = line
|
|
423 e.event.foot_comment = foot
|
|
424 e.event.tail_comment = tail
|
|
425 e.emit()
|
|
426 }
|
|
427
|
|
428 func (e *encoder) nodev(in reflect.Value) {
|
|
429 e.node(in.Interface().(*Node), "")
|
|
430 }
|
|
431
|
|
432 func (e *encoder) node(node *Node, tail string) {
|
|
433 // Zero nodes behave as nil.
|
|
434 if node.Kind == 0 && node.IsZero() {
|
|
435 e.nilv()
|
|
436 return
|
|
437 }
|
|
438
|
|
439 // If the tag was not explicitly requested, and dropping it won't change the
|
|
440 // implicit tag of the value, don't include it in the presentation.
|
|
441 var tag = node.Tag
|
|
442 var stag = shortTag(tag)
|
|
443 var forceQuoting bool
|
|
444 if tag != "" && node.Style&TaggedStyle == 0 {
|
|
445 if node.Kind == ScalarNode {
|
|
446 if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
|
|
447 tag = ""
|
|
448 } else {
|
|
449 rtag, _ := resolve("", node.Value)
|
|
450 if rtag == stag {
|
|
451 tag = ""
|
|
452 } else if stag == strTag {
|
|
453 tag = ""
|
|
454 forceQuoting = true
|
|
455 }
|
|
456 }
|
|
457 } else {
|
|
458 var rtag string
|
|
459 switch node.Kind {
|
|
460 case MappingNode:
|
|
461 rtag = mapTag
|
|
462 case SequenceNode:
|
|
463 rtag = seqTag
|
|
464 }
|
|
465 if rtag == stag {
|
|
466 tag = ""
|
|
467 }
|
|
468 }
|
|
469 }
|
|
470
|
|
471 switch node.Kind {
|
|
472 case DocumentNode:
|
|
473 yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
|
474 e.event.head_comment = []byte(node.HeadComment)
|
|
475 e.emit()
|
|
476 for _, node := range node.Content {
|
|
477 e.node(node, "")
|
|
478 }
|
|
479 yaml_document_end_event_initialize(&e.event, true)
|
|
480 e.event.foot_comment = []byte(node.FootComment)
|
|
481 e.emit()
|
|
482
|
|
483 case SequenceNode:
|
|
484 style := yaml_BLOCK_SEQUENCE_STYLE
|
|
485 if node.Style&FlowStyle != 0 {
|
|
486 style = yaml_FLOW_SEQUENCE_STYLE
|
|
487 }
|
|
488 e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style))
|
|
489 e.event.head_comment = []byte(node.HeadComment)
|
|
490 e.emit()
|
|
491 for _, node := range node.Content {
|
|
492 e.node(node, "")
|
|
493 }
|
|
494 e.must(yaml_sequence_end_event_initialize(&e.event))
|
|
495 e.event.line_comment = []byte(node.LineComment)
|
|
496 e.event.foot_comment = []byte(node.FootComment)
|
|
497 e.emit()
|
|
498
|
|
499 case MappingNode:
|
|
500 style := yaml_BLOCK_MAPPING_STYLE
|
|
501 if node.Style&FlowStyle != 0 {
|
|
502 style = yaml_FLOW_MAPPING_STYLE
|
|
503 }
|
|
504 yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
|
|
505 e.event.tail_comment = []byte(tail)
|
|
506 e.event.head_comment = []byte(node.HeadComment)
|
|
507 e.emit()
|
|
508
|
|
509 // The tail logic below moves the foot comment of prior keys to the following key,
|
|
510 // since the value for each key may be a nested structure and the foot needs to be
|
|
511 // processed only the entirety of the value is streamed. The last tail is processed
|
|
512 // with the mapping end event.
|
|
513 var tail string
|
|
514 for i := 0; i+1 < len(node.Content); i += 2 {
|
|
515 k := node.Content[i]
|
|
516 foot := k.FootComment
|
|
517 if foot != "" {
|
|
518 kopy := *k
|
|
519 kopy.FootComment = ""
|
|
520 k = &kopy
|
|
521 }
|
|
522 e.node(k, tail)
|
|
523 tail = foot
|
|
524
|
|
525 v := node.Content[i+1]
|
|
526 e.node(v, "")
|
|
527 }
|
|
528
|
|
529 yaml_mapping_end_event_initialize(&e.event)
|
|
530 e.event.tail_comment = []byte(tail)
|
|
531 e.event.line_comment = []byte(node.LineComment)
|
|
532 e.event.foot_comment = []byte(node.FootComment)
|
|
533 e.emit()
|
|
534
|
|
535 case AliasNode:
|
|
536 yaml_alias_event_initialize(&e.event, []byte(node.Value))
|
|
537 e.event.head_comment = []byte(node.HeadComment)
|
|
538 e.event.line_comment = []byte(node.LineComment)
|
|
539 e.event.foot_comment = []byte(node.FootComment)
|
|
540 e.emit()
|
|
541
|
|
542 case ScalarNode:
|
|
543 value := node.Value
|
|
544 if !utf8.ValidString(value) {
|
|
545 if stag == binaryTag {
|
|
546 failf("explicitly tagged !!binary data must be base64-encoded")
|
|
547 }
|
|
548 if stag != "" {
|
|
549 failf("cannot marshal invalid UTF-8 data as %s", stag)
|
|
550 }
|
|
551 // It can't be encoded directly as YAML so use a binary tag
|
|
552 // and encode it as base64.
|
|
553 tag = binaryTag
|
|
554 value = encodeBase64(value)
|
|
555 }
|
|
556
|
|
557 style := yaml_PLAIN_SCALAR_STYLE
|
|
558 switch {
|
|
559 case node.Style&DoubleQuotedStyle != 0:
|
|
560 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
|
561 case node.Style&SingleQuotedStyle != 0:
|
|
562 style = yaml_SINGLE_QUOTED_SCALAR_STYLE
|
|
563 case node.Style&LiteralStyle != 0:
|
|
564 style = yaml_LITERAL_SCALAR_STYLE
|
|
565 case node.Style&FoldedStyle != 0:
|
|
566 style = yaml_FOLDED_SCALAR_STYLE
|
|
567 case strings.Contains(value, "\n"):
|
|
568 style = yaml_LITERAL_SCALAR_STYLE
|
|
569 case forceQuoting:
|
|
570 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
|
571 }
|
|
572
|
|
573 e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
|
|
574 default:
|
|
575 failf("cannot encode node with unknown kind %d", node.Kind)
|
|
576 }
|
|
577 }
|