209 lines
6.1 KiB
Markdown
209 lines
6.1 KiB
Markdown
# interpres
|
|
|
|
A dependency-free **TOML 1.0 parser for Go** — standard library only, no
|
|
third-party modules. Passes the entire
|
|
[toml-test](https://github.com/toml-lang/toml-test) suite (185 valid and 371
|
|
invalid cases).
|
|
|
|
`interpres` (Latin for *interpreter*) reads TOML documents into Go values with a
|
|
small, `encoding/json`-style API. It is built for embedding in zero-dependency
|
|
Go programs that still want their configuration in TOML.
|
|
|
|
## Install
|
|
|
|
```sh
|
|
go get codeberg.org/petrbalvin/interpres
|
|
```
|
|
|
|
Requires Go 1.26 or newer. The module imports only the standard library.
|
|
|
|
## Usage
|
|
|
|
### Decode into a struct
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"codeberg.org/petrbalvin/interpres"
|
|
)
|
|
|
|
type Config struct {
|
|
Title string `toml:"title"`
|
|
Server struct {
|
|
Host string `toml:"host"`
|
|
Port int `toml:"port"`
|
|
} `toml:"server"`
|
|
}
|
|
|
|
func main() {
|
|
data := []byte(`
|
|
title = "example"
|
|
|
|
[server]
|
|
host = "127.0.0.1"
|
|
port = 9090
|
|
`)
|
|
|
|
var cfg Config
|
|
if err := interpres.Unmarshal(data, &cfg); err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("%+v\n", cfg)
|
|
}
|
|
```
|
|
|
|
### Encode from a struct
|
|
|
|
```go
|
|
out, err := interpres.Marshal(cfg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(string(out))
|
|
```
|
|
|
|
writes the same struct back to TOML:
|
|
|
|
```toml
|
|
title = "example"
|
|
|
|
[server]
|
|
host = "127.0.0.1"
|
|
port = 9090
|
|
```
|
|
|
|
### Untyped tree
|
|
|
|
```go
|
|
tree, err := interpres.Parse(data) // []byte → map[string]any
|
|
out, err := interpres.Marshal(tree) // map[string]any → []byte
|
|
```
|
|
|
|
### Custom encoding
|
|
|
|
Types that need a non-default TOML shape can implement `Marshaler`:
|
|
|
|
```go
|
|
type Port int
|
|
|
|
func (p Port) MarshalTOML() (any, error) {
|
|
return int64(p), nil
|
|
}
|
|
```
|
|
|
|
The returned value is encoded just like any other value passed to `Marshal`,
|
|
which means it can be any type `Marshal` itself understands.
|
|
|
|
### Strict decoding
|
|
|
|
Reject keys that have no matching struct field — useful for catching typos in
|
|
configuration files:
|
|
|
|
```go
|
|
err := interpres.NewDecoder().
|
|
DisallowUnknownFields().
|
|
Decode(data, &cfg)
|
|
```
|
|
|
|
## Struct mapping
|
|
|
|
Fields are matched to TOML keys by the `toml:"name"` tag, or by a
|
|
case-insensitive match on the field name when no tag is present. A tag of `"-"`
|
|
skips the field.
|
|
|
|
| TOML value | Go type (untyped tree) |
|
|
|------------|------------------------|
|
|
| string | `string` |
|
|
| integer | `int64` |
|
|
| float | `float64` |
|
|
| boolean | `bool` |
|
|
| offset date-time | `time.Time` |
|
|
| local date-time | `LocalDateTime` |
|
|
| local date | `LocalDate` |
|
|
| local time | `LocalTime` |
|
|
| array | `[]any` |
|
|
| table / inline table | `map[string]any` |
|
|
| array of tables | `[]map[string]any` |
|
|
|
|
When decoding into a struct, these map onto the destination's concrete types
|
|
(any integer/unsigned/float width, slices, nested structs, and `map[string]T`).
|
|
|
|
## Encoding
|
|
|
|
`Marshal` produces valid TOML 1.0 from any `struct` or `map[string]V` value,
|
|
applying these rules:
|
|
|
|
- The top-level value must be a struct or `map[string]V`. Pointers are
|
|
followed; a nil top-level pointer is an error.
|
|
- Struct fields are matched by `toml:"name"` tag (case-insensitive fallback to
|
|
field name; `toml:"-"` skips). Anonymous (embedded) fields without a tag
|
|
are inlined.
|
|
- Maps emit keys in sorted order for deterministic output.
|
|
- **Output is grouped by kind at every level**: scalars come first, then
|
|
sub-tables, then arrays of tables. Within each group the order matches
|
|
struct field declaration order (or, for maps, sorted key order). This means
|
|
in TOML terms a struct that mixes scalars and sub-tables always lays out
|
|
scalars at the top of the section followed by the table headers — there is
|
|
no way to interleave them and still produce a document that re-parses to
|
|
the same tree.
|
|
- Slices and arrays of structs or maps become `[[a]]` arrays of tables. Empty
|
|
or nil arrays of tables are omitted (TOML forbids an empty `[[a]]`); empty
|
|
arrays of scalars emit as `key = []`.
|
|
- Other slices and arrays become TOML arrays, including mixed-type `[]any`.
|
|
- Scalars map to TOML scalars: bool, int64, float64, string, `time.Time`
|
|
(offset date-time), and the local variants (`LocalDateTime`, `LocalDate`,
|
|
`LocalTime`).
|
|
- Strings are always emitted as basic `"..."` strings with the escapes TOML
|
|
requires (`\"`, `\\`, control characters as `\uXXXX`).
|
|
- Keys are emitted as bare keys when they match `[A-Za-z0-9_-]+`, quoted
|
|
otherwise.
|
|
- Floats always carry a `.` or an exponent (so `1` is always emitted as
|
|
`1.0`), which preserves the distinction between an integer-typed value and
|
|
a float-typed value across a `Marshal` → `Parse` round-trip.
|
|
- Values implementing `Marshaler` are encoded by calling `MarshalTOML` and
|
|
using its result.
|
|
- `nil` pointer fields are omitted.
|
|
|
|
`Marshal` cannot encode cyclic data structures. The output is not guaranteed
|
|
to be byte-identical to any TOML document that produced the input value:
|
|
comments, whitespace, key order (for maps), the choice between `[table]`
|
|
headers and inline tables, and the basic-vs-literal quoting style are not
|
|
preserved. The emitted document is, however, guaranteed to be re-parsable by
|
|
`interpres.Parse` back into an equivalent value tree.
|
|
|
|
## TOML 1.0 support
|
|
|
|
`interpres` implements the full [TOML 1.0](https://toml.io/en/v1.0.0) grammar:
|
|
|
|
- Comments, and bare / quoted / dotted keys.
|
|
- Tables `[a.b]` and arrays of tables `[[a]]`.
|
|
- Basic and literal strings, including multiline (`"""` / `'''`) with escape
|
|
sequences (`\n`, `\t`, `\uXXXX`, `\UXXXXXXXX`, line-ending backslash).
|
|
- Integers in decimal, hex (`0x`), octal (`0o`), and binary (`0b`) with `_`
|
|
separators; floats with exponents and `inf` / `nan`.
|
|
- Booleans, offset/local date-times, dates, and times.
|
|
- Arrays (multiline, mixed types) and inline tables.
|
|
|
|
Invalid documents are rejected per the specification (malformed numbers and
|
|
floats, control characters, non-UTF-8 input, out-of-range escapes, table and
|
|
inline-table redefinitions, and more).
|
|
|
|
## Compliance
|
|
|
|
The parser is verified against the official
|
|
[toml-test](https://github.com/toml-lang/toml-test) suite via
|
|
`cmd/interpres-decode`:
|
|
|
|
```sh
|
|
go build -o interpres-decode ./cmd/interpres-decode
|
|
toml-test ./interpres-decode
|
|
```
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|
|
Copyright © 2026 [Petr Balvín](https://petrbalvin.org)
|