Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -46,6 +46,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
(10000 levels, which no hand-written document approaches): a document that
|
(10000 levels, which no hand-written document approaches): a document that
|
||||||
nests arrays or inline tables deeper used to run the stack out and is now
|
nests arrays or inline tables deeper used to run the stack out and is now
|
||||||
rejected with a `SyntaxError` naming the limit.
|
rejected with a `SyntaxError` naming the limit.
|
||||||
|
- A TOML array decodes into a Go fixed-size array, `[N]T`, where only a slice
|
||||||
|
was accepted before; the encoder could already encode one. A length mismatch
|
||||||
|
is an error wrapped with the key path.
|
||||||
|
- `MarshalAppend(buf, v)` appends the TOML encoding of v to buf and returns
|
||||||
|
the extended buffer, the shape `json.MarshalAppend` has.
|
||||||
- `ParseFile(path)` reads the file and parses it into a `Document`, with the
|
- `ParseFile(path)` reads the file and parses it into a `Document`, with the
|
||||||
file name at the front of every error it returns, read failure and parse
|
file name at the front of every error it returns, read failure and parse
|
||||||
failure alike. `Valid(data)` reports whether a document parses, nil on
|
failure alike. `Valid(data)` reports whether a document parses, nil on
|
||||||
|
|||||||
@@ -309,31 +309,58 @@ func (d *decoder) assignMap(tbl map[string]any, dst reflect.Value) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) assignSlice(items []any, dst reflect.Value) error {
|
func (d *decoder) assignSlice(items []any, dst reflect.Value) error {
|
||||||
if dst.Kind() != reflect.Slice {
|
switch dst.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
if err := d.assign(item, out.Index(i)); err != nil {
|
||||||
|
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst.Set(out)
|
||||||
|
return nil
|
||||||
|
case reflect.Array:
|
||||||
|
// A fixed-size array takes the elements in place; a length mismatch is
|
||||||
|
// the error, because a TOML array carries no way to name a default for
|
||||||
|
// the elements it is short of, and the surplus has nowhere to go.
|
||||||
|
if dst.Len() != len(items) {
|
||||||
|
return fmt.Errorf("interpres: cannot assign %d elements to %s", len(items), dst.Type())
|
||||||
|
}
|
||||||
|
for i, item := range items {
|
||||||
|
if err := d.assign(item, dst.Index(i)); err != nil {
|
||||||
|
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
return fmt.Errorf("interpres: cannot assign array to %s", dst.Type())
|
return fmt.Errorf("interpres: cannot assign array to %s", dst.Type())
|
||||||
}
|
}
|
||||||
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
if err := d.assign(item, out.Index(i)); err != nil {
|
|
||||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dst.Set(out)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) assignTableSlice(items []map[string]any, dst reflect.Value) error {
|
func (d *decoder) assignTableSlice(items []map[string]any, dst reflect.Value) error {
|
||||||
if dst.Kind() != reflect.Slice {
|
switch dst.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
if err := d.assign(item, out.Index(i)); err != nil {
|
||||||
|
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst.Set(out)
|
||||||
|
return nil
|
||||||
|
case reflect.Array:
|
||||||
|
if dst.Len() != len(items) {
|
||||||
|
return fmt.Errorf("interpres: cannot assign %d elements to %s", len(items), dst.Type())
|
||||||
|
}
|
||||||
|
for i, item := range items {
|
||||||
|
if err := d.assign(item, dst.Index(i)); err != nil {
|
||||||
|
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
return fmt.Errorf("interpres: cannot assign array of tables to %s", dst.Type())
|
return fmt.Errorf("interpres: cannot assign array of tables to %s", dst.Type())
|
||||||
}
|
}
|
||||||
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
if err := d.assign(item, out.Index(i)); err != nil {
|
|
||||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dst.Set(out)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- low-level setters -----------------------------------------------------
|
// --- low-level setters -----------------------------------------------------
|
||||||
|
|||||||
@@ -1273,3 +1273,46 @@ func TestSourceLineEdgePositions(t *testing.T) {
|
|||||||
t.Errorf("SourceLine(offset past end) =\n%q\nwant:\n%q", got, want)
|
t.Errorf("SourceLine(offset past end) =\n%q\nwant:\n%q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodeFixedArray(t *testing.T) {
|
||||||
|
t.Run("value array", func(t *testing.T) {
|
||||||
|
var cfg struct {
|
||||||
|
Ports [2]int `toml:"ports"`
|
||||||
|
Label [2]string `toml:"label"`
|
||||||
|
Grid [2][2]int64 `toml:"grid"`
|
||||||
|
}
|
||||||
|
in := []byte("ports = [8080, 9090]\nlabel = [\"a\", \"b\"]\ngrid = [[1, 2], [3, 4]]\n")
|
||||||
|
if err := Unmarshal(in, &cfg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.Ports != [2]int{8080, 9090} || cfg.Label != [2]string{"a", "b"} || cfg.Grid != [2][2]int64{{1, 2}, {3, 4}} {
|
||||||
|
t.Errorf("decoded %+v", cfg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("array of tables", func(t *testing.T) {
|
||||||
|
type Item struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
Qty int `toml:"qty"`
|
||||||
|
}
|
||||||
|
var cfg struct {
|
||||||
|
Items [2]Item `toml:"items"`
|
||||||
|
}
|
||||||
|
in := []byte("[[items]]\nname = \"a\"\nqty = 1\n[[items]]\nname = \"b\"\nqty = 2\n")
|
||||||
|
if err := Unmarshal(in, &cfg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.Items != [2]Item{{"a", 1}, {"b", 2}} {
|
||||||
|
t.Errorf("decoded %+v", cfg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("a length mismatch is an error", func(t *testing.T) {
|
||||||
|
var cfg struct {
|
||||||
|
Ports [3]int `toml:"ports"`
|
||||||
|
}
|
||||||
|
err := Unmarshal([]byte("ports = [8080, 9090]\n"), &cfg)
|
||||||
|
want := "ports: interpres: cannot assign 2 elements to [3]int"
|
||||||
|
if err == nil || err.Error() != want {
|
||||||
|
t.Errorf("err = %v, want %q", err, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+10
@@ -158,6 +158,11 @@ out, err := interpres.Marshal(cfg)
|
|||||||
The cancellable variant of `Marshal`. The context is checked before any work
|
The cancellable variant of `Marshal`. The context is checked before any work
|
||||||
and every 64 fields during the reflection walk.
|
and every 64 fields during the reflection walk.
|
||||||
|
|
||||||
|
### `func MarshalAppend(buf []byte, v any) ([]byte, error)`
|
||||||
|
|
||||||
|
Appends the TOML encoding of `v` to `buf` and returns the extended buffer, the
|
||||||
|
shape `json.MarshalAppend` has. A failed encoding leaves `buf` untouched.
|
||||||
|
|
||||||
## Decoding
|
## Decoding
|
||||||
|
|
||||||
### Value mapping
|
### Value mapping
|
||||||
@@ -282,6 +287,11 @@ destination is a slice, each element decodes into the slice's element type
|
|||||||
(`[]struct` or `[]map[string]V`); a mismatch on one element surfaces as an
|
(`[]struct` or `[]map[string]V`); a mismatch on one element surfaces as an
|
||||||
error wrapped with `[i]:` and the element index.
|
error wrapped with `[i]:` and the element index.
|
||||||
|
|
||||||
|
A value array also decodes into a fixed-size array, `[N]T`, the mirror of the
|
||||||
|
encoder's ability to encode one. The element count has to match: an array
|
||||||
|
whose length differs from `N` is an error, `interpres: cannot assign 2
|
||||||
|
elements to [3]int`, wrapped with the key path.
|
||||||
|
|
||||||
### Custom decoding: `Unmarshaler`
|
### Custom decoding: `Unmarshaler`
|
||||||
|
|
||||||
A type that wants full control of its decode implements:
|
A type that wants full control of its decode implements:
|
||||||
|
|||||||
@@ -1998,3 +1998,29 @@ func TestMarshalNumber(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalAppend(t *testing.T) {
|
||||||
|
buf := []byte("preamble\n")
|
||||||
|
out, err := MarshalAppend(buf, map[string]any{"a": int64(1)})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := "preamble\na = 1\n"
|
||||||
|
if string(out) != want {
|
||||||
|
t.Errorf("output %q, want %q", out, want)
|
||||||
|
}
|
||||||
|
if &out[0] != &buf[0] {
|
||||||
|
t.Log("append reallocated; capacity differed")
|
||||||
|
}
|
||||||
|
out2, err := MarshalAppend(out, map[string]any{"b": true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(out2) != want+"b = true\n" {
|
||||||
|
t.Errorf("second append %q", out2)
|
||||||
|
}
|
||||||
|
buf = []byte("keep\n")
|
||||||
|
if out3, err := MarshalAppend(buf, Document{}); err == nil {
|
||||||
|
t.Errorf("MarshalAppend with an unencodable value = %q, want an error", out3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -399,6 +399,17 @@ func Marshal(v any) ([]byte, error) {
|
|||||||
return MarshalContext(context.Background(), v)
|
return MarshalContext(context.Background(), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalAppend appends the TOML encoding of v to buf and returns the extended
|
||||||
|
// buffer, the shape json.MarshalAppend has. A failed encoding leaves buf
|
||||||
|
// untouched and comes back with a nil slice.
|
||||||
|
func MarshalAppend(buf []byte, v any) ([]byte, error) {
|
||||||
|
out, err := Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(buf, out...), nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalContext is the cancellable variant of Marshal.
|
// MarshalContext is the cancellable variant of Marshal.
|
||||||
func MarshalContext(ctx context.Context, v any) ([]byte, error) {
|
func MarshalContext(ctx context.Context, v any) ([]byte, error) {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user