marshaler.go raw
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 // The "commented" option prefixes the value and all its children with a comment
152 // symbol.
153 //
154 // In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
155 // a TOML comment before the value being annotated. Comments are ignored inside
156 // inline tables. For array tables, the comment is only present before the first
157 // element of the array.
158 func (enc *Encoder) Encode(v interface{}) error {
159 var (
160 b []byte
161 ctx encoderCtx
162 )
163
164 ctx.inline = enc.tablesInline
165
166 if v == nil {
167 return fmt.Errorf("toml: cannot encode a nil interface")
168 }
169
170 b, err := enc.encode(b, ctx, reflect.ValueOf(v))
171 if err != nil {
172 return err
173 }
174
175 _, err = enc.w.Write(b)
176 if err != nil {
177 return fmt.Errorf("toml: cannot write: %w", err)
178 }
179
180 return nil
181 }
182
183 type valueOptions struct {
184 multiline bool
185 omitempty bool
186 commented bool
187 comment string
188 }
189
190 type encoderCtx struct {
191 // Current top-level key.
192 parentKey []string
193
194 // Key that should be used for a KV.
195 key string
196 // Extra flag to account for the empty string
197 hasKey bool
198
199 // Set to true to indicate that the encoder is inside a KV, so that all
200 // tables need to be inlined.
201 insideKv bool
202
203 // Set to true to skip the first table header in an array table.
204 skipTableHeader bool
205
206 // Should the next table be encoded as inline
207 inline bool
208
209 // Indentation level
210 indent int
211
212 // Prefix the current value with a comment.
213 commented bool
214
215 // Options coming from struct tags
216 options valueOptions
217 }
218
219 func (ctx *encoderCtx) shiftKey() {
220 if ctx.hasKey {
221 ctx.parentKey = append(ctx.parentKey, ctx.key)
222 ctx.clearKey()
223 }
224 }
225
226 func (ctx *encoderCtx) setKey(k string) {
227 ctx.key = k
228 ctx.hasKey = true
229 }
230
231 func (ctx *encoderCtx) clearKey() {
232 ctx.key = ""
233 ctx.hasKey = false
234 }
235
236 func (ctx *encoderCtx) isRoot() bool {
237 return len(ctx.parentKey) == 0 && !ctx.hasKey
238 }
239
240 func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
241 i := v.Interface()
242
243 switch x := i.(type) {
244 case time.Time:
245 if x.Nanosecond() > 0 {
246 return x.AppendFormat(b, time.RFC3339Nano), nil
247 }
248 return x.AppendFormat(b, time.RFC3339), nil
249 case LocalTime:
250 return append(b, x.String()...), nil
251 case LocalDate:
252 return append(b, x.String()...), nil
253 case LocalDateTime:
254 return append(b, x.String()...), nil
255 }
256
257 hasTextMarshaler := v.Type().Implements(textMarshalerType)
258 if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
259 if !hasTextMarshaler {
260 v = v.Addr()
261 }
262
263 if ctx.isRoot() {
264 return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type())
265 }
266
267 text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
268 if err != nil {
269 return nil, err
270 }
271
272 b = enc.encodeString(b, string(text), ctx.options)
273
274 return b, nil
275 }
276
277 switch v.Kind() {
278 // containers
279 case reflect.Map:
280 return enc.encodeMap(b, ctx, v)
281 case reflect.Struct:
282 return enc.encodeStruct(b, ctx, v)
283 case reflect.Slice, reflect.Array:
284 return enc.encodeSlice(b, ctx, v)
285 case reflect.Interface:
286 if v.IsNil() {
287 return nil, fmt.Errorf("toml: encoding a nil interface is not supported")
288 }
289
290 return enc.encode(b, ctx, v.Elem())
291 case reflect.Ptr:
292 if v.IsNil() {
293 return enc.encode(b, ctx, reflect.Zero(v.Type().Elem()))
294 }
295
296 return enc.encode(b, ctx, v.Elem())
297
298 // values
299 case reflect.String:
300 b = enc.encodeString(b, v.String(), ctx.options)
301 case reflect.Float32:
302 f := v.Float()
303
304 if math.IsNaN(f) {
305 b = append(b, "nan"...)
306 } else if f > math.MaxFloat32 {
307 b = append(b, "inf"...)
308 } else if f < -math.MaxFloat32 {
309 b = append(b, "-inf"...)
310 } else if math.Trunc(f) == f {
311 b = strconv.AppendFloat(b, f, 'f', 1, 32)
312 } else {
313 b = strconv.AppendFloat(b, f, 'f', -1, 32)
314 }
315 case reflect.Float64:
316 f := v.Float()
317 if math.IsNaN(f) {
318 b = append(b, "nan"...)
319 } else if f > math.MaxFloat64 {
320 b = append(b, "inf"...)
321 } else if f < -math.MaxFloat64 {
322 b = append(b, "-inf"...)
323 } else if math.Trunc(f) == f {
324 b = strconv.AppendFloat(b, f, 'f', 1, 64)
325 } else {
326 b = strconv.AppendFloat(b, f, 'f', -1, 64)
327 }
328 case reflect.Bool:
329 if v.Bool() {
330 b = append(b, "true"...)
331 } else {
332 b = append(b, "false"...)
333 }
334 case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
335 x := v.Uint()
336 if x > uint64(math.MaxInt64) {
337 return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))
338 }
339 b = strconv.AppendUint(b, x, 10)
340 case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
341 b = strconv.AppendInt(b, v.Int(), 10)
342 default:
343 return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind())
344 }
345
346 return b, nil
347 }
348
349 func isNil(v reflect.Value) bool {
350 switch v.Kind() {
351 case reflect.Ptr, reflect.Interface, reflect.Map:
352 return v.IsNil()
353 default:
354 return false
355 }
356 }
357
358 func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
359 return options.omitempty && isEmptyValue(v)
360 }
361
362 func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
363 var err error
364
365 if !ctx.inline {
366 b = enc.encodeComment(ctx.indent, options.comment, b)
367 b = enc.commented(ctx.commented, b)
368 b = enc.indent(ctx.indent, b)
369 }
370
371 b = enc.encodeKey(b, ctx.key)
372 b = append(b, " = "...)
373
374 // create a copy of the context because the value of a KV shouldn't
375 // modify the global context.
376 subctx := ctx
377 subctx.insideKv = true
378 subctx.shiftKey()
379 subctx.options = options
380
381 b, err = enc.encode(b, subctx, v)
382 if err != nil {
383 return nil, err
384 }
385
386 return b, nil
387 }
388
389 func (enc *Encoder) commented(commented bool, b []byte) []byte {
390 if commented {
391 return append(b, "# "...)
392 }
393 return b
394 }
395
396 func isEmptyValue(v reflect.Value) bool {
397 switch v.Kind() {
398 case reflect.Struct:
399 return isEmptyStruct(v)
400 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
401 return v.Len() == 0
402 case reflect.Bool:
403 return !v.Bool()
404 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
405 return v.Int() == 0
406 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
407 return v.Uint() == 0
408 case reflect.Float32, reflect.Float64:
409 return v.Float() == 0
410 case reflect.Interface, reflect.Ptr:
411 return v.IsNil()
412 }
413 return false
414 }
415
416 func isEmptyStruct(v reflect.Value) bool {
417 // TODO: merge with walkStruct and cache.
418 typ := v.Type()
419 for i := 0; i < typ.NumField(); i++ {
420 fieldType := typ.Field(i)
421
422 // only consider exported fields
423 if fieldType.PkgPath != "" {
424 continue
425 }
426
427 tag := fieldType.Tag.Get("toml")
428
429 // special field name to skip field
430 if tag == "-" {
431 continue
432 }
433
434 f := v.Field(i)
435
436 if !isEmptyValue(f) {
437 return false
438 }
439 }
440
441 return true
442 }
443
444 const literalQuote = '\''
445
446 func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
447 if needsQuoting(v) {
448 return enc.encodeQuotedString(options.multiline, b, v)
449 }
450
451 return enc.encodeLiteralString(b, v)
452 }
453
454 func needsQuoting(v string) bool {
455 // TODO: vectorize
456 for _, b := range []byte(v) {
457 if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) {
458 return true
459 }
460 }
461 return false
462 }
463
464 // caller should have checked that the string does not contain new lines or ' .
465 func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
466 b = append(b, literalQuote)
467 b = append(b, v...)
468 b = append(b, literalQuote)
469
470 return b
471 }
472
473 func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
474 stringQuote := `"`
475
476 if multiline {
477 stringQuote = `"""`
478 }
479
480 b = append(b, stringQuote...)
481 if multiline {
482 b = append(b, '\n')
483 }
484
485 const (
486 hextable = "0123456789ABCDEF"
487 // U+0000 to U+0008, U+000A to U+001F, U+007F
488 nul = 0x0
489 bs = 0x8
490 lf = 0xa
491 us = 0x1f
492 del = 0x7f
493 )
494
495 for _, r := range []byte(v) {
496 switch r {
497 case '\\':
498 b = append(b, `\\`...)
499 case '"':
500 b = append(b, `\"`...)
501 case '\b':
502 b = append(b, `\b`...)
503 case '\f':
504 b = append(b, `\f`...)
505 case '\n':
506 if multiline {
507 b = append(b, r)
508 } else {
509 b = append(b, `\n`...)
510 }
511 case '\r':
512 b = append(b, `\r`...)
513 case '\t':
514 b = append(b, `\t`...)
515 default:
516 switch {
517 case r >= nul && r <= bs, r >= lf && r <= us, r == del:
518 b = append(b, `\u00`...)
519 b = append(b, hextable[r>>4])
520 b = append(b, hextable[r&0x0f])
521 default:
522 b = append(b, r)
523 }
524 }
525 }
526
527 b = append(b, stringQuote...)
528
529 return b
530 }
531
532 // caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
533 func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
534 return append(b, v...)
535 }
536
537 func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) {
538 if len(ctx.parentKey) == 0 {
539 return b, nil
540 }
541
542 b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
543
544 b = enc.commented(ctx.commented, b)
545
546 b = enc.indent(ctx.indent, b)
547
548 b = append(b, '[')
549
550 b = enc.encodeKey(b, ctx.parentKey[0])
551
552 for _, k := range ctx.parentKey[1:] {
553 b = append(b, '.')
554 b = enc.encodeKey(b, k)
555 }
556
557 b = append(b, "]\n"...)
558
559 return b, nil
560 }
561
562 //nolint:cyclop
563 func (enc *Encoder) encodeKey(b []byte, k string) []byte {
564 needsQuotation := false
565 cannotUseLiteral := false
566
567 if len(k) == 0 {
568 return append(b, "''"...)
569 }
570
571 for _, c := range k {
572 if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
573 continue
574 }
575
576 if c == literalQuote {
577 cannotUseLiteral = true
578 }
579
580 needsQuotation = true
581 }
582
583 if needsQuotation && needsQuoting(k) {
584 cannotUseLiteral = true
585 }
586
587 switch {
588 case cannotUseLiteral:
589 return enc.encodeQuotedString(false, b, k)
590 case needsQuotation:
591 return enc.encodeLiteralString(b, k)
592 default:
593 return enc.encodeUnquotedKey(b, k)
594 }
595 }
596
597 func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
598 keyType := k.Type()
599 switch {
600 case keyType.Kind() == reflect.String:
601 return k.String(), nil
602
603 case keyType.Implements(textMarshalerType):
604 keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()
605 if err != nil {
606 return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
607 }
608 return string(keyB), nil
609 }
610 return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
611 }
612
613 func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
614 var (
615 t table
616 emptyValueOptions valueOptions
617 )
618
619 iter := v.MapRange()
620 for iter.Next() {
621 v := iter.Value()
622
623 if isNil(v) {
624 continue
625 }
626
627 k, err := enc.keyToString(iter.Key())
628 if err != nil {
629 return nil, err
630 }
631
632 if willConvertToTableOrArrayTable(ctx, v) {
633 t.pushTable(k, v, emptyValueOptions)
634 } else {
635 t.pushKV(k, v, emptyValueOptions)
636 }
637 }
638
639 sortEntriesByKey(t.kvs)
640 sortEntriesByKey(t.tables)
641
642 return enc.encodeTable(b, ctx, t)
643 }
644
645 func sortEntriesByKey(e []entry) {
646 sort.Slice(e, func(i, j int) bool {
647 return e[i].Key < e[j].Key
648 })
649 }
650
651 type entry struct {
652 Key string
653 Value reflect.Value
654 Options valueOptions
655 }
656
657 type table struct {
658 kvs []entry
659 tables []entry
660 }
661
662 func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
663 for _, e := range t.kvs {
664 if e.Key == k {
665 return
666 }
667 }
668
669 t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
670 }
671
672 func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
673 for _, e := range t.tables {
674 if e.Key == k {
675 return
676 }
677 }
678 t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
679 }
680
681 func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
682 // TODO: cache this
683 typ := v.Type()
684 for i := 0; i < typ.NumField(); i++ {
685 fieldType := typ.Field(i)
686
687 // only consider exported fields
688 if fieldType.PkgPath != "" {
689 continue
690 }
691
692 tag := fieldType.Tag.Get("toml")
693
694 // special field name to skip field
695 if tag == "-" {
696 continue
697 }
698
699 k, opts := parseTag(tag)
700 if !isValidName(k) {
701 k = ""
702 }
703
704 f := v.Field(i)
705
706 if k == "" {
707 if fieldType.Anonymous {
708 if fieldType.Type.Kind() == reflect.Struct {
709 walkStruct(ctx, t, f)
710 }
711 continue
712 } else {
713 k = fieldType.Name
714 }
715 }
716
717 if isNil(f) {
718 continue
719 }
720
721 options := valueOptions{
722 multiline: opts.multiline,
723 omitempty: opts.omitempty,
724 commented: opts.commented,
725 comment: fieldType.Tag.Get("comment"),
726 }
727
728 if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {
729 t.pushKV(k, f, options)
730 } else {
731 t.pushTable(k, f, options)
732 }
733 }
734 }
735
736 func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
737 var t table
738
739 walkStruct(ctx, &t, v)
740
741 return enc.encodeTable(b, ctx, t)
742 }
743
744 func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
745 for len(comment) > 0 {
746 var line string
747 idx := strings.IndexByte(comment, '\n')
748 if idx >= 0 {
749 line = comment[:idx]
750 comment = comment[idx+1:]
751 } else {
752 line = comment
753 comment = ""
754 }
755 b = enc.indent(indent, b)
756 b = append(b, "# "...)
757 b = append(b, line...)
758 b = append(b, '\n')
759 }
760 return b
761 }
762
763 func isValidName(s string) bool {
764 if s == "" {
765 return false
766 }
767 for _, c := range s {
768 switch {
769 case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
770 // Backslash and quote chars are reserved, but
771 // otherwise any punctuation chars are allowed
772 // in a tag name.
773 case !unicode.IsLetter(c) && !unicode.IsDigit(c):
774 return false
775 }
776 }
777 return true
778 }
779
780 type tagOptions struct {
781 multiline bool
782 inline bool
783 omitempty bool
784 commented bool
785 }
786
787 func parseTag(tag string) (string, tagOptions) {
788 opts := tagOptions{}
789
790 idx := strings.Index(tag, ",")
791 if idx == -1 {
792 return tag, opts
793 }
794
795 raw := tag[idx+1:]
796 tag = string(tag[:idx])
797 for raw != "" {
798 var o string
799 i := strings.Index(raw, ",")
800 if i >= 0 {
801 o, raw = raw[:i], raw[i+1:]
802 } else {
803 o, raw = raw, ""
804 }
805 switch o {
806 case "multiline":
807 opts.multiline = true
808 case "inline":
809 opts.inline = true
810 case "omitempty":
811 opts.omitempty = true
812 case "commented":
813 opts.commented = true
814 }
815 }
816
817 return tag, opts
818 }
819
820 func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) {
821 var err error
822
823 ctx.shiftKey()
824
825 if ctx.insideKv || (ctx.inline && !ctx.isRoot()) {
826 return enc.encodeTableInline(b, ctx, t)
827 }
828
829 if !ctx.skipTableHeader {
830 b, err = enc.encodeTableHeader(ctx, b)
831 if err != nil {
832 return nil, err
833 }
834
835 if enc.indentTables && len(ctx.parentKey) > 0 {
836 ctx.indent++
837 }
838 }
839 ctx.skipTableHeader = false
840
841 hasNonEmptyKV := false
842 for _, kv := range t.kvs {
843 if shouldOmitEmpty(kv.Options, kv.Value) {
844 continue
845 }
846 hasNonEmptyKV = true
847
848 ctx.setKey(kv.Key)
849 ctx2 := ctx
850 ctx2.commented = kv.Options.commented || ctx2.commented
851
852 b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value)
853 if err != nil {
854 return nil, err
855 }
856
857 b = append(b, '\n')
858 }
859
860 first := true
861 for _, table := range t.tables {
862 if shouldOmitEmpty(table.Options, table.Value) {
863 continue
864 }
865 if first {
866 first = false
867 if hasNonEmptyKV {
868 b = append(b, '\n')
869 }
870 } else {
871 b = append(b, "\n"...)
872 }
873
874 ctx.setKey(table.Key)
875
876 ctx.options = table.Options
877 ctx2 := ctx
878 ctx2.commented = ctx2.commented || ctx.options.commented
879
880 b, err = enc.encode(b, ctx2, table.Value)
881 if err != nil {
882 return nil, err
883 }
884 }
885
886 return b, nil
887 }
888
889 func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) {
890 var err error
891
892 b = append(b, '{')
893
894 first := true
895 for _, kv := range t.kvs {
896 if shouldOmitEmpty(kv.Options, kv.Value) {
897 continue
898 }
899
900 if first {
901 first = false
902 } else {
903 b = append(b, `, `...)
904 }
905
906 ctx.setKey(kv.Key)
907
908 b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
909 if err != nil {
910 return nil, err
911 }
912 }
913
914 if len(t.tables) > 0 {
915 panic("inline table cannot contain nested tables, only key-values")
916 }
917
918 b = append(b, "}"...)
919
920 return b, nil
921 }
922
923 func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
924 if !v.IsValid() {
925 return false
926 }
927 if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
928 return false
929 }
930
931 t := v.Type()
932 switch t.Kind() {
933 case reflect.Map, reflect.Struct:
934 return !ctx.inline
935 case reflect.Interface:
936 return willConvertToTable(ctx, v.Elem())
937 case reflect.Ptr:
938 if v.IsNil() {
939 return false
940 }
941
942 return willConvertToTable(ctx, v.Elem())
943 default:
944 return false
945 }
946 }
947
948 func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
949 if ctx.insideKv {
950 return false
951 }
952 t := v.Type()
953
954 if t.Kind() == reflect.Interface {
955 return willConvertToTableOrArrayTable(ctx, v.Elem())
956 }
957
958 if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
959 if v.Len() == 0 {
960 // An empty slice should be a kv = [].
961 return false
962 }
963
964 for i := 0; i < v.Len(); i++ {
965 t := willConvertToTable(ctx, v.Index(i))
966
967 if !t {
968 return false
969 }
970 }
971
972 return true
973 }
974
975 return willConvertToTable(ctx, v)
976 }
977
978 func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
979 if v.Len() == 0 {
980 b = append(b, "[]"...)
981
982 return b, nil
983 }
984
985 if willConvertToTableOrArrayTable(ctx, v) {
986 return enc.encodeSliceAsArrayTable(b, ctx, v)
987 }
988
989 return enc.encodeSliceAsArray(b, ctx, v)
990 }
991
992 // caller should have checked that v is a slice that only contains values that
993 // encode into tables.
994 func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
995 ctx.shiftKey()
996
997 scratch := make([]byte, 0, 64)
998
999 scratch = enc.commented(ctx.commented, scratch)
1000
1001 scratch = append(scratch, "[["...)
1002
1003 for i, k := range ctx.parentKey {
1004 if i > 0 {
1005 scratch = append(scratch, '.')
1006 }
1007
1008 scratch = enc.encodeKey(scratch, k)
1009 }
1010
1011 scratch = append(scratch, "]]\n"...)
1012 ctx.skipTableHeader = true
1013
1014 b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
1015
1016 if enc.indentTables {
1017 ctx.indent++
1018 }
1019
1020 for i := 0; i < v.Len(); i++ {
1021 if i != 0 {
1022 b = append(b, "\n"...)
1023 }
1024
1025 b = append(b, scratch...)
1026
1027 var err error
1028 b, err = enc.encode(b, ctx, v.Index(i))
1029 if err != nil {
1030 return nil, err
1031 }
1032 }
1033
1034 return b, nil
1035 }
1036
1037 func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
1038 multiline := ctx.options.multiline || enc.arraysMultiline
1039 separator := ", "
1040
1041 b = append(b, '[')
1042
1043 subCtx := ctx
1044 subCtx.options = valueOptions{}
1045
1046 if multiline {
1047 separator = ",\n"
1048
1049 b = append(b, '\n')
1050
1051 subCtx.indent++
1052 }
1053
1054 var err error
1055 first := true
1056
1057 for i := 0; i < v.Len(); i++ {
1058 if first {
1059 first = false
1060 } else {
1061 b = append(b, separator...)
1062 }
1063
1064 if multiline {
1065 b = enc.indent(subCtx.indent, b)
1066 }
1067
1068 b, err = enc.encode(b, subCtx, v.Index(i))
1069 if err != nil {
1070 return nil, err
1071 }
1072 }
1073
1074 if multiline {
1075 b = append(b, '\n')
1076 b = enc.indent(ctx.indent, b)
1077 }
1078
1079 b = append(b, ']')
1080
1081 return b, nil
1082 }
1083
1084 func (enc *Encoder) indent(level int, b []byte) []byte {
1085 for i := 0; i < level; i++ {
1086 b = append(b, enc.indentSymbol...)
1087 }
1088
1089 return b
1090 }
1091