...

Source file src/github.com/pelletier/go-toml/v2/marshaler.go

Documentation: github.com/pelletier/go-toml/v2

     1  package toml
     2  
     3  import (
     4  	"bytes"
     5  	"encoding"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"reflect"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  	"unicode"
    16  
    17  	"github.com/pelletier/go-toml/v2/internal/characters"
    18  )
    19  
    20  // Marshal serializes a Go value as a TOML document.
    21  //
    22  // It is a shortcut for Encoder.Encode() with the default options.
    23  func Marshal(v interface{}) ([]byte, error) {
    24  	var buf bytes.Buffer
    25  	enc := NewEncoder(&buf)
    26  
    27  	err := enc.Encode(v)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	return buf.Bytes(), nil
    33  }
    34  
    35  // Encoder writes a TOML document to an output stream.
    36  type Encoder struct {
    37  	// output
    38  	w io.Writer
    39  
    40  	// global settings
    41  	tablesInline       bool
    42  	arraysMultiline    bool
    43  	indentSymbol       string
    44  	indentTables       bool
    45  	marshalJsonNumbers bool
    46  }
    47  
    48  // NewEncoder returns a new Encoder that writes to w.
    49  func NewEncoder(w io.Writer) *Encoder {
    50  	return &Encoder{
    51  		w:            w,
    52  		indentSymbol: "  ",
    53  	}
    54  }
    55  
    56  // SetTablesInline forces the encoder to emit all tables inline.
    57  //
    58  // This behavior can be controlled on an individual struct field basis with the
    59  // inline tag:
    60  //
    61  //	MyField `toml:",inline"`
    62  func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
    63  	enc.tablesInline = inline
    64  	return enc
    65  }
    66  
    67  // SetArraysMultiline forces the encoder to emit all arrays with one element per
    68  // line.
    69  //
    70  // This behavior can be controlled on an individual struct field basis with the multiline tag:
    71  //
    72  //	MyField `multiline:"true"`
    73  func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder {
    74  	enc.arraysMultiline = multiline
    75  	return enc
    76  }
    77  
    78  // SetIndentSymbol defines the string that should be used for indentation. The
    79  // provided string is repeated for each indentation level. Defaults to two
    80  // spaces.
    81  func (enc *Encoder) SetIndentSymbol(s string) *Encoder {
    82  	enc.indentSymbol = s
    83  	return enc
    84  }
    85  
    86  // SetIndentTables forces the encoder to intent tables and array tables.
    87  func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
    88  	enc.indentTables = indent
    89  	return enc
    90  }
    91  
    92  // SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a
    93  // float or integer instead of relying on TextMarshaler to emit a string.
    94  //
    95  // *Unstable:* This method does not follow the compatibility guarantees of
    96  // semver. It can be changed or removed without a new major version being
    97  // issued.
    98  func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder {
    99  	enc.marshalJsonNumbers = indent
   100  	return enc
   101  }
   102  
   103  // Encode writes a TOML representation of v to the stream.
   104  //
   105  // If v cannot be represented to TOML it returns an error.
   106  //
   107  // # Encoding rules
   108  //
   109  // A top level slice containing only maps or structs is encoded as [[table
   110  // array]].
   111  //
   112  // All slices not matching rule 1 are encoded as [array]. As a result, any map
   113  // or struct they contain is encoded as an {inline table}.
   114  //
   115  // Nil interfaces and nil pointers are not supported.
   116  //
   117  // Keys in key-values always have one part.
   118  //
   119  // Intermediate tables are always printed.
   120  //
   121  // By default, strings are encoded as literal string, unless they contain either
   122  // a newline character or a single quote. In that case they are emitted as
   123  // quoted strings.
   124  //
   125  // Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so
   126  // results in an error. This rule exists because the TOML specification only
   127  // requires parsers to support at least the 64 bits integer range. Allowing
   128  // larger numbers would create non-standard TOML documents, which may not be
   129  // readable (at best) by other implementations. To encode such numbers, a
   130  // solution is a custom type that implements encoding.TextMarshaler.
   131  //
   132  // When encoding structs, fields are encoded in order of definition, with their
   133  // exact name.
   134  //
   135  // Tables and array tables are separated by empty lines. However, consecutive
   136  // subtables definitions are not. For example:
   137  //
   138  //	[top1]
   139  //
   140  //	[top2]
   141  //	[top2.child1]
   142  //
   143  //	[[array]]
   144  //
   145  //	[[array]]
   146  //	[array.child2]
   147  //
   148  // # Struct tags
   149  //
   150  // The encoding of each public struct field can be customized by the format
   151  // string in the "toml" key of the struct field's tag. This follows
   152  // encoding/json's convention. The format string starts with the name of the
   153  // field, optionally followed by a comma-separated list of options. The name may
   154  // be empty in order to provide options without overriding the default name.
   155  //
   156  // The "multiline" option emits strings as quoted multi-line TOML strings. It
   157  // has no effect on fields that would not be encoded as strings.
   158  //
   159  // The "inline" option turns fields that would be emitted as tables into inline
   160  // tables instead. It has no effect on other fields.
   161  //
   162  // The "omitempty" option prevents empty values or groups from being emitted.
   163  //
   164  // The "commented" option prefixes the value and all its children with a comment
   165  // symbol.
   166  //
   167  // In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
   168  // a TOML comment before the value being annotated. Comments are ignored inside
   169  // inline tables. For array tables, the comment is only present before the first
   170  // element of the array.
   171  func (enc *Encoder) Encode(v interface{}) error {
   172  	var (
   173  		b   []byte
   174  		ctx encoderCtx
   175  	)
   176  
   177  	ctx.inline = enc.tablesInline
   178  
   179  	if v == nil {
   180  		return fmt.Errorf("toml: cannot encode a nil interface")
   181  	}
   182  
   183  	b, err := enc.encode(b, ctx, reflect.ValueOf(v))
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	_, err = enc.w.Write(b)
   189  	if err != nil {
   190  		return fmt.Errorf("toml: cannot write: %w", err)
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  type valueOptions struct {
   197  	multiline bool
   198  	omitempty bool
   199  	commented bool
   200  	comment   string
   201  }
   202  
   203  type encoderCtx struct {
   204  	// Current top-level key.
   205  	parentKey []string
   206  
   207  	// Key that should be used for a KV.
   208  	key string
   209  	// Extra flag to account for the empty string
   210  	hasKey bool
   211  
   212  	// Set to true to indicate that the encoder is inside a KV, so that all
   213  	// tables need to be inlined.
   214  	insideKv bool
   215  
   216  	// Set to true to skip the first table header in an array table.
   217  	skipTableHeader bool
   218  
   219  	// Should the next table be encoded as inline
   220  	inline bool
   221  
   222  	// Indentation level
   223  	indent int
   224  
   225  	// Prefix the current value with a comment.
   226  	commented bool
   227  
   228  	// Options coming from struct tags
   229  	options valueOptions
   230  }
   231  
   232  func (ctx *encoderCtx) shiftKey() {
   233  	if ctx.hasKey {
   234  		ctx.parentKey = append(ctx.parentKey, ctx.key)
   235  		ctx.clearKey()
   236  	}
   237  }
   238  
   239  func (ctx *encoderCtx) setKey(k string) {
   240  	ctx.key = k
   241  	ctx.hasKey = true
   242  }
   243  
   244  func (ctx *encoderCtx) clearKey() {
   245  	ctx.key = ""
   246  	ctx.hasKey = false
   247  }
   248  
   249  func (ctx *encoderCtx) isRoot() bool {
   250  	return len(ctx.parentKey) == 0 && !ctx.hasKey
   251  }
   252  
   253  func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
   254  	i := v.Interface()
   255  
   256  	switch x := i.(type) {
   257  	case time.Time:
   258  		if x.Nanosecond() > 0 {
   259  			return x.AppendFormat(b, time.RFC3339Nano), nil
   260  		}
   261  		return x.AppendFormat(b, time.RFC3339), nil
   262  	case LocalTime:
   263  		return append(b, x.String()...), nil
   264  	case LocalDate:
   265  		return append(b, x.String()...), nil
   266  	case LocalDateTime:
   267  		return append(b, x.String()...), nil
   268  	case json.Number:
   269  		if enc.marshalJsonNumbers {
   270  			if x == "" { /// Useful zero value.
   271  				return append(b, "0"...), nil
   272  			} else if v, err := x.Int64(); err == nil {
   273  				return enc.encode(b, ctx, reflect.ValueOf(v))
   274  			} else if f, err := x.Float64(); err == nil {
   275  				return enc.encode(b, ctx, reflect.ValueOf(f))
   276  			} else {
   277  				return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x)
   278  			}
   279  		}
   280  	}
   281  
   282  	hasTextMarshaler := v.Type().Implements(textMarshalerType)
   283  	if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
   284  		if !hasTextMarshaler {
   285  			v = v.Addr()
   286  		}
   287  
   288  		if ctx.isRoot() {
   289  			return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type())
   290  		}
   291  
   292  		text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
   293  		if err != nil {
   294  			return nil, err
   295  		}
   296  
   297  		b = enc.encodeString(b, string(text), ctx.options)
   298  
   299  		return b, nil
   300  	}
   301  
   302  	switch v.Kind() {
   303  	// containers
   304  	case reflect.Map:
   305  		return enc.encodeMap(b, ctx, v)
   306  	case reflect.Struct:
   307  		return enc.encodeStruct(b, ctx, v)
   308  	case reflect.Slice, reflect.Array:
   309  		return enc.encodeSlice(b, ctx, v)
   310  	case reflect.Interface:
   311  		if v.IsNil() {
   312  			return nil, fmt.Errorf("toml: encoding a nil interface is not supported")
   313  		}
   314  
   315  		return enc.encode(b, ctx, v.Elem())
   316  	case reflect.Ptr:
   317  		if v.IsNil() {
   318  			return enc.encode(b, ctx, reflect.Zero(v.Type().Elem()))
   319  		}
   320  
   321  		return enc.encode(b, ctx, v.Elem())
   322  
   323  	// values
   324  	case reflect.String:
   325  		b = enc.encodeString(b, v.String(), ctx.options)
   326  	case reflect.Float32:
   327  		f := v.Float()
   328  
   329  		if math.IsNaN(f) {
   330  			b = append(b, "nan"...)
   331  		} else if f > math.MaxFloat32 {
   332  			b = append(b, "inf"...)
   333  		} else if f < -math.MaxFloat32 {
   334  			b = append(b, "-inf"...)
   335  		} else if math.Trunc(f) == f {
   336  			b = strconv.AppendFloat(b, f, 'f', 1, 32)
   337  		} else {
   338  			b = strconv.AppendFloat(b, f, 'f', -1, 32)
   339  		}
   340  	case reflect.Float64:
   341  		f := v.Float()
   342  		if math.IsNaN(f) {
   343  			b = append(b, "nan"...)
   344  		} else if f > math.MaxFloat64 {
   345  			b = append(b, "inf"...)
   346  		} else if f < -math.MaxFloat64 {
   347  			b = append(b, "-inf"...)
   348  		} else if math.Trunc(f) == f {
   349  			b = strconv.AppendFloat(b, f, 'f', 1, 64)
   350  		} else {
   351  			b = strconv.AppendFloat(b, f, 'f', -1, 64)
   352  		}
   353  	case reflect.Bool:
   354  		if v.Bool() {
   355  			b = append(b, "true"...)
   356  		} else {
   357  			b = append(b, "false"...)
   358  		}
   359  	case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
   360  		x := v.Uint()
   361  		if x > uint64(math.MaxInt64) {
   362  			return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))
   363  		}
   364  		b = strconv.AppendUint(b, x, 10)
   365  	case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
   366  		b = strconv.AppendInt(b, v.Int(), 10)
   367  	default:
   368  		return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind())
   369  	}
   370  
   371  	return b, nil
   372  }
   373  
   374  func isNil(v reflect.Value) bool {
   375  	switch v.Kind() {
   376  	case reflect.Ptr, reflect.Interface, reflect.Map:
   377  		return v.IsNil()
   378  	default:
   379  		return false
   380  	}
   381  }
   382  
   383  func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
   384  	return options.omitempty && isEmptyValue(v)
   385  }
   386  
   387  func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
   388  	var err error
   389  
   390  	if !ctx.inline {
   391  		b = enc.encodeComment(ctx.indent, options.comment, b)
   392  		b = enc.commented(ctx.commented, b)
   393  		b = enc.indent(ctx.indent, b)
   394  	}
   395  
   396  	b = enc.encodeKey(b, ctx.key)
   397  	b = append(b, " = "...)
   398  
   399  	// create a copy of the context because the value of a KV shouldn't
   400  	// modify the global context.
   401  	subctx := ctx
   402  	subctx.insideKv = true
   403  	subctx.shiftKey()
   404  	subctx.options = options
   405  
   406  	b, err = enc.encode(b, subctx, v)
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  
   411  	return b, nil
   412  }
   413  
   414  func (enc *Encoder) commented(commented bool, b []byte) []byte {
   415  	if commented {
   416  		return append(b, "# "...)
   417  	}
   418  	return b
   419  }
   420  
   421  func isEmptyValue(v reflect.Value) bool {
   422  	switch v.Kind() {
   423  	case reflect.Struct:
   424  		return isEmptyStruct(v)
   425  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   426  		return v.Len() == 0
   427  	case reflect.Bool:
   428  		return !v.Bool()
   429  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   430  		return v.Int() == 0
   431  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   432  		return v.Uint() == 0
   433  	case reflect.Float32, reflect.Float64:
   434  		return v.Float() == 0
   435  	case reflect.Interface, reflect.Ptr:
   436  		return v.IsNil()
   437  	}
   438  	return false
   439  }
   440  
   441  func isEmptyStruct(v reflect.Value) bool {
   442  	// TODO: merge with walkStruct and cache.
   443  	typ := v.Type()
   444  	for i := 0; i < typ.NumField(); i++ {
   445  		fieldType := typ.Field(i)
   446  
   447  		// only consider exported fields
   448  		if fieldType.PkgPath != "" {
   449  			continue
   450  		}
   451  
   452  		tag := fieldType.Tag.Get("toml")
   453  
   454  		// special field name to skip field
   455  		if tag == "-" {
   456  			continue
   457  		}
   458  
   459  		f := v.Field(i)
   460  
   461  		if !isEmptyValue(f) {
   462  			return false
   463  		}
   464  	}
   465  
   466  	return true
   467  }
   468  
   469  const literalQuote = '\''
   470  
   471  func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
   472  	if needsQuoting(v) {
   473  		return enc.encodeQuotedString(options.multiline, b, v)
   474  	}
   475  
   476  	return enc.encodeLiteralString(b, v)
   477  }
   478  
   479  func needsQuoting(v string) bool {
   480  	// TODO: vectorize
   481  	for _, b := range []byte(v) {
   482  		if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) {
   483  			return true
   484  		}
   485  	}
   486  	return false
   487  }
   488  
   489  // caller should have checked that the string does not contain new lines or ' .
   490  func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
   491  	b = append(b, literalQuote)
   492  	b = append(b, v...)
   493  	b = append(b, literalQuote)
   494  
   495  	return b
   496  }
   497  
   498  func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
   499  	stringQuote := `"`
   500  
   501  	if multiline {
   502  		stringQuote = `"""`
   503  	}
   504  
   505  	b = append(b, stringQuote...)
   506  	if multiline {
   507  		b = append(b, '\n')
   508  	}
   509  
   510  	const (
   511  		hextable = "0123456789ABCDEF"
   512  		// U+0000 to U+0008, U+000A to U+001F, U+007F
   513  		nul = 0x0
   514  		bs  = 0x8
   515  		lf  = 0xa
   516  		us  = 0x1f
   517  		del = 0x7f
   518  	)
   519  
   520  	for _, r := range []byte(v) {
   521  		switch r {
   522  		case '\\':
   523  			b = append(b, `\\`...)
   524  		case '"':
   525  			b = append(b, `\"`...)
   526  		case '\b':
   527  			b = append(b, `\b`...)
   528  		case '\f':
   529  			b = append(b, `\f`...)
   530  		case '\n':
   531  			if multiline {
   532  				b = append(b, r)
   533  			} else {
   534  				b = append(b, `\n`...)
   535  			}
   536  		case '\r':
   537  			b = append(b, `\r`...)
   538  		case '\t':
   539  			b = append(b, `\t`...)
   540  		default:
   541  			switch {
   542  			case r >= nul && r <= bs, r >= lf && r <= us, r == del:
   543  				b = append(b, `\u00`...)
   544  				b = append(b, hextable[r>>4])
   545  				b = append(b, hextable[r&0x0f])
   546  			default:
   547  				b = append(b, r)
   548  			}
   549  		}
   550  	}
   551  
   552  	b = append(b, stringQuote...)
   553  
   554  	return b
   555  }
   556  
   557  // caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
   558  func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
   559  	return append(b, v...)
   560  }
   561  
   562  func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) {
   563  	if len(ctx.parentKey) == 0 {
   564  		return b, nil
   565  	}
   566  
   567  	b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
   568  
   569  	b = enc.commented(ctx.commented, b)
   570  
   571  	b = enc.indent(ctx.indent, b)
   572  
   573  	b = append(b, '[')
   574  
   575  	b = enc.encodeKey(b, ctx.parentKey[0])
   576  
   577  	for _, k := range ctx.parentKey[1:] {
   578  		b = append(b, '.')
   579  		b = enc.encodeKey(b, k)
   580  	}
   581  
   582  	b = append(b, "]\n"...)
   583  
   584  	return b, nil
   585  }
   586  
   587  //nolint:cyclop
   588  func (enc *Encoder) encodeKey(b []byte, k string) []byte {
   589  	needsQuotation := false
   590  	cannotUseLiteral := false
   591  
   592  	if len(k) == 0 {
   593  		return append(b, "''"...)
   594  	}
   595  
   596  	for _, c := range k {
   597  		if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
   598  			continue
   599  		}
   600  
   601  		if c == literalQuote {
   602  			cannotUseLiteral = true
   603  		}
   604  
   605  		needsQuotation = true
   606  	}
   607  
   608  	if needsQuotation && needsQuoting(k) {
   609  		cannotUseLiteral = true
   610  	}
   611  
   612  	switch {
   613  	case cannotUseLiteral:
   614  		return enc.encodeQuotedString(false, b, k)
   615  	case needsQuotation:
   616  		return enc.encodeLiteralString(b, k)
   617  	default:
   618  		return enc.encodeUnquotedKey(b, k)
   619  	}
   620  }
   621  
   622  func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
   623  	keyType := k.Type()
   624  	switch {
   625  	case keyType.Kind() == reflect.String:
   626  		return k.String(), nil
   627  
   628  	case keyType.Implements(textMarshalerType):
   629  		keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()
   630  		if err != nil {
   631  			return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
   632  		}
   633  		return string(keyB), nil
   634  	}
   635  	return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
   636  }
   637  
   638  func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
   639  	var (
   640  		t                 table
   641  		emptyValueOptions valueOptions
   642  	)
   643  
   644  	iter := v.MapRange()
   645  	for iter.Next() {
   646  		v := iter.Value()
   647  
   648  		if isNil(v) {
   649  			continue
   650  		}
   651  
   652  		k, err := enc.keyToString(iter.Key())
   653  		if err != nil {
   654  			return nil, err
   655  		}
   656  
   657  		if willConvertToTableOrArrayTable(ctx, v) {
   658  			t.pushTable(k, v, emptyValueOptions)
   659  		} else {
   660  			t.pushKV(k, v, emptyValueOptions)
   661  		}
   662  	}
   663  
   664  	sortEntriesByKey(t.kvs)
   665  	sortEntriesByKey(t.tables)
   666  
   667  	return enc.encodeTable(b, ctx, t)
   668  }
   669  
   670  func sortEntriesByKey(e []entry) {
   671  	sort.Slice(e, func(i, j int) bool {
   672  		return e[i].Key < e[j].Key
   673  	})
   674  }
   675  
   676  type entry struct {
   677  	Key     string
   678  	Value   reflect.Value
   679  	Options valueOptions
   680  }
   681  
   682  type table struct {
   683  	kvs    []entry
   684  	tables []entry
   685  }
   686  
   687  func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
   688  	for _, e := range t.kvs {
   689  		if e.Key == k {
   690  			return
   691  		}
   692  	}
   693  
   694  	t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
   695  }
   696  
   697  func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
   698  	for _, e := range t.tables {
   699  		if e.Key == k {
   700  			return
   701  		}
   702  	}
   703  	t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
   704  }
   705  
   706  func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
   707  	// TODO: cache this
   708  	typ := v.Type()
   709  	for i := 0; i < typ.NumField(); i++ {
   710  		fieldType := typ.Field(i)
   711  
   712  		// only consider exported fields
   713  		if fieldType.PkgPath != "" {
   714  			continue
   715  		}
   716  
   717  		tag := fieldType.Tag.Get("toml")
   718  
   719  		// special field name to skip field
   720  		if tag == "-" {
   721  			continue
   722  		}
   723  
   724  		k, opts := parseTag(tag)
   725  		if !isValidName(k) {
   726  			k = ""
   727  		}
   728  
   729  		f := v.Field(i)
   730  
   731  		if k == "" {
   732  			if fieldType.Anonymous {
   733  				if fieldType.Type.Kind() == reflect.Struct {
   734  					walkStruct(ctx, t, f)
   735  				} else if fieldType.Type.Kind() == reflect.Pointer && !f.IsNil() && f.Elem().Kind() == reflect.Struct {
   736  					walkStruct(ctx, t, f.Elem())
   737  				}
   738  				continue
   739  			} else {
   740  				k = fieldType.Name
   741  			}
   742  		}
   743  
   744  		if isNil(f) {
   745  			continue
   746  		}
   747  
   748  		options := valueOptions{
   749  			multiline: opts.multiline,
   750  			omitempty: opts.omitempty,
   751  			commented: opts.commented,
   752  			comment:   fieldType.Tag.Get("comment"),
   753  		}
   754  
   755  		if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {
   756  			t.pushKV(k, f, options)
   757  		} else {
   758  			t.pushTable(k, f, options)
   759  		}
   760  	}
   761  }
   762  
   763  func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
   764  	var t table
   765  
   766  	walkStruct(ctx, &t, v)
   767  
   768  	return enc.encodeTable(b, ctx, t)
   769  }
   770  
   771  func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
   772  	for len(comment) > 0 {
   773  		var line string
   774  		idx := strings.IndexByte(comment, '\n')
   775  		if idx >= 0 {
   776  			line = comment[:idx]
   777  			comment = comment[idx+1:]
   778  		} else {
   779  			line = comment
   780  			comment = ""
   781  		}
   782  		b = enc.indent(indent, b)
   783  		b = append(b, "# "...)
   784  		b = append(b, line...)
   785  		b = append(b, '\n')
   786  	}
   787  	return b
   788  }
   789  
   790  func isValidName(s string) bool {
   791  	if s == "" {
   792  		return false
   793  	}
   794  	for _, c := range s {
   795  		switch {
   796  		case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
   797  			// Backslash and quote chars are reserved, but
   798  			// otherwise any punctuation chars are allowed
   799  			// in a tag name.
   800  		case !unicode.IsLetter(c) && !unicode.IsDigit(c):
   801  			return false
   802  		}
   803  	}
   804  	return true
   805  }
   806  
   807  type tagOptions struct {
   808  	multiline bool
   809  	inline    bool
   810  	omitempty bool
   811  	commented bool
   812  }
   813  
   814  func parseTag(tag string) (string, tagOptions) {
   815  	opts := tagOptions{}
   816  
   817  	idx := strings.Index(tag, ",")
   818  	if idx == -1 {
   819  		return tag, opts
   820  	}
   821  
   822  	raw := tag[idx+1:]
   823  	tag = string(tag[:idx])
   824  	for raw != "" {
   825  		var o string
   826  		i := strings.Index(raw, ",")
   827  		if i >= 0 {
   828  			o, raw = raw[:i], raw[i+1:]
   829  		} else {
   830  			o, raw = raw, ""
   831  		}
   832  		switch o {
   833  		case "multiline":
   834  			opts.multiline = true
   835  		case "inline":
   836  			opts.inline = true
   837  		case "omitempty":
   838  			opts.omitempty = true
   839  		case "commented":
   840  			opts.commented = true
   841  		}
   842  	}
   843  
   844  	return tag, opts
   845  }
   846  
   847  func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) {
   848  	var err error
   849  
   850  	ctx.shiftKey()
   851  
   852  	if ctx.insideKv || (ctx.inline && !ctx.isRoot()) {
   853  		return enc.encodeTableInline(b, ctx, t)
   854  	}
   855  
   856  	if !ctx.skipTableHeader {
   857  		b, err = enc.encodeTableHeader(ctx, b)
   858  		if err != nil {
   859  			return nil, err
   860  		}
   861  
   862  		if enc.indentTables && len(ctx.parentKey) > 0 {
   863  			ctx.indent++
   864  		}
   865  	}
   866  	ctx.skipTableHeader = false
   867  
   868  	hasNonEmptyKV := false
   869  	for _, kv := range t.kvs {
   870  		if shouldOmitEmpty(kv.Options, kv.Value) {
   871  			continue
   872  		}
   873  		hasNonEmptyKV = true
   874  
   875  		ctx.setKey(kv.Key)
   876  		ctx2 := ctx
   877  		ctx2.commented = kv.Options.commented || ctx2.commented
   878  
   879  		b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value)
   880  		if err != nil {
   881  			return nil, err
   882  		}
   883  
   884  		b = append(b, '\n')
   885  	}
   886  
   887  	first := true
   888  	for _, table := range t.tables {
   889  		if shouldOmitEmpty(table.Options, table.Value) {
   890  			continue
   891  		}
   892  		if first {
   893  			first = false
   894  			if hasNonEmptyKV {
   895  				b = append(b, '\n')
   896  			}
   897  		} else {
   898  			b = append(b, "\n"...)
   899  		}
   900  
   901  		ctx.setKey(table.Key)
   902  
   903  		ctx.options = table.Options
   904  		ctx2 := ctx
   905  		ctx2.commented = ctx2.commented || ctx.options.commented
   906  
   907  		b, err = enc.encode(b, ctx2, table.Value)
   908  		if err != nil {
   909  			return nil, err
   910  		}
   911  	}
   912  
   913  	return b, nil
   914  }
   915  
   916  func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) {
   917  	var err error
   918  
   919  	b = append(b, '{')
   920  
   921  	first := true
   922  	for _, kv := range t.kvs {
   923  		if shouldOmitEmpty(kv.Options, kv.Value) {
   924  			continue
   925  		}
   926  
   927  		if first {
   928  			first = false
   929  		} else {
   930  			b = append(b, `, `...)
   931  		}
   932  
   933  		ctx.setKey(kv.Key)
   934  
   935  		b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
   936  		if err != nil {
   937  			return nil, err
   938  		}
   939  	}
   940  
   941  	if len(t.tables) > 0 {
   942  		panic("inline table cannot contain nested tables, only key-values")
   943  	}
   944  
   945  	b = append(b, "}"...)
   946  
   947  	return b, nil
   948  }
   949  
   950  func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
   951  	if !v.IsValid() {
   952  		return false
   953  	}
   954  	if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
   955  		return false
   956  	}
   957  
   958  	t := v.Type()
   959  	switch t.Kind() {
   960  	case reflect.Map, reflect.Struct:
   961  		return !ctx.inline
   962  	case reflect.Interface:
   963  		return willConvertToTable(ctx, v.Elem())
   964  	case reflect.Ptr:
   965  		if v.IsNil() {
   966  			return false
   967  		}
   968  
   969  		return willConvertToTable(ctx, v.Elem())
   970  	default:
   971  		return false
   972  	}
   973  }
   974  
   975  func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
   976  	if ctx.insideKv {
   977  		return false
   978  	}
   979  	t := v.Type()
   980  
   981  	if t.Kind() == reflect.Interface {
   982  		return willConvertToTableOrArrayTable(ctx, v.Elem())
   983  	}
   984  
   985  	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
   986  		if v.Len() == 0 {
   987  			// An empty slice should be a kv = [].
   988  			return false
   989  		}
   990  
   991  		for i := 0; i < v.Len(); i++ {
   992  			t := willConvertToTable(ctx, v.Index(i))
   993  
   994  			if !t {
   995  				return false
   996  			}
   997  		}
   998  
   999  		return true
  1000  	}
  1001  
  1002  	return willConvertToTable(ctx, v)
  1003  }
  1004  
  1005  func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
  1006  	if v.Len() == 0 {
  1007  		b = append(b, "[]"...)
  1008  
  1009  		return b, nil
  1010  	}
  1011  
  1012  	if willConvertToTableOrArrayTable(ctx, v) {
  1013  		return enc.encodeSliceAsArrayTable(b, ctx, v)
  1014  	}
  1015  
  1016  	return enc.encodeSliceAsArray(b, ctx, v)
  1017  }
  1018  
  1019  // caller should have checked that v is a slice that only contains values that
  1020  // encode into tables.
  1021  func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
  1022  	ctx.shiftKey()
  1023  
  1024  	scratch := make([]byte, 0, 64)
  1025  
  1026  	scratch = enc.commented(ctx.commented, scratch)
  1027  
  1028  	if enc.indentTables {
  1029  		scratch = enc.indent(ctx.indent, scratch)
  1030  	}
  1031  
  1032  	scratch = append(scratch, "[["...)
  1033  
  1034  	for i, k := range ctx.parentKey {
  1035  		if i > 0 {
  1036  			scratch = append(scratch, '.')
  1037  		}
  1038  
  1039  		scratch = enc.encodeKey(scratch, k)
  1040  	}
  1041  
  1042  	scratch = append(scratch, "]]\n"...)
  1043  	ctx.skipTableHeader = true
  1044  
  1045  	b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
  1046  
  1047  	if enc.indentTables {
  1048  		ctx.indent++
  1049  	}
  1050  
  1051  	for i := 0; i < v.Len(); i++ {
  1052  		if i != 0 {
  1053  			b = append(b, "\n"...)
  1054  		}
  1055  
  1056  		b = append(b, scratch...)
  1057  
  1058  		var err error
  1059  		b, err = enc.encode(b, ctx, v.Index(i))
  1060  		if err != nil {
  1061  			return nil, err
  1062  		}
  1063  	}
  1064  
  1065  	return b, nil
  1066  }
  1067  
  1068  func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
  1069  	multiline := ctx.options.multiline || enc.arraysMultiline
  1070  	separator := ", "
  1071  
  1072  	b = append(b, '[')
  1073  
  1074  	subCtx := ctx
  1075  	subCtx.options = valueOptions{}
  1076  
  1077  	if multiline {
  1078  		separator = ",\n"
  1079  
  1080  		b = append(b, '\n')
  1081  
  1082  		subCtx.indent++
  1083  	}
  1084  
  1085  	var err error
  1086  	first := true
  1087  
  1088  	for i := 0; i < v.Len(); i++ {
  1089  		if first {
  1090  			first = false
  1091  		} else {
  1092  			b = append(b, separator...)
  1093  		}
  1094  
  1095  		if multiline {
  1096  			b = enc.indent(subCtx.indent, b)
  1097  		}
  1098  
  1099  		b, err = enc.encode(b, subCtx, v.Index(i))
  1100  		if err != nil {
  1101  			return nil, err
  1102  		}
  1103  	}
  1104  
  1105  	if multiline {
  1106  		b = append(b, '\n')
  1107  		b = enc.indent(ctx.indent, b)
  1108  	}
  1109  
  1110  	b = append(b, ']')
  1111  
  1112  	return b, nil
  1113  }
  1114  
  1115  func (enc *Encoder) indent(level int, b []byte) []byte {
  1116  	for i := 0; i < level; i++ {
  1117  		b = append(b, enc.indentSymbol...)
  1118  	}
  1119  
  1120  	return b
  1121  }
  1122  

View as plain text