Path: blob/main/vendor/github.com/pelletier/go-toml/v2/marshaler.go
3531 views
package toml12import (3"bytes"4"encoding"5"encoding/json"6"fmt"7"io"8"math"9"reflect"10"slices"11"strconv"12"strings"13"time"14"unicode"1516"github.com/pelletier/go-toml/v2/internal/characters"17)1819// Marshal serializes a Go value as a TOML document.20//21// It is a shortcut for Encoder.Encode() with the default options.22func Marshal(v interface{}) ([]byte, error) {23var buf bytes.Buffer24enc := NewEncoder(&buf)2526err := enc.Encode(v)27if err != nil {28return nil, err29}3031return buf.Bytes(), nil32}3334// Encoder writes a TOML document to an output stream.35type Encoder struct {36// output37w io.Writer3839// global settings40tablesInline bool41arraysMultiline bool42indentSymbol string43indentTables bool44marshalJsonNumbers bool45}4647// NewEncoder returns a new Encoder that writes to w.48func NewEncoder(w io.Writer) *Encoder {49return &Encoder{50w: w,51indentSymbol: " ",52}53}5455// SetTablesInline forces the encoder to emit all tables inline.56//57// This behavior can be controlled on an individual struct field basis with the58// inline tag:59//60// MyField `toml:",inline"`61func (enc *Encoder) SetTablesInline(inline bool) *Encoder {62enc.tablesInline = inline63return enc64}6566// SetArraysMultiline forces the encoder to emit all arrays with one element per67// line.68//69// This behavior can be controlled on an individual struct field basis with the multiline tag:70//71// MyField `multiline:"true"`72func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder {73enc.arraysMultiline = multiline74return enc75}7677// SetIndentSymbol defines the string that should be used for indentation. The78// provided string is repeated for each indentation level. Defaults to two79// spaces.80func (enc *Encoder) SetIndentSymbol(s string) *Encoder {81enc.indentSymbol = s82return enc83}8485// SetIndentTables forces the encoder to intent tables and array tables.86func (enc *Encoder) SetIndentTables(indent bool) *Encoder {87enc.indentTables = indent88return enc89}9091// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a92// float or integer instead of relying on TextMarshaler to emit a string.93//94// *Unstable:* This method does not follow the compatibility guarantees of95// semver. It can be changed or removed without a new major version being96// issued.97func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder {98enc.marshalJsonNumbers = indent99return enc100}101102// Encode writes a TOML representation of v to the stream.103//104// If v cannot be represented to TOML it returns an error.105//106// # Encoding rules107//108// A top level slice containing only maps or structs is encoded as [[table109// array]].110//111// All slices not matching rule 1 are encoded as [array]. As a result, any map112// or struct they contain is encoded as an {inline table}.113//114// Nil interfaces and nil pointers are not supported.115//116// Keys in key-values always have one part.117//118// Intermediate tables are always printed.119//120// By default, strings are encoded as literal string, unless they contain either121// a newline character or a single quote. In that case they are emitted as122// quoted strings.123//124// Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so125// results in an error. This rule exists because the TOML specification only126// requires parsers to support at least the 64 bits integer range. Allowing127// larger numbers would create non-standard TOML documents, which may not be128// readable (at best) by other implementations. To encode such numbers, a129// solution is a custom type that implements encoding.TextMarshaler.130//131// When encoding structs, fields are encoded in order of definition, with their132// exact name.133//134// Tables and array tables are separated by empty lines. However, consecutive135// subtables definitions are not. For example:136//137// [top1]138//139// [top2]140// [top2.child1]141//142// [[array]]143//144// [[array]]145// [array.child2]146//147// # Struct tags148//149// The encoding of each public struct field can be customized by the format150// string in the "toml" key of the struct field's tag. This follows151// encoding/json's convention. The format string starts with the name of the152// field, optionally followed by a comma-separated list of options. The name may153// be empty in order to provide options without overriding the default name.154//155// The "multiline" option emits strings as quoted multi-line TOML strings. It156// has no effect on fields that would not be encoded as strings.157//158// The "inline" option turns fields that would be emitted as tables into inline159// tables instead. It has no effect on other fields.160//161// The "omitempty" option prevents empty values or groups from being emitted.162//163// The "commented" option prefixes the value and all its children with a comment164// symbol.165//166// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit167// a TOML comment before the value being annotated. Comments are ignored inside168// inline tables. For array tables, the comment is only present before the first169// element of the array.170func (enc *Encoder) Encode(v interface{}) error {171var (172b []byte173ctx encoderCtx174)175176ctx.inline = enc.tablesInline177178if v == nil {179return fmt.Errorf("toml: cannot encode a nil interface")180}181182b, err := enc.encode(b, ctx, reflect.ValueOf(v))183if err != nil {184return err185}186187_, err = enc.w.Write(b)188if err != nil {189return fmt.Errorf("toml: cannot write: %w", err)190}191192return nil193}194195type valueOptions struct {196multiline bool197omitempty bool198commented bool199comment string200}201202type encoderCtx struct {203// Current top-level key.204parentKey []string205206// Key that should be used for a KV.207key string208// Extra flag to account for the empty string209hasKey bool210211// Set to true to indicate that the encoder is inside a KV, so that all212// tables need to be inlined.213insideKv bool214215// Set to true to skip the first table header in an array table.216skipTableHeader bool217218// Should the next table be encoded as inline219inline bool220221// Indentation level222indent int223224// Prefix the current value with a comment.225commented bool226227// Options coming from struct tags228options valueOptions229}230231func (ctx *encoderCtx) shiftKey() {232if ctx.hasKey {233ctx.parentKey = append(ctx.parentKey, ctx.key)234ctx.clearKey()235}236}237238func (ctx *encoderCtx) setKey(k string) {239ctx.key = k240ctx.hasKey = true241}242243func (ctx *encoderCtx) clearKey() {244ctx.key = ""245ctx.hasKey = false246}247248func (ctx *encoderCtx) isRoot() bool {249return len(ctx.parentKey) == 0 && !ctx.hasKey250}251252func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {253i := v.Interface()254255switch x := i.(type) {256case time.Time:257if x.Nanosecond() > 0 {258return x.AppendFormat(b, time.RFC3339Nano), nil259}260return x.AppendFormat(b, time.RFC3339), nil261case LocalTime:262return append(b, x.String()...), nil263case LocalDate:264return append(b, x.String()...), nil265case LocalDateTime:266return append(b, x.String()...), nil267case json.Number:268if enc.marshalJsonNumbers {269if x == "" { /// Useful zero value.270return append(b, "0"...), nil271} else if v, err := x.Int64(); err == nil {272return enc.encode(b, ctx, reflect.ValueOf(v))273} else if f, err := x.Float64(); err == nil {274return enc.encode(b, ctx, reflect.ValueOf(f))275} else {276return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x)277}278}279}280281hasTextMarshaler := v.Type().Implements(textMarshalerType)282if hasTextMarshaler || (v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {283if !hasTextMarshaler {284v = v.Addr()285}286287if ctx.isRoot() {288return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type())289}290291text, err := v.Interface().(encoding.TextMarshaler).MarshalText()292if err != nil {293return nil, err294}295296b = enc.encodeString(b, string(text), ctx.options)297298return b, nil299}300301switch v.Kind() {302// containers303case reflect.Map:304return enc.encodeMap(b, ctx, v)305case reflect.Struct:306return enc.encodeStruct(b, ctx, v)307case reflect.Slice, reflect.Array:308return enc.encodeSlice(b, ctx, v)309case reflect.Interface:310if v.IsNil() {311return nil, fmt.Errorf("toml: encoding a nil interface is not supported")312}313314return enc.encode(b, ctx, v.Elem())315case reflect.Ptr:316if v.IsNil() {317return enc.encode(b, ctx, reflect.Zero(v.Type().Elem()))318}319320return enc.encode(b, ctx, v.Elem())321322// values323case reflect.String:324b = enc.encodeString(b, v.String(), ctx.options)325case reflect.Float32:326f := v.Float()327328if math.IsNaN(f) {329b = append(b, "nan"...)330} else if f > math.MaxFloat32 {331b = append(b, "inf"...)332} else if f < -math.MaxFloat32 {333b = append(b, "-inf"...)334} else if math.Trunc(f) == f {335b = strconv.AppendFloat(b, f, 'f', 1, 32)336} else {337b = strconv.AppendFloat(b, f, 'f', -1, 32)338}339case reflect.Float64:340f := v.Float()341if math.IsNaN(f) {342b = append(b, "nan"...)343} else if f > math.MaxFloat64 {344b = append(b, "inf"...)345} else if f < -math.MaxFloat64 {346b = append(b, "-inf"...)347} else if math.Trunc(f) == f {348b = strconv.AppendFloat(b, f, 'f', 1, 64)349} else {350b = strconv.AppendFloat(b, f, 'f', -1, 64)351}352case reflect.Bool:353if v.Bool() {354b = append(b, "true"...)355} else {356b = append(b, "false"...)357}358case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:359x := v.Uint()360if x > uint64(math.MaxInt64) {361return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))362}363b = strconv.AppendUint(b, x, 10)364case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:365b = strconv.AppendInt(b, v.Int(), 10)366default:367return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind())368}369370return b, nil371}372373func isNil(v reflect.Value) bool {374switch v.Kind() {375case reflect.Ptr, reflect.Interface, reflect.Map:376return v.IsNil()377default:378return false379}380}381382func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {383return options.omitempty && isEmptyValue(v)384}385386func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {387var err error388389if !ctx.inline {390b = enc.encodeComment(ctx.indent, options.comment, b)391b = enc.commented(ctx.commented, b)392b = enc.indent(ctx.indent, b)393}394395b = enc.encodeKey(b, ctx.key)396b = append(b, " = "...)397398// create a copy of the context because the value of a KV shouldn't399// modify the global context.400subctx := ctx401subctx.insideKv = true402subctx.shiftKey()403subctx.options = options404405b, err = enc.encode(b, subctx, v)406if err != nil {407return nil, err408}409410return b, nil411}412413func (enc *Encoder) commented(commented bool, b []byte) []byte {414if commented {415return append(b, "# "...)416}417return b418}419420func isEmptyValue(v reflect.Value) bool {421switch v.Kind() {422case reflect.Struct:423return isEmptyStruct(v)424case reflect.Array, reflect.Map, reflect.Slice, reflect.String:425return v.Len() == 0426case reflect.Bool:427return !v.Bool()428case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:429return v.Int() == 0430case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:431return v.Uint() == 0432case reflect.Float32, reflect.Float64:433return v.Float() == 0434case reflect.Interface, reflect.Ptr:435return v.IsNil()436}437return false438}439440func isEmptyStruct(v reflect.Value) bool {441// TODO: merge with walkStruct and cache.442typ := v.Type()443for i := 0; i < typ.NumField(); i++ {444fieldType := typ.Field(i)445446// only consider exported fields447if fieldType.PkgPath != "" {448continue449}450451tag := fieldType.Tag.Get("toml")452453// special field name to skip field454if tag == "-" {455continue456}457458f := v.Field(i)459460if !isEmptyValue(f) {461return false462}463}464465return true466}467468const literalQuote = '\''469470func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {471if needsQuoting(v) {472return enc.encodeQuotedString(options.multiline, b, v)473}474475return enc.encodeLiteralString(b, v)476}477478func needsQuoting(v string) bool {479// TODO: vectorize480for _, b := range []byte(v) {481if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) {482return true483}484}485return false486}487488// caller should have checked that the string does not contain new lines or ' .489func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {490b = append(b, literalQuote)491b = append(b, v...)492b = append(b, literalQuote)493494return b495}496497func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {498stringQuote := `"`499500if multiline {501stringQuote = `"""`502}503504b = append(b, stringQuote...)505if multiline {506b = append(b, '\n')507}508509const (510hextable = "0123456789ABCDEF"511// U+0000 to U+0008, U+000A to U+001F, U+007F512nul = 0x0513bs = 0x8514lf = 0xa515us = 0x1f516del = 0x7f517)518519for _, r := range []byte(v) {520switch r {521case '\\':522b = append(b, `\\`...)523case '"':524b = append(b, `\"`...)525case '\b':526b = append(b, `\b`...)527case '\f':528b = append(b, `\f`...)529case '\n':530if multiline {531b = append(b, r)532} else {533b = append(b, `\n`...)534}535case '\r':536b = append(b, `\r`...)537case '\t':538b = append(b, `\t`...)539default:540switch {541case r >= nul && r <= bs, r >= lf && r <= us, r == del:542b = append(b, `\u00`...)543b = append(b, hextable[r>>4])544b = append(b, hextable[r&0x0f])545default:546b = append(b, r)547}548}549}550551b = append(b, stringQuote...)552553return b554}555556// caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .557func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {558return append(b, v...)559}560561func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) {562if len(ctx.parentKey) == 0 {563return b, nil564}565566b = enc.encodeComment(ctx.indent, ctx.options.comment, b)567568b = enc.commented(ctx.commented, b)569570b = enc.indent(ctx.indent, b)571572b = append(b, '[')573574b = enc.encodeKey(b, ctx.parentKey[0])575576for _, k := range ctx.parentKey[1:] {577b = append(b, '.')578b = enc.encodeKey(b, k)579}580581b = append(b, "]\n"...)582583return b, nil584}585586//nolint:cyclop587func (enc *Encoder) encodeKey(b []byte, k string) []byte {588needsQuotation := false589cannotUseLiteral := false590591if len(k) == 0 {592return append(b, "''"...)593}594595for _, c := range k {596if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {597continue598}599600if c == literalQuote {601cannotUseLiteral = true602}603604needsQuotation = true605}606607if needsQuotation && needsQuoting(k) {608cannotUseLiteral = true609}610611switch {612case cannotUseLiteral:613return enc.encodeQuotedString(false, b, k)614case needsQuotation:615return enc.encodeLiteralString(b, k)616default:617return enc.encodeUnquotedKey(b, k)618}619}620621func (enc *Encoder) keyToString(k reflect.Value) (string, error) {622keyType := k.Type()623switch {624case keyType.Kind() == reflect.String:625return k.String(), nil626627case keyType.Implements(textMarshalerType):628keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()629if err != nil {630return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)631}632return string(keyB), nil633634case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:635return strconv.FormatInt(k.Int(), 10), nil636637case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:638return strconv.FormatUint(k.Uint(), 10), nil639640case keyType.Kind() == reflect.Float32:641return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil642643case keyType.Kind() == reflect.Float64:644return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil645}646return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())647}648649func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {650var (651t table652emptyValueOptions valueOptions653)654655iter := v.MapRange()656for iter.Next() {657v := iter.Value()658659if isNil(v) {660continue661}662663k, err := enc.keyToString(iter.Key())664if err != nil {665return nil, err666}667668if willConvertToTableOrArrayTable(ctx, v) {669t.pushTable(k, v, emptyValueOptions)670} else {671t.pushKV(k, v, emptyValueOptions)672}673}674675sortEntriesByKey(t.kvs)676sortEntriesByKey(t.tables)677678return enc.encodeTable(b, ctx, t)679}680681func sortEntriesByKey(e []entry) {682slices.SortFunc(e, func(a, b entry) int {683return strings.Compare(a.Key, b.Key)684})685}686687type entry struct {688Key string689Value reflect.Value690Options valueOptions691}692693type table struct {694kvs []entry695tables []entry696}697698func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {699for _, e := range t.kvs {700if e.Key == k {701return702}703}704705t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})706}707708func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {709for _, e := range t.tables {710if e.Key == k {711return712}713}714t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})715}716717func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {718// TODO: cache this719typ := v.Type()720for i := 0; i < typ.NumField(); i++ {721fieldType := typ.Field(i)722723// only consider exported fields724if fieldType.PkgPath != "" {725continue726}727728tag := fieldType.Tag.Get("toml")729730// special field name to skip field731if tag == "-" {732continue733}734735k, opts := parseTag(tag)736if !isValidName(k) {737k = ""738}739740f := v.Field(i)741742if k == "" {743if fieldType.Anonymous {744if fieldType.Type.Kind() == reflect.Struct {745walkStruct(ctx, t, f)746} else if fieldType.Type.Kind() == reflect.Ptr && !f.IsNil() && f.Elem().Kind() == reflect.Struct {747walkStruct(ctx, t, f.Elem())748}749continue750} else {751k = fieldType.Name752}753}754755if isNil(f) {756continue757}758759options := valueOptions{760multiline: opts.multiline,761omitempty: opts.omitempty,762commented: opts.commented,763comment: fieldType.Tag.Get("comment"),764}765766if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {767t.pushKV(k, f, options)768} else {769t.pushTable(k, f, options)770}771}772}773774func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {775var t table776777walkStruct(ctx, &t, v)778779return enc.encodeTable(b, ctx, t)780}781782func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {783for len(comment) > 0 {784var line string785idx := strings.IndexByte(comment, '\n')786if idx >= 0 {787line = comment[:idx]788comment = comment[idx+1:]789} else {790line = comment791comment = ""792}793b = enc.indent(indent, b)794b = append(b, "# "...)795b = append(b, line...)796b = append(b, '\n')797}798return b799}800801func isValidName(s string) bool {802if s == "" {803return false804}805for _, c := range s {806switch {807case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):808// Backslash and quote chars are reserved, but809// otherwise any punctuation chars are allowed810// in a tag name.811case !unicode.IsLetter(c) && !unicode.IsDigit(c):812return false813}814}815return true816}817818type tagOptions struct {819multiline bool820inline bool821omitempty bool822commented bool823}824825func parseTag(tag string) (string, tagOptions) {826opts := tagOptions{}827828idx := strings.Index(tag, ",")829if idx == -1 {830return tag, opts831}832833raw := tag[idx+1:]834tag = string(tag[:idx])835for raw != "" {836var o string837i := strings.Index(raw, ",")838if i >= 0 {839o, raw = raw[:i], raw[i+1:]840} else {841o, raw = raw, ""842}843switch o {844case "multiline":845opts.multiline = true846case "inline":847opts.inline = true848case "omitempty":849opts.omitempty = true850case "commented":851opts.commented = true852}853}854855return tag, opts856}857858func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) {859var err error860861ctx.shiftKey()862863if ctx.insideKv || (ctx.inline && !ctx.isRoot()) {864return enc.encodeTableInline(b, ctx, t)865}866867if !ctx.skipTableHeader {868b, err = enc.encodeTableHeader(ctx, b)869if err != nil {870return nil, err871}872873if enc.indentTables && len(ctx.parentKey) > 0 {874ctx.indent++875}876}877ctx.skipTableHeader = false878879hasNonEmptyKV := false880for _, kv := range t.kvs {881if shouldOmitEmpty(kv.Options, kv.Value) {882continue883}884hasNonEmptyKV = true885886ctx.setKey(kv.Key)887ctx2 := ctx888ctx2.commented = kv.Options.commented || ctx2.commented889890b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value)891if err != nil {892return nil, err893}894895b = append(b, '\n')896}897898first := true899for _, table := range t.tables {900if shouldOmitEmpty(table.Options, table.Value) {901continue902}903if first {904first = false905if hasNonEmptyKV {906b = append(b, '\n')907}908} else {909b = append(b, "\n"...)910}911912ctx.setKey(table.Key)913914ctx.options = table.Options915ctx2 := ctx916ctx2.commented = ctx2.commented || ctx.options.commented917918b, err = enc.encode(b, ctx2, table.Value)919if err != nil {920return nil, err921}922}923924return b, nil925}926927func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) {928var err error929930b = append(b, '{')931932first := true933for _, kv := range t.kvs {934if shouldOmitEmpty(kv.Options, kv.Value) {935continue936}937938if first {939first = false940} else {941b = append(b, `, `...)942}943944ctx.setKey(kv.Key)945946b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)947if err != nil {948return nil, err949}950}951952if len(t.tables) > 0 {953panic("inline table cannot contain nested tables, only key-values")954}955956b = append(b, "}"...)957958return b, nil959}960961func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {962if !v.IsValid() {963return false964}965if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {966return false967}968969t := v.Type()970switch t.Kind() {971case reflect.Map, reflect.Struct:972return !ctx.inline973case reflect.Interface:974return willConvertToTable(ctx, v.Elem())975case reflect.Ptr:976if v.IsNil() {977return false978}979980return willConvertToTable(ctx, v.Elem())981default:982return false983}984}985986func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {987if ctx.insideKv {988return false989}990t := v.Type()991992if t.Kind() == reflect.Interface {993return willConvertToTableOrArrayTable(ctx, v.Elem())994}995996if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {997if v.Len() == 0 {998// An empty slice should be a kv = [].999return false1000}10011002for i := 0; i < v.Len(); i++ {1003t := willConvertToTable(ctx, v.Index(i))10041005if !t {1006return false1007}1008}10091010return true1011}10121013return willConvertToTable(ctx, v)1014}10151016func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {1017if v.Len() == 0 {1018b = append(b, "[]"...)10191020return b, nil1021}10221023if willConvertToTableOrArrayTable(ctx, v) {1024return enc.encodeSliceAsArrayTable(b, ctx, v)1025}10261027return enc.encodeSliceAsArray(b, ctx, v)1028}10291030// caller should have checked that v is a slice that only contains values that1031// encode into tables.1032func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {1033ctx.shiftKey()10341035scratch := make([]byte, 0, 64)10361037scratch = enc.commented(ctx.commented, scratch)10381039if enc.indentTables {1040scratch = enc.indent(ctx.indent, scratch)1041}10421043scratch = append(scratch, "[["...)10441045for i, k := range ctx.parentKey {1046if i > 0 {1047scratch = append(scratch, '.')1048}10491050scratch = enc.encodeKey(scratch, k)1051}10521053scratch = append(scratch, "]]\n"...)1054ctx.skipTableHeader = true10551056b = enc.encodeComment(ctx.indent, ctx.options.comment, b)10571058if enc.indentTables {1059ctx.indent++1060}10611062for i := 0; i < v.Len(); i++ {1063if i != 0 {1064b = append(b, "\n"...)1065}10661067b = append(b, scratch...)10681069var err error1070b, err = enc.encode(b, ctx, v.Index(i))1071if err != nil {1072return nil, err1073}1074}10751076return b, nil1077}10781079func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {1080multiline := ctx.options.multiline || enc.arraysMultiline1081separator := ", "10821083b = append(b, '[')10841085subCtx := ctx1086subCtx.options = valueOptions{}10871088if multiline {1089separator = ",\n"10901091b = append(b, '\n')10921093subCtx.indent++1094}10951096var err error1097first := true10981099for i := 0; i < v.Len(); i++ {1100if first {1101first = false1102} else {1103b = append(b, separator...)1104}11051106if multiline {1107b = enc.indent(subCtx.indent, b)1108}11091110b, err = enc.encode(b, subCtx, v.Index(i))1111if err != nil {1112return nil, err1113}1114}11151116if multiline {1117b = append(b, '\n')1118b = enc.indent(ctx.indent, b)1119}11201121b = append(b, ']')11221123return b, nil1124}11251126func (enc *Encoder) indent(level int, b []byte) []byte {1127for i := 0; i < level; i++ {1128b = append(b, enc.indentSymbol...)1129}11301131return b1132}113311341135