// 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 }