docs: add godoc examples, extend the basic example and map encoding/json
Test / test (push) Successful in 1m47s
Test / test (push) Successful in 1m47s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+23
@@ -797,6 +797,29 @@ sequenceDiagram
|
|||||||
Marshal-->>Caller: bytes, error
|
Marshal-->>Caller: bytes, error
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Coming from encoding/json
|
||||||
|
|
||||||
|
The API follows the shapes encoding/json made familiar, with the differences
|
||||||
|
TOML asks for:
|
||||||
|
|
||||||
|
| encoding/json | interpres | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `json.Unmarshal(data, v)` | `Unmarshal(data, v)` | the same shape; the value mapping is TOML's |
|
||||||
|
| `json.Marshal(v)` | `Marshal(v)` | the same shape; the output is TOML 1.1 |
|
||||||
|
| `json.MarshalAppend(buf, v)` | `MarshalAppend(buf, v)` | the same shape |
|
||||||
|
| `(*json.Decoder).DisallowUnknownFields` | `(*Decoder).DisallowUnknownFields` | the same effect; the one-shot form is `UnmarshalWithOptions` |
|
||||||
|
| `json.Number`, `(*json.Decoder).UseNumber` | `Number`, `(*Decoder).UseNumber` | the TOML literal carries its radix and separators, so `0x1f` stays `0x1f` |
|
||||||
|
| `json.MarshalIndent` | none | TOML is the presentation format; the `-json` mode of interpres-decode prints plain JSON |
|
||||||
|
| tag `json:"name,omitempty"` | tag `toml:"name,omitempty"` | the empty-value rules match encoding/json as of 2.0 |
|
||||||
|
| tag `json:"name,omitzero"` | tag `toml:"name,omitzero"` | the same, `IsZero()` honoured |
|
||||||
|
| tag `json:"name,inline"` (v2) | tag `toml:"name,inline"` | forces the inline table form on encode |
|
||||||
|
| `json.Marshaler` (`MarshalJSON`) | `Marshaler` (`MarshalTOML`) | the TOML method returns a value the encoder renders, not bytes |
|
||||||
|
| `json.Unmarshaler` (`UnmarshalJSON`) | `Unmarshaler` (`UnmarshalTOML`) | the data arrives decoded, not as bytes |
|
||||||
|
| `encoding.TextMarshaler`, `TextUnmarshaler` | honoured, the same | a type that renders itself as text becomes a TOML string, both ways |
|
||||||
|
| `*json.UnmarshalTypeError` | `*DecodeError` | the path is segments with a `String()` renderer, not a dotted string |
|
||||||
|
| `*json.SyntaxError` | `*SyntaxError` | the TOML error adds the byte `Offset` and the `Column` to the line |
|
||||||
|
| context support | `*Context` variants of every entry point | encoding/json has none |
|
||||||
|
|
||||||
## Types
|
## Types
|
||||||
|
|
||||||
### `type SyntaxError struct{ Line, Offset, Column int; Msg string }`
|
### `type SyntaxError struct{ Line, Offset, Column int; Msg string }`
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package interpres_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/interpres/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleParse() {
|
||||||
|
const doc = `
|
||||||
|
title = "interpres"
|
||||||
|
|
||||||
|
[server]
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 9090
|
||||||
|
`
|
||||||
|
d, err := interpres.Parse([]byte(doc))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, key := range d.Root().Keys() { // written order, not sorted
|
||||||
|
entry, _ := d.Root().Get(key)
|
||||||
|
fmt.Println(key, "=", entry.Value())
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// title = interpres
|
||||||
|
// server = map[host:127.0.0.1 port:9090]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleUnmarshal() {
|
||||||
|
type Config struct {
|
||||||
|
Host string `toml:"host"`
|
||||||
|
Port int `toml:"port"`
|
||||||
|
}
|
||||||
|
var cfg Config
|
||||||
|
err := interpres.Unmarshal([]byte("host = \"db\"\nport = 5432\n"), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(cfg.Host, cfg.Port)
|
||||||
|
// Output: db 5432
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleMarshal() {
|
||||||
|
type Server struct {
|
||||||
|
Host string `toml:"host"`
|
||||||
|
Port int `toml:"port"`
|
||||||
|
}
|
||||||
|
type Config struct {
|
||||||
|
Title string `toml:"title"`
|
||||||
|
Server Server `toml:"server"`
|
||||||
|
}
|
||||||
|
out, err := interpres.Marshal(Config{
|
||||||
|
Title: "demo",
|
||||||
|
Server: Server{Host: "127.0.0.1", Port: 9090},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", out)
|
||||||
|
// Output:
|
||||||
|
// title = "demo"
|
||||||
|
//
|
||||||
|
// [server]
|
||||||
|
// host = "127.0.0.1"
|
||||||
|
// port = 9090
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDecoder() {
|
||||||
|
var tree map[string]any
|
||||||
|
err := interpres.NewDecoder().
|
||||||
|
DisallowUnknownFields().
|
||||||
|
UseNumber().
|
||||||
|
Decode([]byte("rate = 1_000\n"), &tree)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(tree["rate"], string(tree["rate"].(interpres.Number)))
|
||||||
|
// Output: 1_000 1_000
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleEncoder() {
|
||||||
|
type Config struct {
|
||||||
|
Title string `toml:"title"`
|
||||||
|
Extras map[string]string `toml:"extras,inline"`
|
||||||
|
}
|
||||||
|
out, err := interpres.NewEncoder().
|
||||||
|
Layout(interpres.LayoutKindDeclaration).
|
||||||
|
LiteralMultiline(80).
|
||||||
|
InlineTables(40).
|
||||||
|
Marshal(Config{Title: "demo", Extras: map[string]string{"b": "two", "a": "one"}})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", out)
|
||||||
|
// Output:
|
||||||
|
// title = "demo"
|
||||||
|
// extras = {a = "one", b = "two"}
|
||||||
|
}
|
||||||
+33
-1
@@ -8,6 +8,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -39,13 +40,16 @@ admin = false
|
|||||||
|
|
||||||
// Config mirrors the document above. The Server field is a named struct so
|
// Config mirrors the document above. The Server field is a named struct so
|
||||||
// the reader sees explicit subtable boundaries; Users is a slice of named
|
// the reader sees explicit subtable boundaries; Users is a slice of named
|
||||||
// structs so the array-of-tables path is exercised.
|
// structs so the array-of-tables path is exercised. Retries carries the
|
||||||
|
// `omitzero` tag option: a zero value of the field's type drops from the
|
||||||
|
// output, and a `time.Duration` zero is zero nanoseconds.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Title string `toml:"title"`
|
Title string `toml:"title"`
|
||||||
Launched time.Time `toml:"launched"`
|
Launched time.Time `toml:"launched"`
|
||||||
Debug bool `toml:"debug"`
|
Debug bool `toml:"debug"`
|
||||||
Server Server `toml:"server"`
|
Server Server `toml:"server"`
|
||||||
Users []User `toml:"users"`
|
Users []User `toml:"users"`
|
||||||
|
Retries time.Duration `toml:"retries,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -147,5 +151,33 @@ func Run(stdout, stderr io.Writer) int {
|
|||||||
fmt.Fprintf(stdout, "\n--- round-trip --- ok (title=%q, users=%d)\n",
|
fmt.Fprintf(stdout, "\n--- round-trip --- ok (title=%q, users=%d)\n",
|
||||||
roundTripped.Title, len(roundTripped.Users))
|
roundTripped.Title, len(roundTripped.Users))
|
||||||
|
|
||||||
|
// Typed errors: a decode failure names the key path it failed at, and
|
||||||
|
// errors.AsType reaches the DecodeError to read the path and the cause
|
||||||
|
// separately, without parsing the message text.
|
||||||
|
bad := []byte("[[users]]\nname = \"x\"\nadmin = \"not-a-bool\"\n")
|
||||||
|
var badCfg Config
|
||||||
|
err = interpres.Unmarshal(bad, &badCfg)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Fprintln(stderr, "expected a decode error")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if de, ok := errors.AsType[*interpres.DecodeError](err); ok {
|
||||||
|
fmt.Fprintf(stdout, "\n--- typed error --- path %s: %v\n", de.Path.String(), de.Err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(stderr, "expected a DecodeError")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// omitzero: the retries field carries the tag option and a zero duration,
|
||||||
|
// so the re-encoded config above simply has no retries line. Give it a
|
||||||
|
// value and the line appears.
|
||||||
|
cfg.Retries = 30 * time.Second
|
||||||
|
out3, err := interpres.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(stderr, "marshal:", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stdout, "\n--- omitzero ---\n%s", out3)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user