Encoder multiline array (#520)
This commit is contained in:
+53
-6
@@ -33,21 +33,36 @@ type Encoder struct {
|
|||||||
w io.Writer
|
w io.Writer
|
||||||
|
|
||||||
// global settings
|
// global settings
|
||||||
tablesInline bool
|
tablesInline bool
|
||||||
|
arraysMultiline bool
|
||||||
|
indentSymbol string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns a new Encoder that writes to w.
|
// NewEncoder returns a new Encoder that writes to w.
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
return &Encoder{
|
return &Encoder{
|
||||||
w: w,
|
w: w,
|
||||||
|
indentSymbol: " ",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTablesInline forces the encoder to emit all tables inline.
|
// 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) {
|
func (e *Encoder) SetTablesInline(inline bool) {
|
||||||
e.tablesInline = inline
|
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.
|
// Encode writes a TOML representation of v to the stream.
|
||||||
//
|
//
|
||||||
// If v cannot be represented to TOML it returns an error.
|
// 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
|
// Should the next table be encoded as inline
|
||||||
inline bool
|
inline bool
|
||||||
|
|
||||||
|
// Indentation level
|
||||||
|
indent int
|
||||||
|
|
||||||
|
// Options coming from struct tags
|
||||||
options valueOptions
|
options valueOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,6 +563,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
|||||||
for _, table := range t.tables {
|
for _, table := range t.tables {
|
||||||
ctx.setKey(table.Key)
|
ctx.setKey(table.Key)
|
||||||
|
|
||||||
|
ctx.options = table.Options
|
||||||
b, err = enc.encode(b, ctx, table.Value)
|
b, err = enc.encode(b, ctx, table.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
|
multiline := ctx.options.multiline || enc.arraysMultiline
|
||||||
|
separator := ", "
|
||||||
|
|
||||||
b = append(b, '[')
|
b = append(b, '[')
|
||||||
|
|
||||||
|
subCtx := ctx
|
||||||
|
subCtx.options = valueOptions{}
|
||||||
|
|
||||||
|
if multiline {
|
||||||
|
separator = ",\n"
|
||||||
|
b = append(b, '\n')
|
||||||
|
subCtx.indent++
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
first := true
|
first := true
|
||||||
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
if !first {
|
if first {
|
||||||
b = append(b, ", "...)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if multiline {
|
||||||
|
b = append(b, '\n')
|
||||||
|
b = enc.indent(ctx.indent, b)
|
||||||
|
}
|
||||||
b = append(b, ']')
|
b = append(b, ']')
|
||||||
|
|
||||||
return b, nil
|
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
@@ -3,6 +3,7 @@ package toml_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -262,6 +263,39 @@ world"""`,
|
|||||||
A = {isinline = 'yes'}
|
A = {isinline = 'yes'}
|
||||||
[B]
|
[B]
|
||||||
isinline = 'no'
|
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)
|
err = toml.Unmarshal(b, &defaultMap)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// checks that the TablesInline mode generates valid,
|
testWithAllFlags(t, func(t *testing.T, flags int) {
|
||||||
// equivalent TOML
|
|
||||||
t.Run("tables inline", func(t *testing.T) {
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
enc := toml.NewEncoder(&buf)
|
enc := toml.NewEncoder(&buf)
|
||||||
enc.SetTablesInline(true)
|
setFlags(enc, flags)
|
||||||
|
|
||||||
err := enc.Encode(e.v)
|
err := enc.Encode(e.v)
|
||||||
require.NoError(t, err)
|
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) {
|
func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cutset := "\n"
|
cutset := "\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user