marshaler.go (22480B)
1 package toml 2 3 import ( 4 "bytes" 5 "encoding" 6 "fmt" 7 "io" 8 "math" 9 "reflect" 10 "sort" 11 "strconv" 12 "strings" 13 "time" 14 "unicode" 15 16 "github.com/pelletier/go-toml/v2/internal/characters" 17 ) 18 19 // Marshal serializes a Go value as a TOML document. 20 // 21 // It is a shortcut for Encoder.Encode() with the default options. 22 func Marshal(v interface{}) ([]byte, error) { 23 var buf bytes.Buffer 24 enc := NewEncoder(&buf) 25 26 err := enc.Encode(v) 27 if err != nil { 28 return nil, err 29 } 30 31 return buf.Bytes(), nil 32 } 33 34 // Encoder writes a TOML document to an output stream. 35 type Encoder struct { 36 // output 37 w io.Writer 38 39 // global settings 40 tablesInline bool 41 arraysMultiline bool 42 indentSymbol string 43 indentTables bool 44 } 45 46 // NewEncoder returns a new Encoder that writes to w. 47 func NewEncoder(w io.Writer) *Encoder { 48 return &Encoder{ 49 w: w, 50 indentSymbol: " ", 51 } 52 } 53 54 // SetTablesInline forces the encoder to emit all tables inline. 55 // 56 // This behavior can be controlled on an individual struct field basis with the 57 // inline tag: 58 // 59 // MyField `toml:",inline"` 60 func (enc *Encoder) SetTablesInline(inline bool) *Encoder { 61 enc.tablesInline = inline 62 return enc 63 } 64 65 // SetArraysMultiline forces the encoder to emit all arrays with one element per 66 // line. 67 // 68 // This behavior can be controlled on an individual struct field basis with the multiline tag: 69 // 70 // MyField `multiline:"true"` 71 func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder { 72 enc.arraysMultiline = multiline 73 return enc 74 } 75 76 // SetIndentSymbol defines the string that should be used for indentation. The 77 // provided string is repeated for each indentation level. Defaults to two 78 // spaces. 79 func (enc *Encoder) SetIndentSymbol(s string) *Encoder { 80 enc.indentSymbol = s 81 return enc 82 } 83 84 // SetIndentTables forces the encoder to intent tables and array tables. 85 func (enc *Encoder) SetIndentTables(indent bool) *Encoder { 86 enc.indentTables = indent 87 return enc 88 } 89 90 // Encode writes a TOML representation of v to the stream. 91 // 92 // If v cannot be represented to TOML it returns an error. 93 // 94 // # Encoding rules 95 // 96 // A top level slice containing only maps or structs is encoded as [[table 97 // array]]. 98 // 99 // All slices not matching rule 1 are encoded as [array]. As a result, any map 100 // or struct they contain is encoded as an {inline table}. 101 // 102 // Nil interfaces and nil pointers are not supported. 103 // 104 // Keys in key-values always have one part. 105 // 106 // Intermediate tables are always printed. 107 // 108 // By default, strings are encoded as literal string, unless they contain either 109 // a newline character or a single quote. In that case they are emitted as 110 // quoted strings. 111 // 112 // Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so 113 // results in an error. This rule exists because the TOML specification only 114 // requires parsers to support at least the 64 bits integer range. Allowing 115 // larger numbers would create non-standard TOML documents, which may not be 116 // readable (at best) by other implementations. To encode such numbers, a 117 // solution is a custom type that implements encoding.TextMarshaler. 118 // 119 // When encoding structs, fields are encoded in order of definition, with their 120 // exact name. 121 // 122 // Tables and array tables are separated by empty lines. However, consecutive 123 // subtables definitions are not. For example: 124 // 125 // [top1] 126 // 127 // [top2] 128 // [top2.child1] 129 // 130 // [[array]] 131 // 132 // [[array]] 133 // [array.child2] 134 // 135 // # Struct tags 136 // 137 // The encoding of each public struct field can be customized by the format 138 // string in the "toml" key of the struct field's tag. This follows 139 // encoding/json's convention. The format string starts with the name of the 140 // field, optionally followed by a comma-separated list of options. The name may 141 // be empty in order to provide options without overriding the default name. 142 // 143 // The "multiline" option emits strings as quoted multi-line TOML strings. It 144 // has no effect on fields that would not be encoded as strings. 145 // 146 // The "inline" option turns fields that would be emitted as tables into inline 147 // tables instead. It has no effect on other fields. 148 // 149 // The "omitempty" option prevents empty values or groups from being emitted. 150 // 151 // In addition to the "toml" tag struct tag, a "comment" tag can be used to emit 152 // a TOML comment before the value being annotated. Comments are ignored inside 153 // inline tables. For array tables, the comment is only present before the first 154 // element of the array. 155 func (enc *Encoder) Encode(v interface{}) error { 156 var ( 157 b []byte 158 ctx encoderCtx 159 ) 160 161 ctx.inline = enc.tablesInline 162 163 if v == nil { 164 return fmt.Errorf("toml: cannot encode a nil interface") 165 } 166 167 b, err := enc.encode(b, ctx, reflect.ValueOf(v)) 168 if err != nil { 169 return err 170 } 171 172 _, err = enc.w.Write(b) 173 if err != nil { 174 return fmt.Errorf("toml: cannot write: %w", err) 175 } 176 177 return nil 178 } 179 180 type valueOptions struct { 181 multiline bool 182 omitempty bool 183 comment string 184 } 185 186 type encoderCtx struct { 187 // Current top-level key. 188 parentKey []string 189 190 // Key that should be used for a KV. 191 key string 192 // Extra flag to account for the empty string 193 hasKey bool 194 195 // Set to true to indicate that the encoder is inside a KV, so that all 196 // tables need to be inlined. 197 insideKv bool 198 199 // Set to true to skip the first table header in an array table. 200 skipTableHeader bool 201 202 // Should the next table be encoded as inline 203 inline bool 204 205 // Indentation level 206 indent int 207 208 // Options coming from struct tags 209 options valueOptions 210 } 211 212 func (ctx *encoderCtx) shiftKey() { 213 if ctx.hasKey { 214 ctx.parentKey = append(ctx.parentKey, ctx.key) 215 ctx.clearKey() 216 } 217 } 218 219 func (ctx *encoderCtx) setKey(k string) { 220 ctx.key = k 221 ctx.hasKey = true 222 } 223 224 func (ctx *encoderCtx) clearKey() { 225 ctx.key = "" 226 ctx.hasKey = false 227 } 228 229 func (ctx *encoderCtx) isRoot() bool { 230 return len(ctx.parentKey) == 0 && !ctx.hasKey 231 } 232 233 func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { 234 i := v.Interface() 235 236 switch x := i.(type) { 237 case time.Time: 238 if x.Nanosecond() > 0 { 239 return x.AppendFormat(b, time.RFC3339Nano), nil 240 } 241 return x.AppendFormat(b, time.RFC3339), nil 242 case LocalTime: 243 return append(b, x.String()...), nil 244 case LocalDate: 245 return append(b, x.String()...), nil 246 case LocalDateTime: 247 return append(b, x.String()...), nil 248 } 249 250 hasTextMarshaler := v.Type().Implements(textMarshalerType) 251 if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) { 252 if !hasTextMarshaler { 253 v = v.Addr() 254 } 255 256 if ctx.isRoot() { 257 return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type()) 258 } 259 260 text, err := v.Interface().(encoding.TextMarshaler).MarshalText() 261 if err != nil { 262 return nil, err 263 } 264 265 b = enc.encodeString(b, string(text), ctx.options) 266 267 return b, nil 268 } 269 270 switch v.Kind() { 271 // containers 272 case reflect.Map: 273 return enc.encodeMap(b, ctx, v) 274 case reflect.Struct: 275 return enc.encodeStruct(b, ctx, v) 276 case reflect.Slice: 277 return enc.encodeSlice(b, ctx, v) 278 case reflect.Interface: 279 if v.IsNil() { 280 return nil, fmt.Errorf("toml: encoding a nil interface is not supported") 281 } 282 283 return enc.encode(b, ctx, v.Elem()) 284 case reflect.Ptr: 285 if v.IsNil() { 286 return enc.encode(b, ctx, reflect.Zero(v.Type().Elem())) 287 } 288 289 return enc.encode(b, ctx, v.Elem()) 290 291 // values 292 case reflect.String: 293 b = enc.encodeString(b, v.String(), ctx.options) 294 case reflect.Float32: 295 f := v.Float() 296 297 if math.IsNaN(f) { 298 b = append(b, "nan"...) 299 } else if f > math.MaxFloat32 { 300 b = append(b, "inf"...) 301 } else if f < -math.MaxFloat32 { 302 b = append(b, "-inf"...) 303 } else if math.Trunc(f) == f { 304 b = strconv.AppendFloat(b, f, 'f', 1, 32) 305 } else { 306 b = strconv.AppendFloat(b, f, 'f', -1, 32) 307 } 308 case reflect.Float64: 309 f := v.Float() 310 if math.IsNaN(f) { 311 b = append(b, "nan"...) 312 } else if f > math.MaxFloat64 { 313 b = append(b, "inf"...) 314 } else if f < -math.MaxFloat64 { 315 b = append(b, "-inf"...) 316 } else if math.Trunc(f) == f { 317 b = strconv.AppendFloat(b, f, 'f', 1, 64) 318 } else { 319 b = strconv.AppendFloat(b, f, 'f', -1, 64) 320 } 321 case reflect.Bool: 322 if v.Bool() { 323 b = append(b, "true"...) 324 } else { 325 b = append(b, "false"...) 326 } 327 case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: 328 x := v.Uint() 329 if x > uint64(math.MaxInt64) { 330 return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64)) 331 } 332 b = strconv.AppendUint(b, x, 10) 333 case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: 334 b = strconv.AppendInt(b, v.Int(), 10) 335 default: 336 return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind()) 337 } 338 339 return b, nil 340 } 341 342 func isNil(v reflect.Value) bool { 343 switch v.Kind() { 344 case reflect.Ptr, reflect.Interface, reflect.Map: 345 return v.IsNil() 346 default: 347 return false 348 } 349 } 350 351 func shouldOmitEmpty(options valueOptions, v reflect.Value) bool { 352 return options.omitempty && isEmptyValue(v) 353 } 354 355 func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) { 356 var err error 357 358 if !ctx.inline { 359 b = enc.encodeComment(ctx.indent, options.comment, b) 360 b = enc.indent(ctx.indent, b) 361 } 362 363 b = enc.encodeKey(b, ctx.key) 364 b = append(b, " = "...) 365 366 // create a copy of the context because the value of a KV shouldn't 367 // modify the global context. 368 subctx := ctx 369 subctx.insideKv = true 370 subctx.shiftKey() 371 subctx.options = options 372 373 b, err = enc.encode(b, subctx, v) 374 if err != nil { 375 return nil, err 376 } 377 378 return b, nil 379 } 380 381 func isEmptyValue(v reflect.Value) bool { 382 switch v.Kind() { 383 case reflect.Struct: 384 return isEmptyStruct(v) 385 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 386 return v.Len() == 0 387 case reflect.Bool: 388 return !v.Bool() 389 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 390 return v.Int() == 0 391 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 392 return v.Uint() == 0 393 case reflect.Float32, reflect.Float64: 394 return v.Float() == 0 395 case reflect.Interface, reflect.Ptr: 396 return v.IsNil() 397 } 398 return false 399 } 400 401 func isEmptyStruct(v reflect.Value) bool { 402 // TODO: merge with walkStruct and cache. 403 typ := v.Type() 404 for i := 0; i < typ.NumField(); i++ { 405 fieldType := typ.Field(i) 406 407 // only consider exported fields 408 if fieldType.PkgPath != "" { 409 continue 410 } 411 412 tag := fieldType.Tag.Get("toml") 413 414 // special field name to skip field 415 if tag == "-" { 416 continue 417 } 418 419 f := v.Field(i) 420 421 if !isEmptyValue(f) { 422 return false 423 } 424 } 425 426 return true 427 } 428 429 const literalQuote = '\'' 430 431 func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte { 432 if needsQuoting(v) { 433 return enc.encodeQuotedString(options.multiline, b, v) 434 } 435 436 return enc.encodeLiteralString(b, v) 437 } 438 439 func needsQuoting(v string) bool { 440 // TODO: vectorize 441 for _, b := range []byte(v) { 442 if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) { 443 return true 444 } 445 } 446 return false 447 } 448 449 // caller should have checked that the string does not contain new lines or ' . 450 func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte { 451 b = append(b, literalQuote) 452 b = append(b, v...) 453 b = append(b, literalQuote) 454 455 return b 456 } 457 458 func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte { 459 stringQuote := `"` 460 461 if multiline { 462 stringQuote = `"""` 463 } 464 465 b = append(b, stringQuote...) 466 if multiline { 467 b = append(b, '\n') 468 } 469 470 const ( 471 hextable = "0123456789ABCDEF" 472 // U+0000 to U+0008, U+000A to U+001F, U+007F 473 nul = 0x0 474 bs = 0x8 475 lf = 0xa 476 us = 0x1f 477 del = 0x7f 478 ) 479 480 for _, r := range []byte(v) { 481 switch r { 482 case '\\': 483 b = append(b, `\\`...) 484 case '"': 485 b = append(b, `\"`...) 486 case '\b': 487 b = append(b, `\b`...) 488 case '\f': 489 b = append(b, `\f`...) 490 case '\n': 491 if multiline { 492 b = append(b, r) 493 } else { 494 b = append(b, `\n`...) 495 } 496 case '\r': 497 b = append(b, `\r`...) 498 case '\t': 499 b = append(b, `\t`...) 500 default: 501 switch { 502 case r >= nul && r <= bs, r >= lf && r <= us, r == del: 503 b = append(b, `\u00`...) 504 b = append(b, hextable[r>>4]) 505 b = append(b, hextable[r&0x0f]) 506 default: 507 b = append(b, r) 508 } 509 } 510 } 511 512 b = append(b, stringQuote...) 513 514 return b 515 } 516 517 // caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ . 518 func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte { 519 return append(b, v...) 520 } 521 522 func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) { 523 if len(ctx.parentKey) == 0 { 524 return b, nil 525 } 526 527 b = enc.encodeComment(ctx.indent, ctx.options.comment, b) 528 529 b = enc.indent(ctx.indent, b) 530 531 b = append(b, '[') 532 533 b = enc.encodeKey(b, ctx.parentKey[0]) 534 535 for _, k := range ctx.parentKey[1:] { 536 b = append(b, '.') 537 b = enc.encodeKey(b, k) 538 } 539 540 b = append(b, "]\n"...) 541 542 return b, nil 543 } 544 545 //nolint:cyclop 546 func (enc *Encoder) encodeKey(b []byte, k string) []byte { 547 needsQuotation := false 548 cannotUseLiteral := false 549 550 if len(k) == 0 { 551 return append(b, "''"...) 552 } 553 554 for _, c := range k { 555 if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' { 556 continue 557 } 558 559 if c == literalQuote { 560 cannotUseLiteral = true 561 } 562 563 needsQuotation = true 564 } 565 566 if needsQuotation && needsQuoting(k) { 567 cannotUseLiteral = true 568 } 569 570 switch { 571 case cannotUseLiteral: 572 return enc.encodeQuotedString(false, b, k) 573 case needsQuotation: 574 return enc.encodeLiteralString(b, k) 575 default: 576 return enc.encodeUnquotedKey(b, k) 577 } 578 } 579 580 func (enc *Encoder) keyToString(k reflect.Value) (string, error) { 581 keyType := k.Type() 582 switch { 583 case keyType.Kind() == reflect.String: 584 return k.String(), nil 585 586 case keyType.Implements(textMarshalerType): 587 keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText() 588 if err != nil { 589 return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err) 590 } 591 return string(keyB), nil 592 } 593 return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind()) 594 } 595 596 func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { 597 var ( 598 t table 599 emptyValueOptions valueOptions 600 ) 601 602 iter := v.MapRange() 603 for iter.Next() { 604 v := iter.Value() 605 606 if isNil(v) { 607 continue 608 } 609 610 k, err := enc.keyToString(iter.Key()) 611 if err != nil { 612 return nil, err 613 } 614 615 if willConvertToTableOrArrayTable(ctx, v) { 616 t.pushTable(k, v, emptyValueOptions) 617 } else { 618 t.pushKV(k, v, emptyValueOptions) 619 } 620 } 621 622 sortEntriesByKey(t.kvs) 623 sortEntriesByKey(t.tables) 624 625 return enc.encodeTable(b, ctx, t) 626 } 627 628 func sortEntriesByKey(e []entry) { 629 sort.Slice(e, func(i, j int) bool { 630 return e[i].Key < e[j].Key 631 }) 632 } 633 634 type entry struct { 635 Key string 636 Value reflect.Value 637 Options valueOptions 638 } 639 640 type table struct { 641 kvs []entry 642 tables []entry 643 } 644 645 func (t *table) pushKV(k string, v reflect.Value, options valueOptions) { 646 for _, e := range t.kvs { 647 if e.Key == k { 648 return 649 } 650 } 651 652 t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options}) 653 } 654 655 func (t *table) pushTable(k string, v reflect.Value, options valueOptions) { 656 for _, e := range t.tables { 657 if e.Key == k { 658 return 659 } 660 } 661 t.tables = append(t.tables, entry{Key: k, Value: v, Options: options}) 662 } 663 664 func walkStruct(ctx encoderCtx, t *table, v reflect.Value) { 665 // TODO: cache this 666 typ := v.Type() 667 for i := 0; i < typ.NumField(); i++ { 668 fieldType := typ.Field(i) 669 670 // only consider exported fields 671 if fieldType.PkgPath != "" { 672 continue 673 } 674 675 tag := fieldType.Tag.Get("toml") 676 677 // special field name to skip field 678 if tag == "-" { 679 continue 680 } 681 682 k, opts := parseTag(tag) 683 if !isValidName(k) { 684 k = "" 685 } 686 687 f := v.Field(i) 688 689 if k == "" { 690 if fieldType.Anonymous { 691 if fieldType.Type.Kind() == reflect.Struct { 692 walkStruct(ctx, t, f) 693 } 694 continue 695 } else { 696 k = fieldType.Name 697 } 698 } 699 700 if isNil(f) { 701 continue 702 } 703 704 options := valueOptions{ 705 multiline: opts.multiline, 706 omitempty: opts.omitempty, 707 comment: fieldType.Tag.Get("comment"), 708 } 709 710 if opts.inline || !willConvertToTableOrArrayTable(ctx, f) { 711 t.pushKV(k, f, options) 712 } else { 713 t.pushTable(k, f, options) 714 } 715 } 716 } 717 718 func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { 719 var t table 720 721 walkStruct(ctx, &t, v) 722 723 return enc.encodeTable(b, ctx, t) 724 } 725 726 func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte { 727 for len(comment) > 0 { 728 var line string 729 idx := strings.IndexByte(comment, '\n') 730 if idx >= 0 { 731 line = comment[:idx] 732 comment = comment[idx+1:] 733 } else { 734 line = comment 735 comment = "" 736 } 737 b = enc.indent(indent, b) 738 b = append(b, "# "...) 739 b = append(b, line...) 740 b = append(b, '\n') 741 } 742 return b 743 } 744 745 func isValidName(s string) bool { 746 if s == "" { 747 return false 748 } 749 for _, c := range s { 750 switch { 751 case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c): 752 // Backslash and quote chars are reserved, but 753 // otherwise any punctuation chars are allowed 754 // in a tag name. 755 case !unicode.IsLetter(c) && !unicode.IsDigit(c): 756 return false 757 } 758 } 759 return true 760 } 761 762 type tagOptions struct { 763 multiline bool 764 inline bool 765 omitempty bool 766 } 767 768 func parseTag(tag string) (string, tagOptions) { 769 opts := tagOptions{} 770 771 idx := strings.Index(tag, ",") 772 if idx == -1 { 773 return tag, opts 774 } 775 776 raw := tag[idx+1:] 777 tag = string(tag[:idx]) 778 for raw != "" { 779 var o string 780 i := strings.Index(raw, ",") 781 if i >= 0 { 782 o, raw = raw[:i], raw[i+1:] 783 } else { 784 o, raw = raw, "" 785 } 786 switch o { 787 case "multiline": 788 opts.multiline = true 789 case "inline": 790 opts.inline = true 791 case "omitempty": 792 opts.omitempty = true 793 } 794 } 795 796 return tag, opts 797 } 798 799 func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) { 800 var err error 801 802 ctx.shiftKey() 803 804 if ctx.insideKv || (ctx.inline && !ctx.isRoot()) { 805 return enc.encodeTableInline(b, ctx, t) 806 } 807 808 if !ctx.skipTableHeader { 809 b, err = enc.encodeTableHeader(ctx, b) 810 if err != nil { 811 return nil, err 812 } 813 814 if enc.indentTables && len(ctx.parentKey) > 0 { 815 ctx.indent++ 816 } 817 } 818 ctx.skipTableHeader = false 819 820 hasNonEmptyKV := false 821 for _, kv := range t.kvs { 822 if shouldOmitEmpty(kv.Options, kv.Value) { 823 continue 824 } 825 hasNonEmptyKV = true 826 827 ctx.setKey(kv.Key) 828 829 b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) 830 if err != nil { 831 return nil, err 832 } 833 834 b = append(b, '\n') 835 } 836 837 first := true 838 for _, table := range t.tables { 839 if shouldOmitEmpty(table.Options, table.Value) { 840 continue 841 } 842 if first { 843 first = false 844 if hasNonEmptyKV { 845 b = append(b, '\n') 846 } 847 } else { 848 b = append(b, "\n"...) 849 } 850 851 ctx.setKey(table.Key) 852 853 ctx.options = table.Options 854 855 b, err = enc.encode(b, ctx, table.Value) 856 if err != nil { 857 return nil, err 858 } 859 } 860 861 return b, nil 862 } 863 864 func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) { 865 var err error 866 867 b = append(b, '{') 868 869 first := true 870 for _, kv := range t.kvs { 871 if shouldOmitEmpty(kv.Options, kv.Value) { 872 continue 873 } 874 875 if first { 876 first = false 877 } else { 878 b = append(b, `, `...) 879 } 880 881 ctx.setKey(kv.Key) 882 883 b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) 884 if err != nil { 885 return nil, err 886 } 887 } 888 889 if len(t.tables) > 0 { 890 panic("inline table cannot contain nested tables, only key-values") 891 } 892 893 b = append(b, "}"...) 894 895 return b, nil 896 } 897 898 func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { 899 if !v.IsValid() { 900 return false 901 } 902 if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) { 903 return false 904 } 905 906 t := v.Type() 907 switch t.Kind() { 908 case reflect.Map, reflect.Struct: 909 return !ctx.inline 910 case reflect.Interface: 911 return willConvertToTable(ctx, v.Elem()) 912 case reflect.Ptr: 913 if v.IsNil() { 914 return false 915 } 916 917 return willConvertToTable(ctx, v.Elem()) 918 default: 919 return false 920 } 921 } 922 923 func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool { 924 if ctx.insideKv { 925 return false 926 } 927 t := v.Type() 928 929 if t.Kind() == reflect.Interface { 930 return willConvertToTableOrArrayTable(ctx, v.Elem()) 931 } 932 933 if t.Kind() == reflect.Slice { 934 if v.Len() == 0 { 935 // An empty slice should be a kv = []. 936 return false 937 } 938 939 for i := 0; i < v.Len(); i++ { 940 t := willConvertToTable(ctx, v.Index(i)) 941 942 if !t { 943 return false 944 } 945 } 946 947 return true 948 } 949 950 return willConvertToTable(ctx, v) 951 } 952 953 func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { 954 if v.Len() == 0 { 955 b = append(b, "[]"...) 956 957 return b, nil 958 } 959 960 if willConvertToTableOrArrayTable(ctx, v) { 961 return enc.encodeSliceAsArrayTable(b, ctx, v) 962 } 963 964 return enc.encodeSliceAsArray(b, ctx, v) 965 } 966 967 // caller should have checked that v is a slice that only contains values that 968 // encode into tables. 969 func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { 970 ctx.shiftKey() 971 972 scratch := make([]byte, 0, 64) 973 scratch = append(scratch, "[["...) 974 975 for i, k := range ctx.parentKey { 976 if i > 0 { 977 scratch = append(scratch, '.') 978 } 979 980 scratch = enc.encodeKey(scratch, k) 981 } 982 983 scratch = append(scratch, "]]\n"...) 984 ctx.skipTableHeader = true 985 986 b = enc.encodeComment(ctx.indent, ctx.options.comment, b) 987 988 for i := 0; i < v.Len(); i++ { 989 if i != 0 { 990 b = append(b, "\n"...) 991 } 992 993 b = append(b, scratch...) 994 995 var err error 996 b, err = enc.encode(b, ctx, v.Index(i)) 997 if err != nil { 998 return nil, err 999 } 1000 } 1001 1002 return b, nil 1003 } 1004 1005 func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { 1006 multiline := ctx.options.multiline || enc.arraysMultiline 1007 separator := ", " 1008 1009 b = append(b, '[') 1010 1011 subCtx := ctx 1012 subCtx.options = valueOptions{} 1013 1014 if multiline { 1015 separator = ",\n" 1016 1017 b = append(b, '\n') 1018 1019 subCtx.indent++ 1020 } 1021 1022 var err error 1023 first := true 1024 1025 for i := 0; i < v.Len(); i++ { 1026 if first { 1027 first = false 1028 } else { 1029 b = append(b, separator...) 1030 } 1031 1032 if multiline { 1033 b = enc.indent(subCtx.indent, b) 1034 } 1035 1036 b, err = enc.encode(b, subCtx, v.Index(i)) 1037 if err != nil { 1038 return nil, err 1039 } 1040 } 1041 1042 if multiline { 1043 b = append(b, '\n') 1044 b = enc.indent(ctx.indent, b) 1045 } 1046 1047 b = append(b, ']') 1048 1049 return b, nil 1050 } 1051 1052 func (enc *Encoder) indent(level int, b []byte) []byte { 1053 for i := 0; i < level; i++ { 1054 b = append(b, enc.indentSymbol...) 1055 } 1056 1057 return b 1058 }