blob: de9e72a3e638d166e96ceab3d77ce59afe6e6f8a [file] [log] [blame]
David K. Bainbridgebd6b2882021-08-26 13:31:02 +00001//
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
16package yaml
17
18import (
19 "encoding"
20 "fmt"
21 "io"
22 "reflect"
23 "regexp"
24 "sort"
25 "strconv"
26 "strings"
27 "time"
28 "unicode/utf8"
29)
30
31type 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
40func 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
48func 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
56func (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
69func (e *encoder) finish() {
70 e.emitter.open_ended = false
71 yaml_stream_end_event_initialize(&e.event)
72 e.emit()
73}
74
75func (e *encoder) destroy() {
76 yaml_emitter_delete(&e.emitter)
77}
78
79func (e *encoder) emit() {
80 // This will internally delete the e.event value.
81 e.must(yaml_emitter_emit(&e.emitter, &e.event))
82}
83
84func (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
94func (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
111func (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
186func (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
197func (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
214func (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
256func (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
270func (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.
292func 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.
307var 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.
314func 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
324func (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
365func (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
375func (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
380func (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
385func (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
391func (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
410func (e *encoder) nilv() {
411 e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
412}
413
414func (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
428func (e *encoder) nodev(in reflect.Value) {
429 e.node(in.Interface().(*Node), "")
430}
431
432func (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}