Encoder multiline array (#520)

This commit is contained in:
Thomas Pelletier
2021-04-21 22:13:45 -04:00
committed by GitHub
parent 6fe332a869
commit 9ba52996d8
2 changed files with 133 additions and 11 deletions
+53 -6
View File
@@ -33,21 +33,36 @@ type Encoder struct {
w io.Writer
// global settings
tablesInline bool
tablesInline bool
arraysMultiline bool
indentSymbol string
}
// NewEncoder returns a new Encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
w: w,
w: w,
indentSymbol: " ",
}
}
// SetTablesInline forces the encoder to emit all tables inline.
//
// This behavior can be controled on an individual struct field basis with the
// `inline="true"` tag.
func (e *Encoder) SetTablesInline(inline bool) {
e.tablesInline = inline
}
// SetArraysMultiline forces the encoder to emit all arrays with one element per
// line.
//
// This behavior can be controled on an individual struct field basis with the
// `multiline="true"` tag.
func (e *Encoder) SetArraysMultiline(multiline bool) {
e.arraysMultiline = multiline
}
// Encode writes a TOML representation of v to the stream.
//
// If v cannot be represented to TOML it returns an error.
@@ -121,6 +136,10 @@ type encoderCtx struct {
// Should the next table be encoded as inline
inline bool
// Indentation level
indent int
// Options coming from struct tags
options valueOptions
}
@@ -544,6 +563,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
for _, table := range t.tables {
ctx.setKey(table.Key)
ctx.options = table.Options
b, err = enc.encode(b, ctx, table.Value)
if err != nil {
return nil, err
@@ -721,25 +741,52 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
}
func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
multiline := ctx.options.multiline || enc.arraysMultiline
separator := ", "
b = append(b, '[')
subCtx := ctx
subCtx.options = valueOptions{}
if multiline {
separator = ",\n"
b = append(b, '\n')
subCtx.indent++
}
var err error
first := true
for i := 0; i < v.Len(); i++ {
if !first {
b = append(b, ", "...)
if first {
first = false
} else {
b = append(b, separator...)
}
first = false
if multiline {
b = enc.indent(subCtx.indent, b)
}
b, err = enc.encode(b, ctx, v.Index(i))
b, err = enc.encode(b, subCtx, v.Index(i))
if err != nil {
return nil, err
}
}
if multiline {
b = append(b, '\n')
b = enc.indent(ctx.indent, b)
}
b = append(b, ']')
return b, nil
}
func (enc *Encoder) indent(level int, b []byte) []byte {
for i := 0; i < level; i++ {
b = append(b, enc.indentSymbol...)
}
return b
}
+80 -5
View File
@@ -3,6 +3,7 @@ package toml_test
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
@@ -262,6 +263,39 @@ world"""`,
A = {isinline = 'yes'}
[B]
isinline = 'no'
`,
},
{
desc: "mutiline array int",
v: struct {
A []int `multiline:"true"`
B []int
}{
A: []int{1, 2, 3, 4},
B: []int{1, 2, 3, 4},
},
expected: `
A = [
1,
2,
3,
4
]
B = [1, 2, 3, 4]
`,
},
{
desc: "mutiline array in array",
v: struct {
A [][]int `multiline:"true"`
}{
A: [][]int{{1, 2}, {3, 4}},
},
expected: `
A = [
[1, 2],
[3, 4]
]
`,
},
}
@@ -285,13 +319,10 @@ isinline = 'no'
err = toml.Unmarshal(b, &defaultMap)
require.NoError(t, err)
// checks that the TablesInline mode generates valid,
// equivalent TOML
t.Run("tables inline", func(t *testing.T) {
testWithAllFlags(t, func(t *testing.T, flags int) {
var buf bytes.Buffer
enc := toml.NewEncoder(&buf)
enc.SetTablesInline(true)
setFlags(enc, flags)
err := enc.Encode(e.v)
require.NoError(t, err)
@@ -306,6 +337,50 @@ isinline = 'no'
}
}
type flagsSetters []struct {
name string
f func(enc *toml.Encoder, flag bool)
}
var allFlags = flagsSetters{
{"arrays-multiline", (*toml.Encoder).SetArraysMultiline},
{"tables-inline", (*toml.Encoder).SetTablesInline},
}
func setFlags(enc *toml.Encoder, flags int) {
for i := 0; i < len(allFlags); i++ {
enabled := flags&1 > 0
allFlags[i].f(enc, enabled)
}
}
func testWithAllFlags(t *testing.T, testfn func(t *testing.T, flags int)) {
t.Helper()
testWithFlags(t, 0, allFlags, testfn)
}
func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t *testing.T, flags int)) {
t.Helper()
if len(setters) == 0 {
testfn(t, flags)
return
}
s := setters[0]
for _, enabled := range []bool{false, true} {
name := fmt.Sprintf("%s=%t", s.name, enabled)
newFlags := flags << 1
if enabled {
newFlags++
}
t.Run(name, func(t *testing.T) {
testWithFlags(t, newFlags, setters[1:], testfn)
})
}
}
func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
t.Helper()
cutset := "\n"