185 lines
5.5 KiB
Markdown
185 lines
5.5 KiB
Markdown
# 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**: `Unmarshal` and `Marshal` for structs and maps,
|
|
mirroring `encoding/json`; `Parse` and `ParseMap` for the document with its
|
|
key order and the plain untyped tree.
|
|
- **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**: the parse, decode and marshal entries have `*Context`
|
|
siblings that honour a `context.Context`, checked while the work runs.
|
|
- **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,
|
|
also shipped as the manual page `man/interpres-decode.1`
|
|
|
|
## Licence
|
|
|
|
MIT. See [LICENSE](LICENSE).
|
|
|
|
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|