# interpres A TOML 1.1 parser and encoder for Go, written with the standard library alone. `interpres` (Latin for *interpreter*) gives zero-dependency programs an `encoding/json`-style API for reading and writing TOML, and passes the entire official [toml-test](https://github.com/toml-lang/toml-test) suite: 214 valid, 467 invalid and 214 encoder cases, zero failures. ## Features - **Full TOML 1.1**: bare, quoted and dotted keys; tables and arrays of tables; basic and literal strings including multiline, with the 1.1 `\e` and `\xHH` escapes; integers in the four radixes with `_` separators; floats with exponents, `inf` and `nan`; booleans; the four date-time kinds, seconds optional as of 1.1; arrays and inline tables, multi-line as of 1.1. - **Decoding and encoding**: `Parse` for an untyped tree, `Unmarshal` and `Marshal` for structs and maps, mirroring `encoding/json`. - **Strict decoding**: `RejectUnknownFields(true)` rejects keys that match no destination field, at every struct depth. - **Custom types**: `Marshaler` and `Unmarshaler` let a type control its own TOML representation in both directions, and `encoding.TextMarshaler` and `TextUnmarshaler` are honoured by default, so `net.IP`, `time.Duration` and user types with text methods need no configuration. - **Cancellation**: every entry point has a `*Context` sibling that honours a `context.Context`. - **Ordered documents**: `Parse` gives a `*Document` that keeps the key order, tells an inline table from a header one, and carries the comments; `ParseMap` gives the plain `map[string]any` tree. - **Configurable emission**: `Marshal` options for declaration-order output, omitting empty arrays, literal multiline strings, and inlining small sub-tables. ## Install As a library: ```sh go get sourcedock.dev/petrbalvin/interpres/v2 ``` Requires Go 1.27.1 or newer. The module imports only the standard library. The module is public and resolves through proxy.golang.org and sum.golang.org like any other; no GOPROXY or GOPRIVATE setup is needed to fetch it. A machine that sets `GOPRIVATE=sourcedock.dev` fetches directly from the forge instead, which skips the proxy and the checksum database. ## Quick start ```sh git clone https://sourcedock.dev/petrbalvin/interpres.git cd interpres just example ``` `just example` runs the tour in `examples/basic`: it decodes an embedded document into a struct, prints it, and re-encodes it under both layouts. ## Usage ### Decode into a struct ```go type Config struct { Title string `toml:"title"` Server struct { Host string `toml:"host"` Port int `toml:"port"` } `toml:"server"` } var cfg Config err := interpres.Unmarshal(data, &cfg) ``` Fields match by the `toml:"name"` tag, or by the lower-cased field name when no tag is present; `toml:"-"` skips a field. `Parse` returns a `*Document` that also carries the key order and the comments, `ParseMap` returns the plain `map[string]any` tree, and `UnmarshalContext` accepts a context. ### Encode from a struct ```go out, err := interpres.Marshal(cfg) ``` writes: ```toml title = "example" [server] host = "127.0.0.1" port = 9090 ``` Tables are laid out scalars first, then sub-tables, then arrays of tables, which is the layout that re-parses to the same tree. ### Strict decoding ```go err := interpres.Unmarshal(data, &cfg, interpres.RejectUnknownFields(true)) ``` A key with no matching field becomes an error instead of a silent drop. ### Custom types ```go type Port int func (p Port) MarshalTOML() (any, error) { return int64(p), nil } type IP struct{ net.IP } func (ip *IP) UnmarshalTOML(data any) error { s, ok := data.(string) if !ok { return fmt.Errorf("ip: not a string") } ip.IP = net.ParseIP(s) return nil } ``` The value `MarshalTOML` returns is encoded in place of the receiver; `UnmarshalTOML` receives the parsed value verbatim. ### Options ```go out, err := interpres.Marshal(cfg, interpres.Layout(interpres.LayoutKindDeclaration), // preserve declaration order interpres.OmitEmptyArrays(true), // skip empty scalar arrays interpres.LiteralMultiline(80), // long multi-line strings as literal blocks ) ``` The decode and encode calls take variadic options, the shape encoding/json/v2 uses for its own. `UnmarshalRead(r, v, opts...)` and `MarshalWrite(w, v, opts...)` are the streaming forms. ### Cancellation ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() out, err := interpres.MarshalContext(ctx, cfg) ``` `ParseContext`, `UnmarshalContext` and `MarshalContext` follow the same pattern. The full rules for field matching, numeric conversion and emission live in [docs/API.md](docs/API.md). ## Development ```sh just build # compile bin/interpres-decode just test # the suite, with the 80 percent coverage floor just toml-test # the official compliance suite, needs toml-test on PATH just fmt # gofmt in place ``` See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full workflow, and [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute. ## Documentation - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md): components and data flow - [docs/API.md](docs/API.md): the API reference, decoding and encoding rules - [docs/CLI.md](docs/CLI.md): the interpres-decode adapter and validator ## Licence MIT. See [LICENSE](LICENSE). Copyright © 2026 [Petr Balvín](https://petrbalvin.org)