143 lines
4.8 KiB
Go
143 lines
4.8 KiB
Go
// Package interpres is a dependency-free TOML parser for Go.
|
|
//
|
|
// interpres reads and writes TOML documents using only the standard library.
|
|
// It exposes a small, encoding/json-style API:
|
|
//
|
|
// var cfg Config
|
|
// err := interpres.Unmarshal(data, &cfg)
|
|
//
|
|
// out, err := interpres.Marshal(cfg)
|
|
//
|
|
// or, for an untyped tree:
|
|
//
|
|
// tree, err := interpres.Parse(data)
|
|
//
|
|
// A Decoder allows strict decoding that rejects keys without a matching
|
|
// struct field, mirroring (*json.Decoder).DisallowUnknownFields.
|
|
package interpres
|
|
|
|
import (
|
|
"fmt"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// A SyntaxError describes a malformed TOML document, including the 1-based
|
|
// line on which the problem was detected.
|
|
type SyntaxError struct {
|
|
Line int
|
|
Msg string
|
|
}
|
|
|
|
func (e *SyntaxError) Error() string {
|
|
return fmt.Sprintf("interpres: line %d: %s", e.Line, e.Msg)
|
|
}
|
|
|
|
// Parse decodes a TOML document into a nested map[string]any.
|
|
//
|
|
// Values are mapped to Go types as follows: strings to string, integers to
|
|
// int64, floats to float64, booleans to bool, date-times to time.Time, arrays
|
|
// to []any, and tables (including inline tables) to map[string]any.
|
|
func Parse(data []byte) (map[string]any, error) {
|
|
if !utf8.Valid(data) {
|
|
return nil, &SyntaxError{Line: 1, Msg: "input is not valid UTF-8"}
|
|
}
|
|
p := &parser{src: []rune(string(data)), line: 1}
|
|
return p.parse()
|
|
}
|
|
|
|
// Unmarshal parses a TOML document and stores the result in the value pointed
|
|
// to by v. v is typically a pointer to a struct or to a map[string]any.
|
|
//
|
|
// Struct 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.
|
|
func Unmarshal(data []byte, v any) error {
|
|
tree, err := Parse(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return newDecoder().decode(tree, v)
|
|
}
|
|
|
|
// A Decoder decodes a TOML document into a Go value with configurable
|
|
// strictness.
|
|
type Decoder struct {
|
|
disallowUnknown bool
|
|
}
|
|
|
|
// NewDecoder returns a Decoder.
|
|
func NewDecoder() *Decoder { return &Decoder{} }
|
|
|
|
// DisallowUnknownFields causes Decode to return an error when the document
|
|
// contains a key with no matching destination struct field.
|
|
func (d *Decoder) DisallowUnknownFields() *Decoder {
|
|
d.disallowUnknown = true
|
|
return d
|
|
}
|
|
|
|
// Decode parses data and stores the result in the value pointed to by v,
|
|
// honouring the decoder's strictness settings.
|
|
func (d *Decoder) Decode(data []byte, v any) error {
|
|
tree, err := Parse(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dec := newDecoder()
|
|
dec.disallowUnknown = d.disallowUnknown
|
|
return dec.decode(tree, v)
|
|
}
|
|
|
|
// Marshaler is the interface implemented by types that can produce a custom
|
|
// TOML representation of themselves. MarshalTOML returns a value that Marshal
|
|
// then encodes as if the returned value had been passed in its place — useful
|
|
// for emitting a Go type as a different TOML shape (for example, a struct as
|
|
// an inline table or a primitive alias as a richer value).
|
|
type Marshaler interface {
|
|
MarshalTOML() (any, error)
|
|
}
|
|
|
|
// Marshal returns the TOML 1.0 encoding of v.
|
|
//
|
|
// Marshal traverses v using reflection and applies the following rules:
|
|
//
|
|
// - The top-level value must be a struct or a 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; `-` skips). Anonymous (embedded) fields without
|
|
// a tag are inlined.
|
|
// - Maps use sorted keys for deterministic output.
|
|
// - Slices and arrays of structs or maps become TOML arrays of tables; a
|
|
// nil or empty array of tables is omitted (TOML forbids an empty `[[a]]`),
|
|
// while other empty arrays emit as `key = []`.
|
|
// - Other slices and arrays become TOML arrays.
|
|
// - Scalars encode as TOML scalars: bool, int64, float64, string, time.Time
|
|
// (offset date-time), and LocalDateTime/LocalDate/LocalTime (local
|
|
// variants).
|
|
// - Values implementing Marshaler are encoded by calling MarshalTOML and
|
|
// using its result.
|
|
// - nil pointer fields are omitted.
|
|
//
|
|
// Marshal cannot encode cyclic data structures — passing one will loop until
|
|
// the stack overflows. The output is not guaranteed to be byte-identical to
|
|
// the input that produced v: comments, whitespace, key order (for maps),
|
|
// string quoting style, and the choice between `[table]` headers and inline
|
|
// tables are not preserved.
|
|
func Marshal(v any) ([]byte, error) {
|
|
return NewEncoder().Marshal(v)
|
|
}
|
|
|
|
// An Encoder encodes Go values into TOML.
|
|
type Encoder struct{}
|
|
|
|
// NewEncoder returns an Encoder.
|
|
func NewEncoder() *Encoder { return &Encoder{} }
|
|
|
|
// Marshal encodes v to TOML bytes. It is equivalent to calling Marshal with v.
|
|
func (e *Encoder) Marshal(v any) ([]byte, error) {
|
|
enc := newEncoder()
|
|
if err := enc.encode(v); err != nil {
|
|
return nil, err
|
|
}
|
|
return enc.bytes(), nil
|
|
}
|