Files
interpres/README.md
T

183 lines
5.3 KiB
Markdown
Raw Normal View History

2026-08-19 18:44:00 +02:00
# interpres
A TOML 1.1 parser and encoder for Go, written with the standard library
alone. `interpres` (Latin for *interpreter*) gives zero-dependency
2026-09-17 22:06:26 +02:00
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.
2026-08-19 18:44:00 +02:00
## Features
- **Full TOML 1.1**: bare, quoted and dotted keys; tables and arrays of
2026-09-17 22:06:26 +02:00
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.
2026-08-19 18:44:00 +02:00
- **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
2026-08-19 18:44:00 +02:00
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.
2026-08-19 18:44:00 +02:00
- **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,
2026-09-19 12:18:30 +02:00
omitting empty arrays, literal multiline strings, and inlining small
sub-tables.
2026-08-19 18:44:00 +02:00
## Install
As a library:
```sh
2026-09-19 00:14:39 +02:00
go get sourcedock.dev/petrbalvin/interpres/v2
2026-08-19 18:44:00 +02:00
```
2026-09-17 19:41:22 +02:00
Requires Go 1.27.1 or newer. The module imports only the standard library.
2026-08-19 18:44:00 +02:00
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.
2026-08-19 18:44:00 +02:00
## 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.
2026-08-19 18:44:00 +02:00
## 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.
2026-08-19 18:44:00 +02:00
### 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))
2026-08-19 18:44:00 +02:00
```
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
2026-08-19 18:44:00 +02:00
```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
)
2026-08-19 18:44:00 +02:00
```
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.
2026-08-19 18:44:00 +02:00
### 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.
2026-08-19 18:44:00 +02:00
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
2026-08-19 18:44:00 +02:00
## Licence
MIT. See [LICENSE](LICENSE).
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)