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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
### `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"}
|
||||
}
|
||||
+38
-6
@@ -8,6 +8,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -39,13 +40,16 @@ admin = false
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
Title string `toml:"title"`
|
||||
Launched time.Time `toml:"launched"`
|
||||
Debug bool `toml:"debug"`
|
||||
Server Server `toml:"server"`
|
||||
Users []User `toml:"users"`
|
||||
Title string `toml:"title"`
|
||||
Launched time.Time `toml:"launched"`
|
||||
Debug bool `toml:"debug"`
|
||||
Server Server `toml:"server"`
|
||||
Users []User `toml:"users"`
|
||||
Retries time.Duration `toml:"retries,omitzero"`
|
||||
}
|
||||
|
||||
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",
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user