...

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

View as plain text