15 type tomlEncodeError struct{ error }
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
33 var quotedReplacer = strings.NewReplacer(
41 // Encoder controls the encoding of Go values to a TOML document to some
44 // The indentation level can be controlled with the Indent field.
46 // A single indentation level. By default it is two spaces.
49 // hasWritten is whether we have written any output to w yet.
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 {
58 w: bufio.NewWriter(w),
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.
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.)
73 // When encoding TOML hashes (i.e., Go maps or structs), keys without any
74 // sub-hashes are encoded first.
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.
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 {
94 func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
96 if r := recover(); r != nil {
97 if terr, ok := r.(tomlEncodeError); ok {
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)
121 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
123 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
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)
131 enc.keyEqElement(key, rv)
133 case reflect.Interface:
137 enc.encode(key, rv.Elem())
147 enc.encode(key, rv.Elem())
151 panic(e("unsupported type for key '%s': %s", key, k))
155 // eElement encodes any value that can be an array element (primitives and
157 func (enc *Encoder) eElement(rv reflect.Value) {
158 switch v := rv.Interface().(type) {
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"))
166 // Special case. Use text marshaler if it's available for this value.
167 if s, err := v.MarshalText(); err != nil {
170 enc.writeQuoted(string(s))
176 enc.wf(strconv.FormatBool(rv.Bool()))
177 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
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())
192 enc.writeQuoted(rv.String())
194 panic(e("unexpected primitive type: %s", rv.Kind()))
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, ".") {
207 func (enc *Encoder) writeQuoted(s string) {
208 enc.wf("\"%s\"", quotedReplacer.Replace(s))
211 func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
214 for i := 0; i < length; i++ {
224 func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
228 for i := 0; i < rv.Len(); i++ {
233 panicIfInvalidKey(key)
235 enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
237 enc.eMapOrStruct(key, trv)
241 func (enc *Encoder) eTable(key Key, rv reflect.Value) {
242 panicIfInvalidKey(key)
244 // Output an extra newline between top-level tables.
245 // (The newline isn't written if nothing else has been written though.)
249 enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
252 enc.eMapOrStruct(key, rv)
255 func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
256 switch rv := eindirect(rv); rv.Kind() {
262 panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
266 func (enc *Encoder) eMap(key Key, rv reflect.Value) {
268 if rt.Key().Kind() != reflect.String {
269 encPanic(errNonString)
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() {
277 if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
278 mapKeysSub = append(mapKeysSub, k)
280 mapKeysDirect = append(mapKeysDirect, k)
284 var writeMapKeys = func(mapKeys []string) {
285 sort.Strings(mapKeys)
286 for _, mapKey := range mapKeys {
287 mrv := rv.MapIndex(reflect.ValueOf(mapKey))
289 // Don't write anything for nil fields.
292 enc.encode(key.add(mapKey), mrv)
295 writeMapKeys(mapKeysDirect)
296 writeMapKeys(mapKeysSub)
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).
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++ {
309 // skip unexported fields
310 if f.PkgPath != "" && !f.Anonymous {
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)
326 if t.Elem().Kind() == reflect.Struct &&
327 getOptions(f.Tag).name == "" {
329 addFields(t.Elem(), frv.Elem(), f.Index)
333 // Fall through to the normal field encoding logic below
334 // for non-struct anonymous fields.
338 if typeIsHash(tomlTypeOfGo(frv)) {
339 fieldsSub = append(fieldsSub, append(start, f.Index...))
341 fieldsDirect = append(fieldsDirect, append(start, f.Index...))
345 addFields(rt, rv, nil)
347 var writeFields = func(fields [][]int) {
348 for _, fieldIndex := range fields {
349 sft := rt.FieldByIndex(fieldIndex)
350 sf := rv.FieldByIndex(fieldIndex)
352 // Don't write anything for nil fields.
356 opts := getOptions(sft.Tag)
364 if opts.omitempty && isEmpty(sf) {
367 if opts.omitzero && isZero(sf) {
371 enc.encode(key.add(keyName), sf)
374 writeFields(fieldsDirect)
375 writeFields(fieldsSub)
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.
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() {
392 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
394 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
397 case reflect.Float32, reflect.Float64:
399 case reflect.Array, reflect.Slice:
400 if typeEqual(tomlHash, tomlArrayType(rv)) {
404 case reflect.Ptr, reflect.Interface:
405 return tomlTypeOfGo(rv.Elem())
411 switch rv.Interface().(type) {
420 panic("unexpected reflect.Kind: " + rv.Kind().String())
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 {
433 firstType := tomlTypeOfGo(rv.Index(0))
434 if firstType == nil {
435 encPanic(errArrayNilElement)
439 for i := 1; i < rvlen; i++ {
441 switch elemType := tomlTypeOfGo(elem); {
442 case elemType == nil:
443 encPanic(errArrayNilElement)
444 case !typeEqual(firstType, elemType):
445 encPanic(errArrayMixedElementTypes)
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)
460 type tagOptions struct {
467 func getOptions(tag reflect.StructTag) tagOptions {
470 return tagOptions{skip: true}
473 parts := strings.Split(t, ",")
475 for _, s := range parts[1:] {
478 opts.omitempty = true
486 func isZero(rv reflect.Value) bool {
488 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
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
498 func isEmpty(rv reflect.Value) bool {
500 case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
508 func (enc *Encoder) newline() {
514 func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
518 panicIfInvalidKey(key)
519 enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
524 func (enc *Encoder) wf(format string, v ...interface{}) {
525 if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
528 enc.hasWritten = true
531 func (enc *Encoder) indentStr(key Key) string {
532 return strings.Repeat(enc.Indent, len(key)-1)
535 func encPanic(err error) {
536 panic(tomlEncodeError{err})
539 func eindirect(v reflect.Value) reflect.Value {
541 case reflect.Ptr, reflect.Interface:
542 return eindirect(v.Elem())
548 func isNil(rv reflect.Value) bool {
550 case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
557 func panicIfInvalidKey(key Key) {
558 for _, k := range key {
560 encPanic(e("Key '%s' is not a valid table name. Key names "+
561 "cannot be empty.", key.maybeQuotedAll()))
566 func isValidKeyName(s string) bool {