1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090 |
- package toml
- import (
- "bytes"
- "encoding"
- "fmt"
- "io"
- "math"
- "reflect"
- "sort"
- "strconv"
- "strings"
- "time"
- "unicode"
- "github.com/pelletier/go-toml/v2/internal/characters"
- )
- // 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.
- //
- // The "commented" option prefixes the value and all its children with a comment
- // symbol.
- //
- // 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
- commented 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
- // Prefix the current value with a comment.
- commented bool
- // 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, reflect.Array:
- 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.commented(ctx.commented, 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 (enc *Encoder) commented(commented bool, b []byte) []byte {
- if commented {
- return append(b, "# "...)
- }
- return b
- }
- 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' || characters.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.commented(ctx.commented, 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) keyToString(k reflect.Value) (string, error) {
- keyType := k.Type()
- switch {
- case keyType.Kind() == reflect.String:
- return k.String(), nil
- case keyType.Implements(textMarshalerType):
- keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()
- if err != nil {
- return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
- }
- return string(keyB), nil
- }
- return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
- }
- func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
- var (
- t table
- emptyValueOptions valueOptions
- )
- iter := v.MapRange()
- for iter.Next() {
- v := iter.Value()
- if isNil(v) {
- continue
- }
- k, err := enc.keyToString(iter.Key())
- if err != nil {
- return nil, err
- }
- 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,
- commented: opts.commented,
- 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
- commented 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
- case "commented":
- opts.commented = 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)
- ctx2 := ctx
- ctx2.commented = kv.Options.commented || ctx2.commented
- b, err = enc.encodeKv(b, ctx2, 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
- ctx2 := ctx
- ctx2.commented = ctx2.commented || ctx.options.commented
- b, err = enc.encode(b, ctx2, 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 || t.Kind() == reflect.Array {
- 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 = enc.commented(ctx.commented, scratch)
- 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)
- if enc.indentTables {
- ctx.indent++
- }
- 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
- }
|