gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/BurntSushi/toml/encode.go (about)

     1  package toml
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"reflect"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  type tomlEncodeError struct{ error }
    16  
    17  var (
    18  	errArrayMixedElementTypes = errors.New(
    19  		"toml: cannot encode array with mixed element types")
    20  	errArrayNilElement = errors.New(
    21  		"toml: cannot encode array with nil element")
    22  	errNonString = errors.New(
    23  		"toml: cannot encode a map with non-string key type")
    24  	errAnonNonStruct = errors.New(
    25  		"toml: cannot encode an anonymous field that is not a struct")
    26  	errArrayNoTable = errors.New(
    27  		"toml: TOML array element cannot contain a table")
    28  	errNoKey = errors.New(
    29  		"toml: top-level values must be Go maps or structs")
    30  	errAnything = errors.New("") // used in testing
    31  )
    32  
    33  var quotedReplacer = strings.NewReplacer(
    34  	"\t", "\\t",
    35  	"\n", "\\n",
    36  	"\r", "\\r",
    37  	"\"", "\\\"",
    38  	"\\", "\\\\",
    39  )
    40  
    41  // Encoder controls the encoding of Go values to a TOML document to some
    42  // io.Writer.
    43  //
    44  // The indentation level can be controlled with the Indent field.
    45  type Encoder struct {
    46  	// A single indentation level. By default it is two spaces.
    47  	Indent string
    48  
    49  	// hasWritten is whether we have written any output to w yet.
    50  	hasWritten bool
    51  	w          *bufio.Writer
    52  }
    53  
    54  // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
    55  // given. By default, a single indentation level is 2 spaces.
    56  func NewEncoder(w io.Writer) *Encoder {
    57  	return &Encoder{
    58  		w:      bufio.NewWriter(w),
    59  		Indent: "  ",
    60  	}
    61  }
    62  
    63  // Encode writes a TOML representation of the Go value to the underlying
    64  // io.Writer. If the value given cannot be encoded to a valid TOML document,
    65  // then an error is returned.
    66  //
    67  // The mapping between Go values and TOML values should be precisely the same
    68  // as for the Decode* functions. Similarly, the TextMarshaler interface is
    69  // supported by encoding the resulting bytes as strings. (If you want to write
    70  // arbitrary binary data then you will need to use something like base64 since
    71  // TOML does not have any binary types.)
    72  //
    73  // When encoding TOML hashes (i.e., Go maps or structs), keys without any
    74  // sub-hashes are encoded first.
    75  //
    76  // If a Go map is encoded, then its keys are sorted alphabetically for
    77  // deterministic output. More control over this behavior may be provided if
    78  // there is demand for it.
    79  //
    80  // Encoding Go values without a corresponding TOML representation---like map
    81  // types with non-string keys---will cause an error to be returned. Similarly
    82  // for mixed arrays/slices, arrays/slices with nil elements, embedded
    83  // non-struct types and nested slices containing maps or structs.
    84  // (e.g., [][]map[string]string is not allowed but []map[string]string is OK
    85  // and so is []map[string][]string.)
    86  func (enc *Encoder) Encode(v interface{}) error {
    87  	rv := eindirect(reflect.ValueOf(v))
    88  	if err := enc.safeEncode(Key([]string{}), rv); err != nil {
    89  		return err
    90  	}
    91  	return enc.w.Flush()
    92  }
    93  
    94  func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
    95  	defer func() {
    96  		if r := recover(); r != nil {
    97  			if terr, ok := r.(tomlEncodeError); ok {
    98  				err = terr.error
    99  				return
   100  			}
   101  			panic(r)
   102  		}
   103  	}()
   104  	enc.encode(key, rv)
   105  	return nil
   106  }
   107  
   108  func (enc *Encoder) encode(key Key, rv reflect.Value) {
   109  	// Special case. Time needs to be in ISO8601 format.
   110  	// Special case. If we can marshal the type to text, then we used that.
   111  	// Basically, this prevents the encoder for handling these types as
   112  	// generic structs (or whatever the underlying type of a TextMarshaler is).
   113  	switch rv.Interface().(type) {
   114  	case time.Time, TextMarshaler:
   115  		enc.keyEqElement(key, rv)
   116  		return
   117  	}
   118  
   119  	k := rv.Kind()
   120  	switch k {
   121  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
   122  		reflect.Int64,
   123  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
   124  		reflect.Uint64,
   125  		reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
   126  		enc.keyEqElement(key, rv)
   127  	case reflect.Array, reflect.Slice:
   128  		if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
   129  			enc.eArrayOfTables(key, rv)
   130  		} else {
   131  			enc.keyEqElement(key, rv)
   132  		}
   133  	case reflect.Interface:
   134  		if rv.IsNil() {
   135  			return
   136  		}
   137  		enc.encode(key, rv.Elem())
   138  	case reflect.Map:
   139  		if rv.IsNil() {
   140  			return
   141  		}
   142  		enc.eTable(key, rv)
   143  	case reflect.Ptr:
   144  		if rv.IsNil() {
   145  			return
   146  		}
   147  		enc.encode(key, rv.Elem())
   148  	case reflect.Struct:
   149  		enc.eTable(key, rv)
   150  	default:
   151  		panic(e("unsupported type for key '%s': %s", key, k))
   152  	}
   153  }
   154  
   155  // eElement encodes any value that can be an array element (primitives and
   156  // arrays).
   157  func (enc *Encoder) eElement(rv reflect.Value) {
   158  	switch v := rv.Interface().(type) {
   159  	case time.Time:
   160  		// Special case time.Time as a primitive. Has to come before
   161  		// TextMarshaler below because time.Time implements
   162  		// encoding.TextMarshaler, but we need to always use UTC.
   163  		enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
   164  		return
   165  	case TextMarshaler:
   166  		// Special case. Use text marshaler if it's available for this value.
   167  		if s, err := v.MarshalText(); err != nil {
   168  			encPanic(err)
   169  		} else {
   170  			enc.writeQuoted(string(s))
   171  		}
   172  		return
   173  	}
   174  	switch rv.Kind() {
   175  	case reflect.Bool:
   176  		enc.wf(strconv.FormatBool(rv.Bool()))
   177  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
   178  		reflect.Int64:
   179  		enc.wf(strconv.FormatInt(rv.Int(), 10))
   180  	case reflect.Uint, reflect.Uint8, reflect.Uint16,
   181  		reflect.Uint32, reflect.Uint64:
   182  		enc.wf(strconv.FormatUint(rv.Uint(), 10))
   183  	case reflect.Float32:
   184  		enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
   185  	case reflect.Float64:
   186  		enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
   187  	case reflect.Array, reflect.Slice:
   188  		enc.eArrayOrSliceElement(rv)
   189  	case reflect.Interface:
   190  		enc.eElement(rv.Elem())
   191  	case reflect.String:
   192  		enc.writeQuoted(rv.String())
   193  	default:
   194  		panic(e("unexpected primitive type: %s", rv.Kind()))
   195  	}
   196  }
   197  
   198  // By the TOML spec, all floats must have a decimal with at least one
   199  // number on either side.
   200  func floatAddDecimal(fstr string) string {
   201  	if !strings.Contains(fstr, ".") {
   202  		return fstr + ".0"
   203  	}
   204  	return fstr
   205  }
   206  
   207  func (enc *Encoder) writeQuoted(s string) {
   208  	enc.wf("\"%s\"", quotedReplacer.Replace(s))
   209  }
   210  
   211  func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
   212  	length := rv.Len()
   213  	enc.wf("[")
   214  	for i := 0; i < length; i++ {
   215  		elem := rv.Index(i)
   216  		enc.eElement(elem)
   217  		if i != length-1 {
   218  			enc.wf(", ")
   219  		}
   220  	}
   221  	enc.wf("]")
   222  }
   223  
   224  func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
   225  	if len(key) == 0 {
   226  		encPanic(errNoKey)
   227  	}
   228  	for i := 0; i < rv.Len(); i++ {
   229  		trv := rv.Index(i)
   230  		if isNil(trv) {
   231  			continue
   232  		}
   233  		panicIfInvalidKey(key)
   234  		enc.newline()
   235  		enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
   236  		enc.newline()
   237  		enc.eMapOrStruct(key, trv)
   238  	}
   239  }
   240  
   241  func (enc *Encoder) eTable(key Key, rv reflect.Value) {
   242  	panicIfInvalidKey(key)
   243  	if len(key) == 1 {
   244  		// Output an extra newline between top-level tables.
   245  		// (The newline isn't written if nothing else has been written though.)
   246  		enc.newline()
   247  	}
   248  	if len(key) > 0 {
   249  		enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
   250  		enc.newline()
   251  	}
   252  	enc.eMapOrStruct(key, rv)
   253  }
   254  
   255  func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
   256  	switch rv := eindirect(rv); rv.Kind() {
   257  	case reflect.Map:
   258  		enc.eMap(key, rv)
   259  	case reflect.Struct:
   260  		enc.eStruct(key, rv)
   261  	default:
   262  		panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
   263  	}
   264  }
   265  
   266  func (enc *Encoder) eMap(key Key, rv reflect.Value) {
   267  	rt := rv.Type()
   268  	if rt.Key().Kind() != reflect.String {
   269  		encPanic(errNonString)
   270  	}
   271  
   272  	// Sort keys so that we have deterministic output. And write keys directly
   273  	// underneath this key first, before writing sub-structs or sub-maps.
   274  	var mapKeysDirect, mapKeysSub []string
   275  	for _, mapKey := range rv.MapKeys() {
   276  		k := mapKey.String()
   277  		if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
   278  			mapKeysSub = append(mapKeysSub, k)
   279  		} else {
   280  			mapKeysDirect = append(mapKeysDirect, k)
   281  		}
   282  	}
   283  
   284  	var writeMapKeys = func(mapKeys []string) {
   285  		sort.Strings(mapKeys)
   286  		for _, mapKey := range mapKeys {
   287  			mrv := rv.MapIndex(reflect.ValueOf(mapKey))
   288  			if isNil(mrv) {
   289  				// Don't write anything for nil fields.
   290  				continue
   291  			}
   292  			enc.encode(key.add(mapKey), mrv)
   293  		}
   294  	}
   295  	writeMapKeys(mapKeysDirect)
   296  	writeMapKeys(mapKeysSub)
   297  }
   298  
   299  func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
   300  	// Write keys for fields directly under this key first, because if we write
   301  	// a field that creates a new table, then all keys under it will be in that
   302  	// table (not the one we're writing here).
   303  	rt := rv.Type()
   304  	var fieldsDirect, fieldsSub [][]int
   305  	var addFields func(rt reflect.Type, rv reflect.Value, start []int)
   306  	addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
   307  		for i := 0; i < rt.NumField(); i++ {
   308  			f := rt.Field(i)
   309  			// skip unexported fields
   310  			if f.PkgPath != "" && !f.Anonymous {
   311  				continue
   312  			}
   313  			frv := rv.Field(i)
   314  			if f.Anonymous {
   315  				t := f.Type
   316  				switch t.Kind() {
   317  				case reflect.Struct:
   318  					// Treat anonymous struct fields with
   319  					// tag names as though they are not
   320  					// anonymous, like encoding/json does.
   321  					if getOptions(f.Tag).name == "" {
   322  						addFields(t, frv, f.Index)
   323  						continue
   324  					}
   325  				case reflect.Ptr:
   326  					if t.Elem().Kind() == reflect.Struct &&
   327  						getOptions(f.Tag).name == "" {
   328  						if !frv.IsNil() {
   329  							addFields(t.Elem(), frv.Elem(), f.Index)
   330  						}
   331  						continue
   332  					}
   333  					// Fall through to the normal field encoding logic below
   334  					// for non-struct anonymous fields.
   335  				}
   336  			}
   337  
   338  			if typeIsHash(tomlTypeOfGo(frv)) {
   339  				fieldsSub = append(fieldsSub, append(start, f.Index...))
   340  			} else {
   341  				fieldsDirect = append(fieldsDirect, append(start, f.Index...))
   342  			}
   343  		}
   344  	}
   345  	addFields(rt, rv, nil)
   346  
   347  	var writeFields = func(fields [][]int) {
   348  		for _, fieldIndex := range fields {
   349  			sft := rt.FieldByIndex(fieldIndex)
   350  			sf := rv.FieldByIndex(fieldIndex)
   351  			if isNil(sf) {
   352  				// Don't write anything for nil fields.
   353  				continue
   354  			}
   355  
   356  			opts := getOptions(sft.Tag)
   357  			if opts.skip {
   358  				continue
   359  			}
   360  			keyName := sft.Name
   361  			if opts.name != "" {
   362  				keyName = opts.name
   363  			}
   364  			if opts.omitempty && isEmpty(sf) {
   365  				continue
   366  			}
   367  			if opts.omitzero && isZero(sf) {
   368  				continue
   369  			}
   370  
   371  			enc.encode(key.add(keyName), sf)
   372  		}
   373  	}
   374  	writeFields(fieldsDirect)
   375  	writeFields(fieldsSub)
   376  }
   377  
   378  // tomlTypeName returns the TOML type name of the Go value's type. It is
   379  // used to determine whether the types of array elements are mixed (which is
   380  // forbidden). If the Go value is nil, then it is illegal for it to be an array
   381  // element, and valueIsNil is returned as true.
   382  
   383  // Returns the TOML type of a Go value. The type may be `nil`, which means
   384  // no concrete TOML type could be found.
   385  func tomlTypeOfGo(rv reflect.Value) tomlType {
   386  	if isNil(rv) || !rv.IsValid() {
   387  		return nil
   388  	}
   389  	switch rv.Kind() {
   390  	case reflect.Bool:
   391  		return tomlBool
   392  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
   393  		reflect.Int64,
   394  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
   395  		reflect.Uint64:
   396  		return tomlInteger
   397  	case reflect.Float32, reflect.Float64:
   398  		return tomlFloat
   399  	case reflect.Array, reflect.Slice:
   400  		if typeEqual(tomlHash, tomlArrayType(rv)) {
   401  			return tomlArrayHash
   402  		}
   403  		return tomlArray
   404  	case reflect.Ptr, reflect.Interface:
   405  		return tomlTypeOfGo(rv.Elem())
   406  	case reflect.String:
   407  		return tomlString
   408  	case reflect.Map:
   409  		return tomlHash
   410  	case reflect.Struct:
   411  		switch rv.Interface().(type) {
   412  		case time.Time:
   413  			return tomlDatetime
   414  		case TextMarshaler:
   415  			return tomlString
   416  		default:
   417  			return tomlHash
   418  		}
   419  	default:
   420  		panic("unexpected reflect.Kind: " + rv.Kind().String())
   421  	}
   422  }
   423  
   424  // tomlArrayType returns the element type of a TOML array. The type returned
   425  // may be nil if it cannot be determined (e.g., a nil slice or a zero length
   426  // slize). This function may also panic if it finds a type that cannot be
   427  // expressed in TOML (such as nil elements, heterogeneous arrays or directly
   428  // nested arrays of tables).
   429  func tomlArrayType(rv reflect.Value) tomlType {
   430  	if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
   431  		return nil
   432  	}
   433  	firstType := tomlTypeOfGo(rv.Index(0))
   434  	if firstType == nil {
   435  		encPanic(errArrayNilElement)
   436  	}
   437  
   438  	rvlen := rv.Len()
   439  	for i := 1; i < rvlen; i++ {
   440  		elem := rv.Index(i)
   441  		switch elemType := tomlTypeOfGo(elem); {
   442  		case elemType == nil:
   443  			encPanic(errArrayNilElement)
   444  		case !typeEqual(firstType, elemType):
   445  			encPanic(errArrayMixedElementTypes)
   446  		}
   447  	}
   448  	// If we have a nested array, then we must make sure that the nested
   449  	// array contains ONLY primitives.
   450  	// This checks arbitrarily nested arrays.
   451  	if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
   452  		nest := tomlArrayType(eindirect(rv.Index(0)))
   453  		if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
   454  			encPanic(errArrayNoTable)
   455  		}
   456  	}
   457  	return firstType
   458  }
   459  
   460  type tagOptions struct {
   461  	skip      bool // "-"
   462  	name      string
   463  	omitempty bool
   464  	omitzero  bool
   465  }
   466  
   467  func getOptions(tag reflect.StructTag) tagOptions {
   468  	t := tag.Get("toml")
   469  	if t == "-" {
   470  		return tagOptions{skip: true}
   471  	}
   472  	var opts tagOptions
   473  	parts := strings.Split(t, ",")
   474  	opts.name = parts[0]
   475  	for _, s := range parts[1:] {
   476  		switch s {
   477  		case "omitempty":
   478  			opts.omitempty = true
   479  		case "omitzero":
   480  			opts.omitzero = true
   481  		}
   482  	}
   483  	return opts
   484  }
   485  
   486  func isZero(rv reflect.Value) bool {
   487  	switch rv.Kind() {
   488  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   489  		return rv.Int() == 0
   490  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   491  		return rv.Uint() == 0
   492  	case reflect.Float32, reflect.Float64:
   493  		return rv.Float() == 0.0
   494  	}
   495  	return false
   496  }
   497  
   498  func isEmpty(rv reflect.Value) bool {
   499  	switch rv.Kind() {
   500  	case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
   501  		return rv.Len() == 0
   502  	case reflect.Bool:
   503  		return !rv.Bool()
   504  	}
   505  	return false
   506  }
   507  
   508  func (enc *Encoder) newline() {
   509  	if enc.hasWritten {
   510  		enc.wf("\n")
   511  	}
   512  }
   513  
   514  func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
   515  	if len(key) == 0 {
   516  		encPanic(errNoKey)
   517  	}
   518  	panicIfInvalidKey(key)
   519  	enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
   520  	enc.eElement(val)
   521  	enc.newline()
   522  }
   523  
   524  func (enc *Encoder) wf(format string, v ...interface{}) {
   525  	if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
   526  		encPanic(err)
   527  	}
   528  	enc.hasWritten = true
   529  }
   530  
   531  func (enc *Encoder) indentStr(key Key) string {
   532  	return strings.Repeat(enc.Indent, len(key)-1)
   533  }
   534  
   535  func encPanic(err error) {
   536  	panic(tomlEncodeError{err})
   537  }
   538  
   539  func eindirect(v reflect.Value) reflect.Value {
   540  	switch v.Kind() {
   541  	case reflect.Ptr, reflect.Interface:
   542  		return eindirect(v.Elem())
   543  	default:
   544  		return v
   545  	}
   546  }
   547  
   548  func isNil(rv reflect.Value) bool {
   549  	switch rv.Kind() {
   550  	case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
   551  		return rv.IsNil()
   552  	default:
   553  		return false
   554  	}
   555  }
   556  
   557  func panicIfInvalidKey(key Key) {
   558  	for _, k := range key {
   559  		if len(k) == 0 {
   560  			encPanic(e("Key '%s' is not a valid table name. Key names "+
   561  				"cannot be empty.", key.maybeQuotedAll()))
   562  		}
   563  	}
   564  }
   565  
   566  func isValidKeyName(s string) bool {
   567  	return len(s) != 0
   568  }