Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/pelletier/go-toml/v2/marshaler.go
3531 views
1
package toml
2
3
import (
4
"bytes"
5
"encoding"
6
"encoding/json"
7
"fmt"
8
"io"
9
"math"
10
"reflect"
11
"slices"
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.PointerTo(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
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
636
return strconv.FormatInt(k.Int(), 10), nil
637
638
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
639
return strconv.FormatUint(k.Uint(), 10), nil
640
641
case keyType.Kind() == reflect.Float32:
642
return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil
643
644
case keyType.Kind() == reflect.Float64:
645
return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil
646
}
647
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
648
}
649
650
func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
651
var (
652
t table
653
emptyValueOptions valueOptions
654
)
655
656
iter := v.MapRange()
657
for iter.Next() {
658
v := iter.Value()
659
660
if isNil(v) {
661
continue
662
}
663
664
k, err := enc.keyToString(iter.Key())
665
if err != nil {
666
return nil, err
667
}
668
669
if willConvertToTableOrArrayTable(ctx, v) {
670
t.pushTable(k, v, emptyValueOptions)
671
} else {
672
t.pushKV(k, v, emptyValueOptions)
673
}
674
}
675
676
sortEntriesByKey(t.kvs)
677
sortEntriesByKey(t.tables)
678
679
return enc.encodeTable(b, ctx, t)
680
}
681
682
func sortEntriesByKey(e []entry) {
683
slices.SortFunc(e, func(a, b entry) int {
684
return strings.Compare(a.Key, b.Key)
685
})
686
}
687
688
type entry struct {
689
Key string
690
Value reflect.Value
691
Options valueOptions
692
}
693
694
type table struct {
695
kvs []entry
696
tables []entry
697
}
698
699
func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
700
for _, e := range t.kvs {
701
if e.Key == k {
702
return
703
}
704
}
705
706
t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
707
}
708
709
func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
710
for _, e := range t.tables {
711
if e.Key == k {
712
return
713
}
714
}
715
t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
716
}
717
718
func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
719
// TODO: cache this
720
typ := v.Type()
721
for i := 0; i < typ.NumField(); i++ {
722
fieldType := typ.Field(i)
723
724
// only consider exported fields
725
if fieldType.PkgPath != "" {
726
continue
727
}
728
729
tag := fieldType.Tag.Get("toml")
730
731
// special field name to skip field
732
if tag == "-" {
733
continue
734
}
735
736
k, opts := parseTag(tag)
737
if !isValidName(k) {
738
k = ""
739
}
740
741
f := v.Field(i)
742
743
if k == "" {
744
if fieldType.Anonymous {
745
if fieldType.Type.Kind() == reflect.Struct {
746
walkStruct(ctx, t, f)
747
} else if fieldType.Type.Kind() == reflect.Ptr && !f.IsNil() && f.Elem().Kind() == reflect.Struct {
748
walkStruct(ctx, t, f.Elem())
749
}
750
continue
751
} else {
752
k = fieldType.Name
753
}
754
}
755
756
if isNil(f) {
757
continue
758
}
759
760
options := valueOptions{
761
multiline: opts.multiline,
762
omitempty: opts.omitempty,
763
commented: opts.commented,
764
comment: fieldType.Tag.Get("comment"),
765
}
766
767
if opts.inline || !willConvertToTableOrArrayTable(ctx, f) {
768
t.pushKV(k, f, options)
769
} else {
770
t.pushTable(k, f, options)
771
}
772
}
773
}
774
775
func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
776
var t table
777
778
walkStruct(ctx, &t, v)
779
780
return enc.encodeTable(b, ctx, t)
781
}
782
783
func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte {
784
for len(comment) > 0 {
785
var line string
786
idx := strings.IndexByte(comment, '\n')
787
if idx >= 0 {
788
line = comment[:idx]
789
comment = comment[idx+1:]
790
} else {
791
line = comment
792
comment = ""
793
}
794
b = enc.indent(indent, b)
795
b = append(b, "# "...)
796
b = append(b, line...)
797
b = append(b, '\n')
798
}
799
return b
800
}
801
802
func isValidName(s string) bool {
803
if s == "" {
804
return false
805
}
806
for _, c := range s {
807
switch {
808
case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
809
// Backslash and quote chars are reserved, but
810
// otherwise any punctuation chars are allowed
811
// in a tag name.
812
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
813
return false
814
}
815
}
816
return true
817
}
818
819
type tagOptions struct {
820
multiline bool
821
inline bool
822
omitempty bool
823
commented bool
824
}
825
826
func parseTag(tag string) (string, tagOptions) {
827
opts := tagOptions{}
828
829
idx := strings.Index(tag, ",")
830
if idx == -1 {
831
return tag, opts
832
}
833
834
raw := tag[idx+1:]
835
tag = string(tag[:idx])
836
for raw != "" {
837
var o string
838
i := strings.Index(raw, ",")
839
if i >= 0 {
840
o, raw = raw[:i], raw[i+1:]
841
} else {
842
o, raw = raw, ""
843
}
844
switch o {
845
case "multiline":
846
opts.multiline = true
847
case "inline":
848
opts.inline = true
849
case "omitempty":
850
opts.omitempty = true
851
case "commented":
852
opts.commented = true
853
}
854
}
855
856
return tag, opts
857
}
858
859
func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) {
860
var err error
861
862
ctx.shiftKey()
863
864
if ctx.insideKv || (ctx.inline && !ctx.isRoot()) {
865
return enc.encodeTableInline(b, ctx, t)
866
}
867
868
if !ctx.skipTableHeader {
869
b, err = enc.encodeTableHeader(ctx, b)
870
if err != nil {
871
return nil, err
872
}
873
874
if enc.indentTables && len(ctx.parentKey) > 0 {
875
ctx.indent++
876
}
877
}
878
ctx.skipTableHeader = false
879
880
hasNonEmptyKV := false
881
for _, kv := range t.kvs {
882
if shouldOmitEmpty(kv.Options, kv.Value) {
883
continue
884
}
885
hasNonEmptyKV = true
886
887
ctx.setKey(kv.Key)
888
ctx2 := ctx
889
ctx2.commented = kv.Options.commented || ctx2.commented
890
891
b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value)
892
if err != nil {
893
return nil, err
894
}
895
896
b = append(b, '\n')
897
}
898
899
first := true
900
for _, table := range t.tables {
901
if shouldOmitEmpty(table.Options, table.Value) {
902
continue
903
}
904
if first {
905
first = false
906
if hasNonEmptyKV {
907
b = append(b, '\n')
908
}
909
} else {
910
b = append(b, "\n"...)
911
}
912
913
ctx.setKey(table.Key)
914
915
ctx.options = table.Options
916
ctx2 := ctx
917
ctx2.commented = ctx2.commented || ctx.options.commented
918
919
b, err = enc.encode(b, ctx2, table.Value)
920
if err != nil {
921
return nil, err
922
}
923
}
924
925
return b, nil
926
}
927
928
func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) {
929
var err error
930
931
b = append(b, '{')
932
933
first := true
934
for _, kv := range t.kvs {
935
if shouldOmitEmpty(kv.Options, kv.Value) {
936
continue
937
}
938
939
if first {
940
first = false
941
} else {
942
b = append(b, `, `...)
943
}
944
945
ctx.setKey(kv.Key)
946
947
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
948
if err != nil {
949
return nil, err
950
}
951
}
952
953
if len(t.tables) > 0 {
954
panic("inline table cannot contain nested tables, only key-values")
955
}
956
957
b = append(b, "}"...)
958
959
return b, nil
960
}
961
962
func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
963
if !v.IsValid() {
964
return false
965
}
966
if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
967
return false
968
}
969
970
t := v.Type()
971
switch t.Kind() {
972
case reflect.Map, reflect.Struct:
973
return !ctx.inline
974
case reflect.Interface:
975
return willConvertToTable(ctx, v.Elem())
976
case reflect.Ptr:
977
if v.IsNil() {
978
return false
979
}
980
981
return willConvertToTable(ctx, v.Elem())
982
default:
983
return false
984
}
985
}
986
987
func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool {
988
if ctx.insideKv {
989
return false
990
}
991
t := v.Type()
992
993
if t.Kind() == reflect.Interface {
994
return willConvertToTableOrArrayTable(ctx, v.Elem())
995
}
996
997
if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
998
if v.Len() == 0 {
999
// An empty slice should be a kv = [].
1000
return false
1001
}
1002
1003
for i := 0; i < v.Len(); i++ {
1004
t := willConvertToTable(ctx, v.Index(i))
1005
1006
if !t {
1007
return false
1008
}
1009
}
1010
1011
return true
1012
}
1013
1014
return willConvertToTable(ctx, v)
1015
}
1016
1017
func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
1018
if v.Len() == 0 {
1019
b = append(b, "[]"...)
1020
1021
return b, nil
1022
}
1023
1024
if willConvertToTableOrArrayTable(ctx, v) {
1025
return enc.encodeSliceAsArrayTable(b, ctx, v)
1026
}
1027
1028
return enc.encodeSliceAsArray(b, ctx, v)
1029
}
1030
1031
// caller should have checked that v is a slice that only contains values that
1032
// encode into tables.
1033
func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
1034
ctx.shiftKey()
1035
1036
scratch := make([]byte, 0, 64)
1037
1038
scratch = enc.commented(ctx.commented, scratch)
1039
1040
if enc.indentTables {
1041
scratch = enc.indent(ctx.indent, scratch)
1042
}
1043
1044
scratch = append(scratch, "[["...)
1045
1046
for i, k := range ctx.parentKey {
1047
if i > 0 {
1048
scratch = append(scratch, '.')
1049
}
1050
1051
scratch = enc.encodeKey(scratch, k)
1052
}
1053
1054
scratch = append(scratch, "]]\n"...)
1055
ctx.skipTableHeader = true
1056
1057
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
1058
1059
if enc.indentTables {
1060
ctx.indent++
1061
}
1062
1063
for i := 0; i < v.Len(); i++ {
1064
if i != 0 {
1065
b = append(b, "\n"...)
1066
}
1067
1068
b = append(b, scratch...)
1069
1070
var err error
1071
b, err = enc.encode(b, ctx, v.Index(i))
1072
if err != nil {
1073
return nil, err
1074
}
1075
}
1076
1077
return b, nil
1078
}
1079
1080
func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
1081
multiline := ctx.options.multiline || enc.arraysMultiline
1082
separator := ", "
1083
1084
b = append(b, '[')
1085
1086
subCtx := ctx
1087
subCtx.options = valueOptions{}
1088
1089
if multiline {
1090
separator = ",\n"
1091
1092
b = append(b, '\n')
1093
1094
subCtx.indent++
1095
}
1096
1097
var err error
1098
first := true
1099
1100
for i := 0; i < v.Len(); i++ {
1101
if first {
1102
first = false
1103
} else {
1104
b = append(b, separator...)
1105
}
1106
1107
if multiline {
1108
b = enc.indent(subCtx.indent, b)
1109
}
1110
1111
b, err = enc.encode(b, subCtx, v.Index(i))
1112
if err != nil {
1113
return nil, err
1114
}
1115
}
1116
1117
if multiline {
1118
b = append(b, '\n')
1119
b = enc.indent(ctx.indent, b)
1120
}
1121
1122
b = append(b, ']')
1123
1124
return b, nil
1125
}
1126
1127
func (enc *Encoder) indent(level int, b []byte) []byte {
1128
for i := 0; i < level; i++ {
1129
b = append(b, enc.indentSymbol...)
1130
}
1131
1132
return b
1133
}
1134
1135