123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973 |
- 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 `inline:"true"`
- 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.
- //
- // 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 (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
- var err error
- if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
- return b, nil
- }
- 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.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
- }
- 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
- }
- //nolint:cyclop
- 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
- for _, kv := range t.kvs {
- ctx.setKey(kv.Key)
- b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
- if err != nil {
- return nil, err
- }
- b = append(b, '\n')
- }
- for _, table := range t.tables {
- ctx.setKey(table.Key)
- ctx.options = table.Options
- b, err = enc.encode(b, ctx, table.Value)
- if err != nil {
- return nil, err
- }
- b = append(b, '\n')
- }
- 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 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, online 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++ {
- 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
- }
|