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 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} 562