petrbalvin 75ade89f34
Test / test (push) Successful in 2m3s
chore: prepare release v2.0.0
2026-09-22 21:45:25 +02:00
2026-09-22 21:45:25 +02:00
2026-09-19 00:14:39 +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 programs an encoding/json-style API for reading and writing TOML, and passes the entire official 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:

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

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

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

out, err := interpres.Marshal(cfg)

writes:

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

err := interpres.Unmarshal(data, &cfg, interpres.RejectUnknownFields(true))

A key with no matching field becomes an error instead of a silent drop.

Custom types

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

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

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.

Development

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 for the full workflow, and CONTRIBUTING.md for how to contribute.

Documentation

  • docs/ARCHITECTURE.md: components and data flow
  • docs/API.md: the API reference, decoding and encoding rules
  • docs/CLI.md: the interpres-decode adapter and validator, also shipped as the manual page man/interpres-decode.1

Licence

MIT. See LICENSE.

Copyright © 2026 Petr Balvín

S
Description
TOML 1.1 parser and encoder in pure Go: an encoding/json-style API, documents that keep key order and comments through a round trip, strict decoding and cancellation; standard library only, passing the entire official toml-test suite.
Readme MIT
2.8 MiB
v2.0.0
Latest
2026-09-22 19:52:42 +00:00
Languages
Go 98.4%
Just 1.2%
Perl 0.4%