16
"github.com/pelletier/go-toml/v2/internal/characters"
19
// Marshal serializes a Go value as a TOML document.
21
// It is a shortcut for Encoder.Encode() with the default options.
22
func Marshal(v interface{}) ([]byte, error) {
24
enc := NewEncoder(&buf)
31
return buf.Bytes(), nil
34
// Encoder writes a TOML document to an output stream.
46
// NewEncoder returns a new Encoder that writes to w.
47
func NewEncoder(w io.Writer) *Encoder {
54
// SetTablesInline forces the encoder to emit all tables inline.
56
// This behavior can be controlled on an individual struct field basis with the
59
// MyField `toml:",inline"`
60
func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
61
enc.tablesInline = inline
65
// SetArraysMultiline forces the encoder to emit all arrays with one element per
68
// This behavior can be controlled on an individual struct field basis with the multiline tag:
70
// MyField `multiline:"true"`
71
func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder {
72
enc.arraysMultiline = multiline
76
// SetIndentSymbol defines the string that should be used for indentation. The
77
// provided string is repeated for each indentation level. Defaults to two
79
func (enc *Encoder) SetIndentSymbol(s string) *Encoder {
84
// SetIndentTables forces the encoder to intent tables and array tables.
85
func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
86
enc.indentTables = indent
90
// Encode writes a TOML representation of v to the stream.
92
// If v cannot be represented to TOML it returns an error.
96
// A top level slice containing only maps or structs is encoded as [[table
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}.
102
// Nil interfaces and nil pointers are not supported.
104
// Keys in key-values always have one part.
106
// Intermediate tables are always printed.
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
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.
119
// When encoding structs, fields are encoded in order of definition, with their
122
// Tables and array tables are separated by empty lines. However, consecutive
123
// subtables definitions are not. For example:
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.
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.
146
// The "inline" option turns fields that would be emitted as tables into inline
147
// tables instead. It has no effect on other fields.
149
// The "omitempty" option prevents empty values or groups from being emitted.
151
// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit
152
// a TOML comment before the value being annotated. Comments are ignored inside
153
// inline tables. For array tables, the comment is only present before the first
154
// element of the array.
155
func (enc *Encoder) Encode(v interface{}) error {
161
ctx.inline = enc.tablesInline
164
return fmt.Errorf("toml: cannot encode a nil interface")
167
b, err := enc.encode(b, ctx, reflect.ValueOf(v))
172
_, err = enc.w.Write(b)
174
return fmt.Errorf("toml: cannot write: %w", err)
180
type valueOptions struct {
186
type encoderCtx struct {
187
// Current top-level key.
190
// Key that should be used for a KV.
192
// Extra flag to account for the empty string
195
// Set to true to indicate that the encoder is inside a KV, so that all
196
// tables need to be inlined.
199
// Set to true to skip the first table header in an array table.
202
// Should the next table be encoded as inline
208
// Options coming from struct tags
212
func (ctx *encoderCtx) shiftKey() {
214
ctx.parentKey = append(ctx.parentKey, ctx.key)
219
func (ctx *encoderCtx) setKey(k string) {
224
func (ctx *encoderCtx) clearKey() {
229
func (ctx *encoderCtx) isRoot() bool {
230
return len(ctx.parentKey) == 0 && !ctx.hasKey
233
func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
236
switch x := i.(type) {
238
if x.Nanosecond() > 0 {
239
return x.AppendFormat(b, time.RFC3339Nano), nil
241
return x.AppendFormat(b, time.RFC3339), nil
243
return append(b, x.String()...), nil
245
return append(b, x.String()...), nil
247
return append(b, x.String()...), nil
250
hasTextMarshaler := v.Type().Implements(textMarshalerType)
251
if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
252
if !hasTextMarshaler {
257
return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type())
260
text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
265
b = enc.encodeString(b, string(text), ctx.options)
273
return enc.encodeMap(b, ctx, v)
275
return enc.encodeStruct(b, ctx, v)
277
return enc.encodeSlice(b, ctx, v)
278
case reflect.Interface:
280
return nil, fmt.Errorf("toml: encoding a nil interface is not supported")
283
return enc.encode(b, ctx, v.Elem())
286
return enc.encode(b, ctx, reflect.Zero(v.Type().Elem()))
289
return enc.encode(b, ctx, v.Elem())
293
b = enc.encodeString(b, v.String(), ctx.options)
294
case reflect.Float32:
298
b = append(b, "nan"...)
299
} else if f > math.MaxFloat32 {
300
b = append(b, "inf"...)
301
} else if f < -math.MaxFloat32 {
302
b = append(b, "-inf"...)
303
} else if math.Trunc(f) == f {
304
b = strconv.AppendFloat(b, f, 'f', 1, 32)
306
b = strconv.AppendFloat(b, f, 'f', -1, 32)
308
case reflect.Float64:
311
b = append(b, "nan"...)
312
} else if f > math.MaxFloat64 {
313
b = append(b, "inf"...)
314
} else if f < -math.MaxFloat64 {
315
b = append(b, "-inf"...)
316
} else if math.Trunc(f) == f {
317
b = strconv.AppendFloat(b, f, 'f', 1, 64)
319
b = strconv.AppendFloat(b, f, 'f', -1, 64)
323
b = append(b, "true"...)
325
b = append(b, "false"...)
327
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
329
if x > uint64(math.MaxInt64) {
330
return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))
332
b = strconv.AppendUint(b, x, 10)
333
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
334
b = strconv.AppendInt(b, v.Int(), 10)
336
return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind())
342
func isNil(v reflect.Value) bool {
344
case reflect.Ptr, reflect.Interface, reflect.Map:
351
func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
352
return options.omitempty && isEmptyValue(v)
355
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
359
b = enc.encodeComment(ctx.indent, options.comment, b)
360
b = enc.indent(ctx.indent, b)
363
b = enc.encodeKey(b, ctx.key)
364
b = append(b, " = "...)
366
// create a copy of the context because the value of a KV shouldn't
367
// modify the global context.
369
subctx.insideKv = true
371
subctx.options = options
373
b, err = enc.encode(b, subctx, v)
381
func isEmptyValue(v reflect.Value) bool {
384
return isEmptyStruct(v)
385
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
389
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
391
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
393
case reflect.Float32, reflect.Float64:
394
return v.Float() == 0
395
case reflect.Interface, reflect.Ptr:
401
func isEmptyStruct(v reflect.Value) bool {
402
// TODO: merge with walkStruct and cache.
404
for i := 0; i < typ.NumField(); i++ {
405
fieldType := typ.Field(i)
407
// only consider exported fields
408
if fieldType.PkgPath != "" {
412
tag := fieldType.Tag.Get("toml")
414
// special field name to skip field
421
if !isEmptyValue(f) {
429
const literalQuote = '\''
431
func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
433
return enc.encodeQuotedString(options.multiline, b, v)
436
return enc.encodeLiteralString(b, v)
439
func needsQuoting(v string) bool {
441
for _, b := range []byte(v) {
442
if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) {
449
// caller should have checked that the string does not contain new lines or ' .
450
func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
451
b = append(b, literalQuote)
453
b = append(b, literalQuote)
458
func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
465
b = append(b, stringQuote...)
471
hextable = "0123456789ABCDEF"
472
// U+0000 to U+0008, U+000A to U+001F, U+007F
480
for _, r := range []byte(v) {
483
b = append(b, `\\`...)
485
b = append(b, `\"`...)
487
b = append(b, `\b`...)
489
b = append(b, `\f`...)
494
b = append(b, `\n`...)
497
b = append(b, `\r`...)
499
b = append(b, `\t`...)
502
case r >= nul && r <= bs, r >= lf && r <= us, r == del:
503
b = append(b, `\u00`...)
504
b = append(b, hextable[r>>4])
505
b = append(b, hextable[r&0x0f])
512
b = append(b, stringQuote...)
517
// caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ .
518
func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
519
return append(b, v...)
522
func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) {
523
if len(ctx.parentKey) == 0 {
527
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
529
b = enc.indent(ctx.indent, b)
533
b = enc.encodeKey(b, ctx.parentKey[0])
535
for _, k := range ctx.parentKey[1:] {
537
b = enc.encodeKey(b, k)
540
b = append(b, "]\n"...)
546
func (enc *Encoder) encodeKey(b []byte, k string) []byte {
547
needsQuotation := false
548
cannotUseLiteral := false
551
return append(b, "''"...)
554
for _, c := range k {
555
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
559
if c == literalQuote {
560
cannotUseLiteral = true
563
needsQuotation = true
566
if needsQuotation && needsQuoting(k) {
567
cannotUseLiteral = true
571
case cannotUseLiteral:
572
return enc.encodeQuotedString(false, b, k)
574
return enc.encodeLiteralString(b, k)
576
return enc.encodeUnquotedKey(b, k)
580
func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
583
case keyType.Kind() == reflect.String:
584
return k.String(), nil
586
case keyType.Implements(textMarshalerType):
587
keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()
589
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
591
return string(keyB), nil
593
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
596
func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
599
emptyValueOptions valueOptions
610
k, err := enc.keyToString(iter.Key())
615
if willConvertToTableOrArrayTable(ctx, v) {
616
t.pushTable(k, v, emptyValueOptions)
618
t.pushKV(k, v, emptyValueOptions)
622
sortEntriesByKey(t.kvs)
623
sortEntriesByKey(t.tables)
625
return enc.encodeTable(b, ctx, t)
628
func sortEntriesByKey(e []entry) {
629
sort.Slice(e, func(i, j int) bool {
630
return e[i].Key < e[j].Key
645
func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
646
for _, e := range t.kvs {
652
t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
655
func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
656
for _, e := range t.tables {
661
t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
664
func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
667
for i := 0; i < typ.NumField(); i++ {
668
fieldType := typ.Field(i)
670
// only consider exported fields
671
if fieldType.PkgPath != "" {
675
tag := fieldType.Tag.Get("toml")
677
// special field name to skip field
682
k, opts := parseTag(tag)
690
if fieldType.Anonymous {
691
if fieldType.Type.Kind() == reflect.Struct {
692
walkStruct(ctx, t, f)
704
options := valueOptions{
705
multiline: opts.multiline,
706
omitempty: opts.omitempty,
707
comment: fieldType.Tag.Get("comment"),
710
if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {
711
t.pushKV(k, f, options)
713
t.pushTable(k, f, options)
718
func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
721
walkStruct(ctx, &t, v)
723
return enc.encodeTable(b, ctx, t)
726
func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
727
for len(comment) > 0 {
729
idx := strings.IndexByte(comment, '\n')
732
comment = comment[idx+1:]
737
b = enc.indent(indent, b)
738
b = append(b, "# "...)
739
b = append(b, line...)
745
func isValidName(s string) bool {
749
for _, c := range s {
751
case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
752
// Backslash and quote chars are reserved, but
753
// otherwise any punctuation chars are allowed
755
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
762
type tagOptions struct {
768
func parseTag(tag string) (string, tagOptions) {
771
idx := strings.Index(tag, ",")
777
tag = string(tag[:idx])
780
i := strings.Index(raw, ",")
782
o, raw = raw[:i], raw[i+1:]
788
opts.multiline = true
792
opts.omitempty = true
799
func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) {
804
if ctx.insideKv || (ctx.inline && !ctx.isRoot()) {
805
return enc.encodeTableInline(b, ctx, t)
808
if !ctx.skipTableHeader {
809
b, err = enc.encodeTableHeader(ctx, b)
814
if enc.indentTables && len(ctx.parentKey) > 0 {
818
ctx.skipTableHeader = false
820
hasNonEmptyKV := false
821
for _, kv := range t.kvs {
822
if shouldOmitEmpty(kv.Options, kv.Value) {
829
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
838
for _, table := range t.tables {
839
if shouldOmitEmpty(table.Options, table.Value) {
848
b = append(b, "\n"...)
851
ctx.setKey(table.Key)
853
ctx.options = table.Options
855
b, err = enc.encode(b, ctx, table.Value)
864
func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) {
870
for _, kv := range t.kvs {
871
if shouldOmitEmpty(kv.Options, kv.Value) {
878
b = append(b, `, `...)
883
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
889
if len(t.tables) > 0 {
890
panic("inline table cannot contain nested tables, only key-values")
893
b = append(b, "}"...)
898
func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
902
if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
908
case reflect.Map, reflect.Struct:
910
case reflect.Interface:
911
return willConvertToTable(ctx, v.Elem())
917
return willConvertToTable(ctx, v.Elem())
923
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
929
if t.Kind() == reflect.Interface {
930
return willConvertToTableOrArrayTable(ctx, v.Elem())
933
if t.Kind() == reflect.Slice {
935
// An empty slice should be a kv = [].
939
for i := 0; i < v.Len(); i++ {
940
t := willConvertToTable(ctx, v.Index(i))
950
return willConvertToTable(ctx, v)
953
func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
955
b = append(b, "[]"...)
960
if willConvertToTableOrArrayTable(ctx, v) {
961
return enc.encodeSliceAsArrayTable(b, ctx, v)
964
return enc.encodeSliceAsArray(b, ctx, v)
967
// caller should have checked that v is a slice that only contains values that
968
// encode into tables.
969
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
972
scratch := make([]byte, 0, 64)
973
scratch = append(scratch, "[["...)
975
for i, k := range ctx.parentKey {
977
scratch = append(scratch, '.')
980
scratch = enc.encodeKey(scratch, k)
983
scratch = append(scratch, "]]\n"...)
984
ctx.skipTableHeader = true
986
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
988
for i := 0; i < v.Len(); i++ {
990
b = append(b, "\n"...)
993
b = append(b, scratch...)
996
b, err = enc.encode(b, ctx, v.Index(i))
1005
func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
1006
multiline := ctx.options.multiline || enc.arraysMultiline
1012
subCtx.options = valueOptions{}
1025
for i := 0; i < v.Len(); i++ {
1029
b = append(b, separator...)
1033
b = enc.indent(subCtx.indent, b)
1036
b, err = enc.encode(b, subCtx, v.Index(i))
1044
b = enc.indent(ctx.indent, b)
1052
func (enc *Encoder) indent(level int, b []byte) []byte {
1053
for i := 0; i < level; i++ {
1054
b = append(b, enc.indentSymbol...)