vendor/github.com/pelletier/go-toml/v2/marshaler.go
changeset 260 445e01aede7e
child 265 05c40b36d3b2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/github.com/pelletier/go-toml/v2/marshaler.go	Tue Aug 23 22:39:43 2022 +0200
@@ -0,0 +1,1040 @@
+package toml
+
+import (
+	"bytes"
+	"encoding"
+	"fmt"
+	"io"
+	"math"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+	"unicode"
+)
+
+// Marshal serializes a Go value as a TOML document.
+//
+// It is a shortcut for Encoder.Encode() with the default options.
+func Marshal(v interface{}) ([]byte, error) {
+	var buf bytes.Buffer
+	enc := NewEncoder(&buf)
+
+	err := enc.Encode(v)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+// Encoder writes a TOML document to an output stream.
+type Encoder struct {
+	// output
+	w io.Writer
+
+	// global settings
+	tablesInline    bool
+	arraysMultiline bool
+	indentSymbol    string
+	indentTables    bool
+}
+
+// NewEncoder returns a new Encoder that writes to w.
+func NewEncoder(w io.Writer) *Encoder {
+	return &Encoder{
+		w:            w,
+		indentSymbol: "  ",
+	}
+}
+
+// SetTablesInline forces the encoder to emit all tables inline.
+//
+// This behavior can be controlled on an individual struct field basis with the
+// inline tag:
+//
+//	MyField `toml:",inline"`
+func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
+	enc.tablesInline = inline
+	return enc
+}
+
+// SetArraysMultiline forces the encoder to emit all arrays with one element per
+// line.
+//
+// This behavior can be controlled on an individual struct field basis with the multiline tag:
+//
+//	MyField `multiline:"true"`
+func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder {
+	enc.arraysMultiline = multiline
+	return enc
+}
+
+// SetIndentSymbol defines the string that should be used for indentation. The
+// provided string is repeated for each indentation level. Defaults to two
+// spaces.
+func (enc *Encoder) SetIndentSymbol(s string) *Encoder {
+	enc.indentSymbol = s
+	return enc
+}
+
+// SetIndentTables forces the encoder to intent tables and array tables.
+func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
+	enc.indentTables = indent
+	return enc
+}
+
+// Encode writes a TOML representation of v to the stream.
+//
+// If v cannot be represented to TOML it returns an error.
+//
+// # Encoding rules
+//
+// A top level slice containing only maps or structs is encoded as [[table
+// array]].
+//
+// All slices not matching rule 1 are encoded as [array]. As a result, any map
+// or struct they contain is encoded as an {inline table}.
+//
+// Nil interfaces and nil pointers are not supported.
+//
+// Keys in key-values always have one part.
+//
+// Intermediate tables are always printed.
+//
+// By default, strings are encoded as literal string, unless they contain either
+// a newline character or a single quote. In that case they are emitted as
+// quoted strings.
+//
+// Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so
+// results in an error. This rule exists because the TOML specification only
+// requires parsers to support at least the 64 bits integer range. Allowing
+// larger numbers would create non-standard TOML documents, which may not be
+// readable (at best) by other implementations. To encode such numbers, a
+// solution is a custom type that implements encoding.TextMarshaler.
+//
+// When encoding structs, fields are encoded in order of definition, with their
+// exact name.
+//
+// Tables and array tables are separated by empty lines. However, consecutive
+// subtables definitions are not. For example:
+//
+//	[top1]
+//
+//	[top2]
+//	[top2.child1]
+//
+//	[[array]]
+//
+//	[[array]]
+//	[array.child2]
+//
+// # Struct tags
+//
+// The encoding of each public struct field can be customized by the format
+// string in the "toml" key of the struct field's tag. This follows
+// encoding/json's convention. The format string starts with the name of the
+// field, optionally followed by a comma-separated list of options. The name may
+// be empty in order to provide options without overriding the default name.
+//
+// The "multiline" option emits strings as quoted multi-line TOML strings. It
+// has no effect on fields that would not be encoded as strings.
+//
+// The "inline" option turns fields that would be emitted as tables into inline
+// tables instead. It has no effect on other fields.
+//
+// The "omitempty" option prevents empty values or groups from being emitted.
+//
+// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
+// a TOML comment before the value being annotated. Comments are ignored inside
+// inline tables. For array tables, the comment is only present before the first
+// element of the array.
+func (enc *Encoder) Encode(v interface{}) error {
+	var (
+		b   []byte
+		ctx encoderCtx
+	)
+
+	ctx.inline = enc.tablesInline
+
+	if v == nil {
+		return fmt.Errorf("toml: cannot encode a nil interface")
+	}
+
+	b, err := enc.encode(b, ctx, reflect.ValueOf(v))
+	if err != nil {
+		return err
+	}
+
+	_, err = enc.w.Write(b)
+	if err != nil {
+		return fmt.Errorf("toml: cannot write: %w", err)
+	}
+
+	return nil
+}
+
+type valueOptions struct {
+	multiline bool
+	omitempty bool
+	comment   string
+}
+
+type encoderCtx struct {
+	// Current top-level key.
+	parentKey []string
+
+	// Key that should be used for a KV.
+	key string
+	// Extra flag to account for the empty string
+	hasKey bool
+
+	// Set to true to indicate that the encoder is inside a KV, so that all
+	// tables need to be inlined.
+	insideKv bool
+
+	// Set to true to skip the first table header in an array table.
+	skipTableHeader bool
+
+	// Should the next table be encoded as inline
+	inline bool
+
+	// Indentation level
+	indent int
+
+	// Options coming from struct tags
+	options valueOptions
+}
+
+func (ctx *encoderCtx) shiftKey() {
+	if ctx.hasKey {
+		ctx.parentKey = append(ctx.parentKey, ctx.key)
+		ctx.clearKey()
+	}
+}
+
+func (ctx *encoderCtx) setKey(k string) {
+	ctx.key = k
+	ctx.hasKey = true
+}
+
+func (ctx *encoderCtx) clearKey() {
+	ctx.key = ""
+	ctx.hasKey = false
+}
+
+func (ctx *encoderCtx) isRoot() bool {
+	return len(ctx.parentKey) == 0 && !ctx.hasKey
+}
+
+func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
+	i := v.Interface()
+
+	switch x := i.(type) {
+	case time.Time:
+		if x.Nanosecond() > 0 {
+			return x.AppendFormat(b, time.RFC3339Nano), nil
+		}
+		return x.AppendFormat(b, time.RFC3339), nil
+	case LocalTime:
+		return append(b, x.String()...), nil
+	case LocalDate:
+		return append(b, x.String()...), nil
+	case LocalDateTime:
+		return append(b, x.String()...), nil
+	}
+
+	hasTextMarshaler := v.Type().Implements(textMarshalerType)
+	if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
+		if !hasTextMarshaler {
+			v = v.Addr()
+		}
+
+		if ctx.isRoot() {
+			return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type())
+		}
+
+		text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
+		if err != nil {
+			return nil, err
+		}
+
+		b = enc.encodeString(b, string(text), ctx.options)
+
+		return b, nil
+	}
+
+	switch v.Kind() {
+	// containers
+	case reflect.Map:
+		return enc.encodeMap(b, ctx, v)
+	case reflect.Struct:
+		return enc.encodeStruct(b, ctx, v)
+	case reflect.Slice:
+		return enc.encodeSlice(b, ctx, v)
+	case reflect.Interface:
+		if v.IsNil() {
+			return nil, fmt.Errorf("toml: encoding a nil interface is not supported")
+		}
+
+		return enc.encode(b, ctx, v.Elem())
+	case reflect.Ptr:
+		if v.IsNil() {
+			return enc.encode(b, ctx, reflect.Zero(v.Type().Elem()))
+		}
+
+		return enc.encode(b, ctx, v.Elem())
+
+	// values
+	case reflect.String:
+		b = enc.encodeString(b, v.String(), ctx.options)
+	case reflect.Float32:
+		f := v.Float()
+
+		if math.IsNaN(f) {
+			b = append(b, "nan"...)
+		} else if f > math.MaxFloat32 {
+			b = append(b, "inf"...)
+		} else if f < -math.MaxFloat32 {
+			b = append(b, "-inf"...)
+		} else if math.Trunc(f) == f {
+			b = strconv.AppendFloat(b, f, 'f', 1, 32)
+		} else {
+			b = strconv.AppendFloat(b, f, 'f', -1, 32)
+		}
+	case reflect.Float64:
+		f := v.Float()
+		if math.IsNaN(f) {
+			b = append(b, "nan"...)
+		} else if f > math.MaxFloat64 {
+			b = append(b, "inf"...)
+		} else if f < -math.MaxFloat64 {
+			b = append(b, "-inf"...)
+		} else if math.Trunc(f) == f {
+			b = strconv.AppendFloat(b, f, 'f', 1, 64)
+		} else {
+			b = strconv.AppendFloat(b, f, 'f', -1, 64)
+		}
+	case reflect.Bool:
+		if v.Bool() {
+			b = append(b, "true"...)
+		} else {
+			b = append(b, "false"...)
+		}
+	case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
+		x := v.Uint()
+		if x > uint64(math.MaxInt64) {
+			return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))
+		}
+		b = strconv.AppendUint(b, x, 10)
+	case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
+		b = strconv.AppendInt(b, v.Int(), 10)
+	default:
+		return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind())
+	}
+
+	return b, nil
+}
+
+func isNil(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Ptr, reflect.Interface, reflect.Map:
+		return v.IsNil()
+	default:
+		return false
+	}
+}
+
+func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
+	return options.omitempty && isEmptyValue(v)
+}
+
+func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
+	var err error
+
+	if !ctx.inline {
+		b = enc.encodeComment(ctx.indent, options.comment, b)
+	}
+
+	b = enc.indent(ctx.indent, b)
+	b = enc.encodeKey(b, ctx.key)
+	b = append(b, " = "...)
+
+	// create a copy of the context because the value of a KV shouldn't
+	// modify the global context.
+	subctx := ctx
+	subctx.insideKv = true
+	subctx.shiftKey()
+	subctx.options = options
+
+	b, err = enc.encode(b, subctx, v)
+	if err != nil {
+		return nil, err
+	}
+
+	return b, nil
+}
+
+func isEmptyValue(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Struct:
+		return isEmptyStruct(v)
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return v.IsNil()
+	}
+	return false
+}
+
+func isEmptyStruct(v reflect.Value) bool {
+	// TODO: merge with walkStruct and cache.
+	typ := v.Type()
+	for i := 0; i < typ.NumField(); i++ {
+		fieldType := typ.Field(i)
+
+		// only consider exported fields
+		if fieldType.PkgPath != "" {
+			continue
+		}
+
+		tag := fieldType.Tag.Get("toml")
+
+		// special field name to skip field
+		if tag == "-" {
+			continue
+		}
+
+		f := v.Field(i)
+
+		if !isEmptyValue(f) {
+			return false
+		}
+	}
+
+	return true
+}
+
+const literalQuote = '\''
+
+func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
+	if needsQuoting(v) {
+		return enc.encodeQuotedString(options.multiline, b, v)
+	}
+
+	return enc.encodeLiteralString(b, v)
+}
+
+func needsQuoting(v string) bool {
+	// TODO: vectorize
+	for _, b := range []byte(v) {
+		if b == '\'' || b == '\r' || b == '\n' || invalidAscii(b) {
+			return true
+		}
+	}
+	return false
+}
+
+// caller should have checked that the string does not contain new lines or ' .
+func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
+	b = append(b, literalQuote)
+	b = append(b, v...)
+	b = append(b, literalQuote)
+
+	return b
+}
+
+func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
+	stringQuote := `"`
+
+	if multiline {
+		stringQuote = `"""`
+	}
+
+	b = append(b, stringQuote...)
+	if multiline {
+		b = append(b, '\n')
+	}
+
+	const (
+		hextable = "0123456789ABCDEF"
+		// U+0000 to U+0008, U+000A to U+001F, U+007F
+		nul = 0x0
+		bs  = 0x8
+		lf  = 0xa
+		us  = 0x1f
+		del = 0x7f
+	)
+
+	for _, r := range []byte(v) {
+		switch r {
+		case '\\':
+			b = append(b, `\\`...)
+		case '"':
+			b = append(b, `\"`...)
+		case '\b':
+			b = append(b, `\b`...)
+		case '\f':
+			b = append(b, `\f`...)
+		case '\n':
+			if multiline {
+				b = append(b, r)
+			} else {
+				b = append(b, `\n`...)
+			}
+		case '\r':
+			b = append(b, `\r`...)
+		case '\t':
+			b = append(b, `\t`...)
+		default:
+			switch {
+			case r >= nul && r <= bs, r >= lf && r <= us, r == del:
+				b = append(b, `\u00`...)
+				b = append(b, hextable[r>>4])
+				b = append(b, hextable[r&0x0f])
+			default:
+				b = append(b, r)
+			}
+		}
+	}
+
+	b = append(b, stringQuote...)
+
+	return b
+}
+
+// caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
+func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
+	return append(b, v...)
+}
+
+func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) {
+	if len(ctx.parentKey) == 0 {
+		return b, nil
+	}
+
+	b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
+
+	b = enc.indent(ctx.indent, b)
+
+	b = append(b, '[')
+
+	b = enc.encodeKey(b, ctx.parentKey[0])
+
+	for _, k := range ctx.parentKey[1:] {
+		b = append(b, '.')
+		b = enc.encodeKey(b, k)
+	}
+
+	b = append(b, "]\n"...)
+
+	return b, nil
+}
+
+//nolint:cyclop
+func (enc *Encoder) encodeKey(b []byte, k string) []byte {
+	needsQuotation := false
+	cannotUseLiteral := false
+
+	if len(k) == 0 {
+		return append(b, "''"...)
+	}
+
+	for _, c := range k {
+		if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
+			continue
+		}
+
+		if c == literalQuote {
+			cannotUseLiteral = true
+		}
+
+		needsQuotation = true
+	}
+
+	if needsQuotation && needsQuoting(k) {
+		cannotUseLiteral = true
+	}
+
+	switch {
+	case cannotUseLiteral:
+		return enc.encodeQuotedString(false, b, k)
+	case needsQuotation:
+		return enc.encodeLiteralString(b, k)
+	default:
+		return enc.encodeUnquotedKey(b, k)
+	}
+}
+
+func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
+	if v.Type().Key().Kind() != reflect.String {
+		return nil, fmt.Errorf("toml: type %s is not supported as a map key", v.Type().Key().Kind())
+	}
+
+	var (
+		t                 table
+		emptyValueOptions valueOptions
+	)
+
+	iter := v.MapRange()
+	for iter.Next() {
+		k := iter.Key().String()
+		v := iter.Value()
+
+		if isNil(v) {
+			continue
+		}
+
+		if willConvertToTableOrArrayTable(ctx, v) {
+			t.pushTable(k, v, emptyValueOptions)
+		} else {
+			t.pushKV(k, v, emptyValueOptions)
+		}
+	}
+
+	sortEntriesByKey(t.kvs)
+	sortEntriesByKey(t.tables)
+
+	return enc.encodeTable(b, ctx, t)
+}
+
+func sortEntriesByKey(e []entry) {
+	sort.Slice(e, func(i, j int) bool {
+		return e[i].Key < e[j].Key
+	})
+}
+
+type entry struct {
+	Key     string
+	Value   reflect.Value
+	Options valueOptions
+}
+
+type table struct {
+	kvs    []entry
+	tables []entry
+}
+
+func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
+	for _, e := range t.kvs {
+		if e.Key == k {
+			return
+		}
+	}
+
+	t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
+}
+
+func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
+	for _, e := range t.tables {
+		if e.Key == k {
+			return
+		}
+	}
+	t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
+}
+
+func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
+	// TODO: cache this
+	typ := v.Type()
+	for i := 0; i < typ.NumField(); i++ {
+		fieldType := typ.Field(i)
+
+		// only consider exported fields
+		if fieldType.PkgPath != "" {
+			continue
+		}
+
+		tag := fieldType.Tag.Get("toml")
+
+		// special field name to skip field
+		if tag == "-" {
+			continue
+		}
+
+		k, opts := parseTag(tag)
+		if !isValidName(k) {
+			k = ""
+		}
+
+		f := v.Field(i)
+
+		if k == "" {
+			if fieldType.Anonymous {
+				if fieldType.Type.Kind() == reflect.Struct {
+					walkStruct(ctx, t, f)
+				}
+				continue
+			} else {
+				k = fieldType.Name
+			}
+		}
+
+		if isNil(f) {
+			continue
+		}
+
+		options := valueOptions{
+			multiline: opts.multiline,
+			omitempty: opts.omitempty,
+			comment:   fieldType.Tag.Get("comment"),
+		}
+
+		if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {
+			t.pushKV(k, f, options)
+		} else {
+			t.pushTable(k, f, options)
+		}
+	}
+}
+
+func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
+	var t table
+
+	walkStruct(ctx, &t, v)
+
+	return enc.encodeTable(b, ctx, t)
+}
+
+func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
+	for len(comment) > 0 {
+		var line string
+		idx := strings.IndexByte(comment, '\n')
+		if idx >= 0 {
+			line = comment[:idx]
+			comment = comment[idx+1:]
+		} else {
+			line = comment
+			comment = ""
+		}
+		b = enc.indent(indent, b)
+		b = append(b, "# "...)
+		b = append(b, line...)
+		b = append(b, '\n')
+	}
+	return b
+}
+
+func isValidName(s string) bool {
+	if s == "" {
+		return false
+	}
+	for _, c := range s {
+		switch {
+		case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
+			// Backslash and quote chars are reserved, but
+			// otherwise any punctuation chars are allowed
+			// in a tag name.
+		case !unicode.IsLetter(c) && !unicode.IsDigit(c):
+			return false
+		}
+	}
+	return true
+}
+
+type tagOptions struct {
+	multiline bool
+	inline    bool
+	omitempty bool
+}
+
+func parseTag(tag string) (string, tagOptions) {
+	opts := tagOptions{}
+
+	idx := strings.Index(tag, ",")
+	if idx == -1 {
+		return tag, opts
+	}
+
+	raw := tag[idx+1:]
+	tag = string(tag[:idx])
+	for raw != "" {
+		var o string
+		i := strings.Index(raw, ",")
+		if i >= 0 {
+			o, raw = raw[:i], raw[i+1:]
+		} else {
+			o, raw = raw, ""
+		}
+		switch o {
+		case "multiline":
+			opts.multiline = true
+		case "inline":
+			opts.inline = true
+		case "omitempty":
+			opts.omitempty = true
+		}
+	}
+
+	return tag, opts
+}
+
+func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) {
+	var err error
+
+	ctx.shiftKey()
+
+	if ctx.insideKv || (ctx.inline && !ctx.isRoot()) {
+		return enc.encodeTableInline(b, ctx, t)
+	}
+
+	if !ctx.skipTableHeader {
+		b, err = enc.encodeTableHeader(ctx, b)
+		if err != nil {
+			return nil, err
+		}
+
+		if enc.indentTables && len(ctx.parentKey) > 0 {
+			ctx.indent++
+		}
+	}
+	ctx.skipTableHeader = false
+
+	hasNonEmptyKV := false
+	for _, kv := range t.kvs {
+		if shouldOmitEmpty(kv.Options, kv.Value) {
+			continue
+		}
+		hasNonEmptyKV = true
+
+		ctx.setKey(kv.Key)
+
+		b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
+		if err != nil {
+			return nil, err
+		}
+
+		b = append(b, '\n')
+	}
+
+	first := true
+	for _, table := range t.tables {
+		if shouldOmitEmpty(table.Options, table.Value) {
+			continue
+		}
+		if first {
+			first = false
+			if hasNonEmptyKV {
+				b = append(b, '\n')
+			}
+		} else {
+			b = append(b, "\n"...)
+		}
+
+		ctx.setKey(table.Key)
+
+		ctx.options = table.Options
+
+		b, err = enc.encode(b, ctx, table.Value)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return b, nil
+}
+
+func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) {
+	var err error
+
+	b = append(b, '{')
+
+	first := true
+	for _, kv := range t.kvs {
+		if shouldOmitEmpty(kv.Options, kv.Value) {
+			continue
+		}
+
+		if first {
+			first = false
+		} else {
+			b = append(b, `, `...)
+		}
+
+		ctx.setKey(kv.Key)
+
+		b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if len(t.tables) > 0 {
+		panic("inline table cannot contain nested tables, only key-values")
+	}
+
+	b = append(b, "}"...)
+
+	return b, nil
+}
+
+func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
+	if !v.IsValid() {
+		return false
+	}
+	if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
+		return false
+	}
+
+	t := v.Type()
+	switch t.Kind() {
+	case reflect.Map, reflect.Struct:
+		return !ctx.inline
+	case reflect.Interface:
+		return willConvertToTable(ctx, v.Elem())
+	case reflect.Ptr:
+		if v.IsNil() {
+			return false
+		}
+
+		return willConvertToTable(ctx, v.Elem())
+	default:
+		return false
+	}
+}
+
+func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
+	if ctx.insideKv {
+		return false
+	}
+	t := v.Type()
+
+	if t.Kind() == reflect.Interface {
+		return willConvertToTableOrArrayTable(ctx, v.Elem())
+	}
+
+	if t.Kind() == reflect.Slice {
+		if v.Len() == 0 {
+			// An empty slice should be a kv = [].
+			return false
+		}
+
+		for i := 0; i < v.Len(); i++ {
+			t := willConvertToTable(ctx, v.Index(i))
+
+			if !t {
+				return false
+			}
+		}
+
+		return true
+	}
+
+	return willConvertToTable(ctx, v)
+}
+
+func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
+	if v.Len() == 0 {
+		b = append(b, "[]"...)
+
+		return b, nil
+	}
+
+	if willConvertToTableOrArrayTable(ctx, v) {
+		return enc.encodeSliceAsArrayTable(b, ctx, v)
+	}
+
+	return enc.encodeSliceAsArray(b, ctx, v)
+}
+
+// caller should have checked that v is a slice that only contains values that
+// encode into tables.
+func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
+	ctx.shiftKey()
+
+	scratch := make([]byte, 0, 64)
+	scratch = append(scratch, "[["...)
+
+	for i, k := range ctx.parentKey {
+		if i > 0 {
+			scratch = append(scratch, '.')
+		}
+
+		scratch = enc.encodeKey(scratch, k)
+	}
+
+	scratch = append(scratch, "]]\n"...)
+	ctx.skipTableHeader = true
+
+	b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
+
+	for i := 0; i < v.Len(); i++ {
+		if i != 0 {
+			b = append(b, "\n"...)
+		}
+
+		b = append(b, scratch...)
+
+		var err error
+		b, err = enc.encode(b, ctx, v.Index(i))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return b, nil
+}
+
+func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
+	multiline := ctx.options.multiline || enc.arraysMultiline
+	separator := ", "
+
+	b = append(b, '[')
+
+	subCtx := ctx
+	subCtx.options = valueOptions{}
+
+	if multiline {
+		separator = ",\n"
+
+		b = append(b, '\n')
+
+		subCtx.indent++
+	}
+
+	var err error
+	first := true
+
+	for i := 0; i < v.Len(); i++ {
+		if first {
+			first = false
+		} else {
+			b = append(b, separator...)
+		}
+
+		if multiline {
+			b = enc.indent(subCtx.indent, b)
+		}
+
+		b, err = enc.encode(b, subCtx, v.Index(i))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if multiline {
+		b = append(b, '\n')
+		b = enc.indent(ctx.indent, b)
+	}
+
+	b = append(b, ']')
+
+	return b, nil
+}
+
+func (enc *Encoder) indent(level int, b []byte) []byte {
+	for i := 0; i < level; i++ {
+		b = append(b, enc.indentSymbol...)
+	}
+
+	return b
+}