blob: 1f37271ce45250d7d510718e6f17ac3bc222aa15 [file] [log] [blame]
mpagenkoaf801632020-07-03 10:00:42 +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 time.Time:
123 e.timev(tag, in)
124 return
125 case *time.Time:
126 e.timev(tag, in.Elem())
127 return
128 case time.Duration:
129 e.stringv(tag, reflect.ValueOf(value.String()))
130 return
131 case Marshaler:
132 v, err := value.MarshalYAML()
133 if err != nil {
134 fail(err)
135 }
136 if v == nil {
137 e.nilv()
138 return
139 }
140 e.marshal(tag, reflect.ValueOf(v))
141 return
142 case encoding.TextMarshaler:
143 text, err := value.MarshalText()
144 if err != nil {
145 fail(err)
146 }
147 in = reflect.ValueOf(string(text))
148 case nil:
149 e.nilv()
150 return
151 }
152 switch in.Kind() {
153 case reflect.Interface:
154 e.marshal(tag, in.Elem())
155 case reflect.Map:
156 e.mapv(tag, in)
157 case reflect.Ptr:
158 e.marshal(tag, in.Elem())
159 case reflect.Struct:
160 e.structv(tag, in)
161 case reflect.Slice, reflect.Array:
162 e.slicev(tag, in)
163 case reflect.String:
164 e.stringv(tag, in)
165 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
166 e.intv(tag, in)
167 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
168 e.uintv(tag, in)
169 case reflect.Float32, reflect.Float64:
170 e.floatv(tag, in)
171 case reflect.Bool:
172 e.boolv(tag, in)
173 default:
174 panic("cannot marshal type: " + in.Type().String())
175 }
176}
177
178func (e *encoder) mapv(tag string, in reflect.Value) {
179 e.mappingv(tag, func() {
180 keys := keyList(in.MapKeys())
181 sort.Sort(keys)
182 for _, k := range keys {
183 e.marshal("", k)
184 e.marshal("", in.MapIndex(k))
185 }
186 })
187}
188
189func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
190 for _, num := range index {
191 for {
192 if v.Kind() == reflect.Ptr {
193 if v.IsNil() {
194 return reflect.Value{}
195 }
196 v = v.Elem()
197 continue
198 }
199 break
200 }
201 v = v.Field(num)
202 }
203 return v
204}
205
206func (e *encoder) structv(tag string, in reflect.Value) {
207 sinfo, err := getStructInfo(in.Type())
208 if err != nil {
209 panic(err)
210 }
211 e.mappingv(tag, func() {
212 for _, info := range sinfo.FieldsList {
213 var value reflect.Value
214 if info.Inline == nil {
215 value = in.Field(info.Num)
216 } else {
217 value = e.fieldByIndex(in, info.Inline)
218 if !value.IsValid() {
219 continue
220 }
221 }
222 if info.OmitEmpty && isZero(value) {
223 continue
224 }
225 e.marshal("", reflect.ValueOf(info.Key))
226 e.flow = info.Flow
227 e.marshal("", value)
228 }
229 if sinfo.InlineMap >= 0 {
230 m := in.Field(sinfo.InlineMap)
231 if m.Len() > 0 {
232 e.flow = false
233 keys := keyList(m.MapKeys())
234 sort.Sort(keys)
235 for _, k := range keys {
236 if _, found := sinfo.FieldsMap[k.String()]; found {
237 panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
238 }
239 e.marshal("", k)
240 e.flow = false
241 e.marshal("", m.MapIndex(k))
242 }
243 }
244 }
245 })
246}
247
248func (e *encoder) mappingv(tag string, f func()) {
249 implicit := tag == ""
250 style := yaml_BLOCK_MAPPING_STYLE
251 if e.flow {
252 e.flow = false
253 style = yaml_FLOW_MAPPING_STYLE
254 }
255 yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
256 e.emit()
257 f()
258 yaml_mapping_end_event_initialize(&e.event)
259 e.emit()
260}
261
262func (e *encoder) slicev(tag string, in reflect.Value) {
263 implicit := tag == ""
264 style := yaml_BLOCK_SEQUENCE_STYLE
265 if e.flow {
266 e.flow = false
267 style = yaml_FLOW_SEQUENCE_STYLE
268 }
269 e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
270 e.emit()
271 n := in.Len()
272 for i := 0; i < n; i++ {
273 e.marshal("", in.Index(i))
274 }
275 e.must(yaml_sequence_end_event_initialize(&e.event))
276 e.emit()
277}
278
279// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
280//
281// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
282// in YAML 1.2 and by this package, but these should be marshalled quoted for
283// the time being for compatibility with other parsers.
284func isBase60Float(s string) (result bool) {
285 // Fast path.
286 if s == "" {
287 return false
288 }
289 c := s[0]
290 if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
291 return false
292 }
293 // Do the full match.
294 return base60float.MatchString(s)
295}
296
297// From http://yaml.org/type/float.html, except the regular expression there
298// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
299var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
300
301// isOldBool returns whether s is bool notation as defined in YAML 1.1.
302//
303// We continue to force strings that YAML 1.1 would interpret as booleans to be
304// rendered as quotes strings so that the marshalled output valid for YAML 1.1
305// parsing.
306func isOldBool(s string) (result bool) {
307 switch s {
308 case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
309 "n", "N", "no", "No", "NO", "off", "Off", "OFF":
310 return true
311 default:
312 return false
313 }
314}
315
316func (e *encoder) stringv(tag string, in reflect.Value) {
317 var style yaml_scalar_style_t
318 s := in.String()
319 canUsePlain := true
320 switch {
321 case !utf8.ValidString(s):
322 if tag == binaryTag {
323 failf("explicitly tagged !!binary data must be base64-encoded")
324 }
325 if tag != "" {
326 failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
327 }
328 // It can't be encoded directly as YAML so use a binary tag
329 // and encode it as base64.
330 tag = binaryTag
331 s = encodeBase64(s)
332 case tag == "":
333 // Check to see if it would resolve to a specific
334 // tag when encoded unquoted. If it doesn't,
335 // there's no need to quote it.
336 rtag, _ := resolve("", s)
337 canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
338 }
339 // Note: it's possible for user code to emit invalid YAML
340 // if they explicitly specify a tag and a string containing
341 // text that's incompatible with that tag.
342 switch {
343 case strings.Contains(s, "\n"):
344 if e.flow {
345 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
346 } else {
347 style = yaml_LITERAL_SCALAR_STYLE
348 }
349 case canUsePlain:
350 style = yaml_PLAIN_SCALAR_STYLE
351 default:
352 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
353 }
354 e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
355}
356
357func (e *encoder) boolv(tag string, in reflect.Value) {
358 var s string
359 if in.Bool() {
360 s = "true"
361 } else {
362 s = "false"
363 }
364 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
365}
366
367func (e *encoder) intv(tag string, in reflect.Value) {
368 s := strconv.FormatInt(in.Int(), 10)
369 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
370}
371
372func (e *encoder) uintv(tag string, in reflect.Value) {
373 s := strconv.FormatUint(in.Uint(), 10)
374 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
375}
376
377func (e *encoder) timev(tag string, in reflect.Value) {
378 t := in.Interface().(time.Time)
379 s := t.Format(time.RFC3339Nano)
380 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
381}
382
383func (e *encoder) floatv(tag string, in reflect.Value) {
384 // Issue #352: When formatting, use the precision of the underlying value
385 precision := 64
386 if in.Kind() == reflect.Float32 {
387 precision = 32
388 }
389
390 s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
391 switch s {
392 case "+Inf":
393 s = ".inf"
394 case "-Inf":
395 s = "-.inf"
396 case "NaN":
397 s = ".nan"
398 }
399 e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
400}
401
402func (e *encoder) nilv() {
403 e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
404}
405
406func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
407 // TODO Kill this function. Replace all initialize calls by their underlining Go literals.
408 implicit := tag == ""
409 if !implicit {
410 tag = longTag(tag)
411 }
412 e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
413 e.event.head_comment = head
414 e.event.line_comment = line
415 e.event.foot_comment = foot
416 e.event.tail_comment = tail
417 e.emit()
418}
419
420func (e *encoder) nodev(in reflect.Value) {
421 e.node(in.Interface().(*Node), "")
422}
423
424func (e *encoder) node(node *Node, tail string) {
425 // If the tag was not explicitly requested, and dropping it won't change the
426 // implicit tag of the value, don't include it in the presentation.
427 var tag = node.Tag
428 var stag = shortTag(tag)
429 var rtag string
430 var forceQuoting bool
431 if tag != "" && node.Style&TaggedStyle == 0 {
432 if node.Kind == ScalarNode {
433 if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
434 tag = ""
435 } else {
436 rtag, _ = resolve("", node.Value)
437 if rtag == stag {
438 tag = ""
439 } else if stag == strTag {
440 tag = ""
441 forceQuoting = true
442 }
443 }
444 } else {
445 switch node.Kind {
446 case MappingNode:
447 rtag = mapTag
448 case SequenceNode:
449 rtag = seqTag
450 }
451 if rtag == stag {
452 tag = ""
453 }
454 }
455 }
456
457 switch node.Kind {
458 case DocumentNode:
459 yaml_document_start_event_initialize(&e.event, nil, nil, true)
460 e.event.head_comment = []byte(node.HeadComment)
461 e.emit()
462 for _, node := range node.Content {
463 e.node(node, "")
464 }
465 yaml_document_end_event_initialize(&e.event, true)
466 e.event.foot_comment = []byte(node.FootComment)
467 e.emit()
468
469 case SequenceNode:
470 style := yaml_BLOCK_SEQUENCE_STYLE
471 if node.Style&FlowStyle != 0 {
472 style = yaml_FLOW_SEQUENCE_STYLE
473 }
474 e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style))
475 e.event.head_comment = []byte(node.HeadComment)
476 e.emit()
477 for _, node := range node.Content {
478 e.node(node, "")
479 }
480 e.must(yaml_sequence_end_event_initialize(&e.event))
481 e.event.line_comment = []byte(node.LineComment)
482 e.event.foot_comment = []byte(node.FootComment)
483 e.emit()
484
485 case MappingNode:
486 style := yaml_BLOCK_MAPPING_STYLE
487 if node.Style&FlowStyle != 0 {
488 style = yaml_FLOW_MAPPING_STYLE
489 }
490 yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style)
491 e.event.tail_comment = []byte(tail)
492 e.event.head_comment = []byte(node.HeadComment)
493 e.emit()
494
495 // The tail logic below moves the foot comment of prior keys to the following key,
496 // since the value for each key may be a nested structure and the foot needs to be
497 // processed only the entirety of the value is streamed. The last tail is processed
498 // with the mapping end event.
499 var tail string
500 for i := 0; i+1 < len(node.Content); i += 2 {
501 k := node.Content[i]
502 foot := k.FootComment
503 if foot != "" {
504 kopy := *k
505 kopy.FootComment = ""
506 k = &kopy
507 }
508 e.node(k, tail)
509 tail = foot
510
511 v := node.Content[i+1]
512 e.node(v, "")
513 }
514
515 yaml_mapping_end_event_initialize(&e.event)
516 e.event.tail_comment = []byte(tail)
517 e.event.line_comment = []byte(node.LineComment)
518 e.event.foot_comment = []byte(node.FootComment)
519 e.emit()
520
521 case AliasNode:
522 yaml_alias_event_initialize(&e.event, []byte(node.Value))
523 e.event.head_comment = []byte(node.HeadComment)
524 e.event.line_comment = []byte(node.LineComment)
525 e.event.foot_comment = []byte(node.FootComment)
526 e.emit()
527
528 case ScalarNode:
529 value := node.Value
530 if !utf8.ValidString(value) {
531 if tag == binaryTag {
532 failf("explicitly tagged !!binary data must be base64-encoded")
533 }
534 if tag != "" {
535 failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
536 }
537 // It can't be encoded directly as YAML so use a binary tag
538 // and encode it as base64.
539 tag = binaryTag
540 value = encodeBase64(value)
541 }
542
543 style := yaml_PLAIN_SCALAR_STYLE
544 switch {
545 case node.Style&DoubleQuotedStyle != 0:
546 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
547 case node.Style&SingleQuotedStyle != 0:
548 style = yaml_SINGLE_QUOTED_SCALAR_STYLE
549 case node.Style&LiteralStyle != 0:
550 style = yaml_LITERAL_SCALAR_STYLE
551 case node.Style&FoldedStyle != 0:
552 style = yaml_FOLDED_SCALAR_STYLE
553 case strings.Contains(value, "\n"):
554 style = yaml_LITERAL_SCALAR_STYLE
555 case forceQuoting:
556 style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
557 }
558
559 e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
560 }
561}