Documentation
¶
Overview ¶
Package tomledit: Zero-dep TOML editing library for Go with comment preservation.
The tree preserves comments, whitespace and formatting. Values can be read, set, deleted and renamed without disturbing unrelated parts of the file. A document is serialized back with Document.Bytes (round-trip fidelity) or reformatted with Document.Format.
Two surfaces, two questions ¶
A document is readable through two surfaces, and which one to use follows from the question being asked.
The SYNTACTIC surface is the AST: Document.Walk, Document.Resolve and the concrete node types. It answers what the file CONTAINS, in the form it was written -- spellings, quoting styles, integer bases, comments, blank lines and the source span of every construct. A value written as an inline table arrives as an *InlineTableNode; one written under a header arrives as that header's children.
The LOGICAL surface is the read-layer: Document.Root returns a Record, whose Entry values are the document's keys in first-appearance order. It answers what the document MEANS -- values, structure and order with the spellings folded away. A dotted key, a [header] table and an inline table are indistinguishable through it, and so is a table only a longer header implies.
Read values through the read-layer or the typed accessors; use the AST to edit, or to inspect how something was written. Reads are spelling-blind; writes are structurally conservative -- a value write touches value fragments and nothing else, and a structural construct changes only through a structural operation or an explicit Document.Delete.
Strictness and diagnostics ¶
Decoding is strict and strictness is the only mode: an unknown key, an unknown table, a value of a refused kind, a value the target cannot hold exactly, and a missing required key are all errors. There is no lenient mode and no option to skip a check.
Every failure that depends on the document -- from parsing, decoding, editing or access alike -- is an *Error carrying a kind, a document path and a position, matchable with errors.Is against the ErrSyntax family of kind sentinels and readable with errors.As. A decode reports every independent violation together as an *Errors aggregate in document order; parsing reports its first failure alone, because a parse cannot continue past a syntax error.
Reading by hand ¶
A file whose rules go beyond its shape is read key by key through a Reader over one table, which records every problem in a Collector: Collector.Read parses a file's bytes into a Reader over its top-level table, the accessors (Reader.Str, Reader.Integer, Reader.Flag, Reader.Tables, Reader.Sub, and the rest) read keys and report a missing key or a value of the wrong type with a fix saying what to write, and Reader.Done reports every key nobody read as unknown, with the kinds a strict decode uses. Collector.Err returns everything recorded, from every file, sorted by file, line, path, and message.
Capabilities ¶
- Lossless round-trip: parse and re-serialize without losing comments or formatting.
- Path-based access: read and write values through paths such as "server.host", "items[0]" and "items[-1]".
- Structural editing: create tables and array-of-tables, reorder children, append to and remove from arrays, rename and delete keys, seed defaults.
- Strict decoding: into Go structs through Unmarshal, Decode, DecodeNode and DecodeOver, or against a hand-built descriptor through Document.Validate and Document.DecodeSpec.
- Diff and merge: compare two documents, or merge one into another.
- File names and writes: ParseWithFileName records a filename for later diagnostics, and Document.RoundTripVerifiedBytes renders only bytes that survive a round trip. The package reads and writes no file itself: the tomlfile subpackage reads a file and parses it, and writes a document atomically through that check.
Index ¶
- Variables
- func Decode[T any](d *Document) (*T, error)
- func DecodeNode[T any](n Node) (*T, error)
- func DecodeOver[T any](d *Document, seed func() T) (*T, []string, error)
- func FormatFloat(f float64) string
- func JoinPath(segs []PathSegment) string
- func PathWithoutIndices(path string) string
- func QuoteKey(s string) string
- func QuoteString(s string) string
- func Unmarshal[T any](data []byte) (*T, error)
- type ArrayNode
- type ArrayTableNode
- type BooleanNode
- func (n *BooleanNode) AsBool() (bool, error)
- func (n *BooleanNode) AsFloat() (float64, error)
- func (n *BooleanNode) AsInt() (int64, error)
- func (n *BooleanNode) AsLocalDate() (LocalDate, error)
- func (n *BooleanNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *BooleanNode) AsLocalTime() (LocalTime, error)
- func (n *BooleanNode) AsString() (string, error)
- func (n *BooleanNode) AsTime() (time.Time, error)
- func (n *BooleanNode) Comment() string
- func (n *BooleanNode) LeadingComments() []string
- func (n *BooleanNode) Raw() []byte
- func (n *BooleanNode) Span() Span
- func (n *BooleanNode) Type() NodeType
- func (n *BooleanNode) Value() any
- type Change
- type ChangeKind
- type Collector
- type CommentNode
- type Cursor
- func (c *Cursor) At(index int) *Cursor
- func (c *Cursor) Bool() (bool, error)
- func (c *Cursor) Err() error
- func (c *Cursor) Float() (float64, error)
- func (c *Cursor) Int() (int64, error)
- func (c *Cursor) Items() iter.Seq2[int, Node]
- func (c *Cursor) Key(name string) *Cursor
- func (c *Cursor) Len() int
- func (c *Cursor) Node() Node
- func (c *Cursor) String() (string, error)
- func (c *Cursor) Time() (time.Time, error)
- type DateTimeNode
- func (n *DateTimeNode) AsBool() (bool, error)
- func (n *DateTimeNode) AsFloat() (float64, error)
- func (n *DateTimeNode) AsInt() (int64, error)
- func (n *DateTimeNode) AsLocalDate() (LocalDate, error)
- func (n *DateTimeNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *DateTimeNode) AsLocalTime() (LocalTime, error)
- func (n *DateTimeNode) AsString() (string, error)
- func (n *DateTimeNode) AsTime() (time.Time, error)
- func (n *DateTimeNode) Comment() string
- func (n *DateTimeNode) LeadingComments() []string
- func (n *DateTimeNode) Raw() []byte
- func (n *DateTimeNode) Span() Span
- func (n *DateTimeNode) Type() NodeType
- func (n *DateTimeNode) Value() any
- type Default
- type Document
- func (d *Document) AppendToArray(path string, value any) error
- func (d *Document) Bytes() []byte
- func (n *Document) Children() []Node
- func (n *Document) Comment() string
- func (d *Document) DecodeSpec(spec *Spec) (map[string]any, error)
- func (d *Document) Delete(path string) error
- func (d *Document) EnsureDefaults(defaults []Default) (added []string, err error)
- func (d *Document) Format(opts ...FormatOption) []byte
- func (d *Document) GetBool(path string) (bool, error)
- func (d *Document) GetComment(path string) (string, error)
- func (d *Document) GetFloat(path string) (float64, error)
- func (d *Document) GetInt(path string) (int64, error)
- func (d *Document) GetLeadingComments(path string) ([]string, error)
- func (d *Document) GetString(path string) (string, error)
- func (d *Document) GetTime(path string) (time.Time, error)
- func (d *Document) Has(path string) bool
- func (d *Document) Key(name string) *Cursor
- func (n *Document) LeadingComments() []string
- func (d *Document) Lookup(path string) (Node, bool)
- func (d *Document) Merge(other *Document) error
- func (d *Document) NewArrayTable(path string) error
- func (d *Document) NewTable(path string) error
- func (d *Document) PermuteChildren(path string, order []int) error
- func (n *Document) Raw() []byte
- func (d *Document) RemoveFromArray(path string, index int) error
- func (d *Document) RenameKey(path string, newKey string) error
- func (d *Document) Resolve(path string) (Node, error)
- func (d *Document) Root() *Record
- func (d *Document) RoundTripVerifiedBytes(file string) ([]byte, error)
- func (d *Document) Set(path string, value any) error
- func (d *Document) SetBools(path string, values []bool) error
- func (d *Document) SetComment(path string, comment string) error
- func (d *Document) SetCreate(path string, value any) error
- func (d *Document) SetCreateBools(path string, values []bool) error
- func (d *Document) SetCreateFloats(path string, values []float64) error
- func (d *Document) SetCreateInlineTable(path string, table InlineTable) error
- func (d *Document) SetCreateInts(path string, values []int64) error
- func (d *Document) SetCreateStrings(path string, values []string) error
- func (d *Document) SetFloats(path string, values []float64) error
- func (d *Document) SetInlineTable(path string, table InlineTable) error
- func (d *Document) SetInts(path string, values []int64) error
- func (d *Document) SetLeadingComments(path string, comments []string) error
- func (d *Document) SetStrings(path string, values []string) error
- func (n *Document) Span() Span
- func (n *Document) Type() NodeType
- func (d *Document) Validate(spec *Spec) error
- func (d *Document) Walk(fn func(path string, node Node) error, mode WalkMode) error
- type Entry
- type EntryKind
- type Error
- type ErrorKind
- type Errors
- type Field
- type FieldKind
- type FloatNode
- func (n *FloatNode) AsBool() (bool, error)
- func (n *FloatNode) AsFloat() (float64, error)
- func (n *FloatNode) AsInt() (int64, error)
- func (n *FloatNode) AsLocalDate() (LocalDate, error)
- func (n *FloatNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *FloatNode) AsLocalTime() (LocalTime, error)
- func (n *FloatNode) AsString() (string, error)
- func (n *FloatNode) AsTime() (time.Time, error)
- func (n *FloatNode) Comment() string
- func (n *FloatNode) LeadingComments() []string
- func (n *FloatNode) Raw() []byte
- func (n *FloatNode) Span() Span
- func (n *FloatNode) Type() NodeType
- func (n *FloatNode) Value() any
- type FormatConfig
- type FormatOption
- type InlineTable
- func (t *InlineTable) Bool(key string, value bool) *InlineTable
- func (t *InlineTable) Bools(key string, values []bool) *InlineTable
- func (t *InlineTable) Float(key string, value float64) *InlineTable
- func (t *InlineTable) Floats(key string, values []float64) *InlineTable
- func (t *InlineTable) Int(key string, value int64) *InlineTable
- func (t *InlineTable) Ints(key string, values []int64) *InlineTable
- func (t *InlineTable) String(key, value string) *InlineTable
- func (t *InlineTable) Strings(key string, values []string) *InlineTable
- func (t *InlineTable) Table(key string, table InlineTable) *InlineTable
- type InlineTableNode
- type IntegerBase
- type IntegerNode
- func (n *IntegerNode) AsBool() (bool, error)
- func (n *IntegerNode) AsFloat() (float64, error)
- func (n *IntegerNode) AsInt() (int64, error)
- func (n *IntegerNode) AsLocalDate() (LocalDate, error)
- func (n *IntegerNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *IntegerNode) AsLocalTime() (LocalTime, error)
- func (n *IntegerNode) AsString() (string, error)
- func (n *IntegerNode) AsTime() (time.Time, error)
- func (n *IntegerNode) Base() IntegerBase
- func (n *IntegerNode) Comment() string
- func (n *IntegerNode) LeadingComments() []string
- func (n *IntegerNode) Raw() []byte
- func (n *IntegerNode) Span() Span
- func (n *IntegerNode) Type() NodeType
- func (n *IntegerNode) Value() any
- type KeyNode
- type KeyValueNode
- type LocalDate
- type LocalDateNode
- func (n *LocalDateNode) AsBool() (bool, error)
- func (n *LocalDateNode) AsFloat() (float64, error)
- func (n *LocalDateNode) AsInt() (int64, error)
- func (n *LocalDateNode) AsLocalDate() (LocalDate, error)
- func (n *LocalDateNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *LocalDateNode) AsLocalTime() (LocalTime, error)
- func (n *LocalDateNode) AsString() (string, error)
- func (n *LocalDateNode) AsTime() (time.Time, error)
- func (n *LocalDateNode) Comment() string
- func (n *LocalDateNode) LeadingComments() []string
- func (n *LocalDateNode) Raw() []byte
- func (n *LocalDateNode) Span() Span
- func (n *LocalDateNode) Type() NodeType
- func (n *LocalDateNode) Value() any
- type LocalDateTime
- type LocalDateTimeNode
- func (n *LocalDateTimeNode) AsBool() (bool, error)
- func (n *LocalDateTimeNode) AsFloat() (float64, error)
- func (n *LocalDateTimeNode) AsInt() (int64, error)
- func (n *LocalDateTimeNode) AsLocalDate() (LocalDate, error)
- func (n *LocalDateTimeNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *LocalDateTimeNode) AsLocalTime() (LocalTime, error)
- func (n *LocalDateTimeNode) AsString() (string, error)
- func (n *LocalDateTimeNode) AsTime() (time.Time, error)
- func (n *LocalDateTimeNode) Comment() string
- func (n *LocalDateTimeNode) LeadingComments() []string
- func (n *LocalDateTimeNode) Raw() []byte
- func (n *LocalDateTimeNode) Span() Span
- func (n *LocalDateTimeNode) Type() NodeType
- func (n *LocalDateTimeNode) Value() any
- type LocalTime
- type LocalTimeNode
- func (n *LocalTimeNode) AsBool() (bool, error)
- func (n *LocalTimeNode) AsFloat() (float64, error)
- func (n *LocalTimeNode) AsInt() (int64, error)
- func (n *LocalTimeNode) AsLocalDate() (LocalDate, error)
- func (n *LocalTimeNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *LocalTimeNode) AsLocalTime() (LocalTime, error)
- func (n *LocalTimeNode) AsString() (string, error)
- func (n *LocalTimeNode) AsTime() (time.Time, error)
- func (n *LocalTimeNode) Comment() string
- func (n *LocalTimeNode) LeadingComments() []string
- func (n *LocalTimeNode) Raw() []byte
- func (n *LocalTimeNode) Span() Span
- func (n *LocalTimeNode) Type() NodeType
- func (n *LocalTimeNode) Value() any
- type Node
- type NodeType
- type Pair
- type PathSegment
- type Position
- type Reader
- func (r *Reader) Boolean(key string, required bool) (bool, bool)
- func (r *Reader) Child(path string, v Value) *Reader
- func (r *Reader) Done()
- func (r *Reader) Errorf(key, format string, args ...any)
- func (r *Reader) ErrorfAt(line int, path, format string, args ...any)
- func (r *Reader) File() string
- func (r *Reader) Flag(key string) bool
- func (r *Reader) Get(key string) (Value, bool)
- func (r *Reader) Has(key string) bool
- func (r *Reader) Integer(key string, required bool, write string) (int64, bool)
- func (r *Reader) KeyPath(key string) string
- func (r *Reader) Keys() []string
- func (r *Reader) Line(key string) int
- func (r *Reader) Lines() map[string]int
- func (r *Reader) Missing(key, write string)
- func (r *Reader) NonEmpty(key, write string)
- func (r *Reader) Path() string
- func (r *Reader) Peek(key string) (Value, bool)
- func (r *Reader) PeekTrue(key string) bool
- func (r *Reader) Str(key string, required bool, write string) (string, bool)
- func (r *Reader) StrList(key string, required bool, write string) ([]string, bool)
- func (r *Reader) Sub(key string) *Reader
- func (r *Reader) TableLine() int
- func (r *Reader) Tables(key string) []*Reader
- func (r *Reader) WrongKind(key string, v Value, want, write string)
- type Record
- type Scalar
- type SegmentKind
- type Span
- type Spec
- type StringNode
- func (n *StringNode) AsBool() (bool, error)
- func (n *StringNode) AsFloat() (float64, error)
- func (n *StringNode) AsInt() (int64, error)
- func (n *StringNode) AsLocalDate() (LocalDate, error)
- func (n *StringNode) AsLocalDateTime() (LocalDateTime, error)
- func (n *StringNode) AsLocalTime() (LocalTime, error)
- func (n *StringNode) AsString() (string, error)
- func (n *StringNode) AsTime() (time.Time, error)
- func (n *StringNode) Comment() string
- func (n *StringNode) LeadingComments() []string
- func (n *StringNode) Raw() []byte
- func (n *StringNode) Span() Span
- func (n *StringNode) Style() StringStyle
- func (n *StringNode) Type() NodeType
- func (n *StringNode) Value() any
- type StringStyle
- type TableNode
- type Value
- type ValueType
- type WalkMode
Examples ¶
- Decode
- Decode (Strict)
- DecodeNode
- DecodeOver
- Diff
- Document.AppendToArray
- Document.DecodeSpec
- Document.Delete
- Document.EnsureDefaults
- Document.Format
- Document.GetComment
- Document.GetTime
- Document.Key
- Document.Merge
- Document.PermuteChildren
- Document.RenameKey
- Document.Root
- Document.RoundTripVerifiedBytes
- Document.Set
- Document.Set (OrderedInlineTable)
- Document.SetComment
- Document.SetCreate
- Document.Validate
- Document.Walk
- Error
- FormatFloat
- Parse
- ParsePath
- ParseWithFileName
- QuoteString
- Reader
- Unmarshal
- Unmarshal (IntoAMap)
Constants ¶
This section is empty.
Variables ¶
var ( ErrSyntax error = kindError(KindSyntax) // a lexing or parsing failure ErrUnknownKey error = kindError(KindUnknownKey) // a key matching no field of the target ErrUnknownTable error = kindError(KindUnknownTable) // a table matching no field of the target ErrMissingKey error = kindError(KindMissingKey) // a required key the document does not carry ErrTypeMismatch error = kindError(KindTypeMismatch) // a value whose kind the target refuses ErrInexact error = kindError(KindInexact) // a value the target cannot hold exactly ErrNotFound error = kindError(KindNotFound) // a path naming nothing ErrBadPath error = kindError(KindBadPath) // a syntactically invalid path ErrWrongContainer error = kindError(KindWrongContainer) // a structurally inapplicable path step ErrBadInput error = kindError(KindBadInput) // an invalid input to an editing operation ErrConflict error = kindError(KindConflict) // an edit that would produce an invalid document ErrRoundTrip error = kindError(KindRoundTrip) // rendered bytes that did not survive a re-parse ErrInvalidValue error = kindError(KindInvalidValue) // a value a hand-reading caller refused )
The kind sentinels. Match a diagnostic against one with errors.Is:
if errors.Is(err, tomledit.ErrNotFound) { ... }
Every ErrorKind has exactly one sentinel and every sentinel has exactly one kind; TestErrorKindSentinelDrift fails when one is added without the other.
var ErrSkipTable = errors.New("skip table")
ErrSkipTable is a sentinel error returned from a Walk visitor function to skip the current table's children (or inline table's children). Returning ErrSkipTable on a scalar node is a no-op.
Functions ¶
func Decode ¶
Decode decodes the document into a freshly allocated T -- a struct, a map with string keys, any, or any other type the conversion table accepts.
The type argument is explicit, because Go infers nothing from a return type:
cfg, err := tomledit.Decode[Config](doc)
Decode writes to no memory the caller can observe. It allocates the value it returns, and on any failure it returns (nil, err): there is no partially written target, and nothing to inspect after an error.
Struct fields are matched by their "toml" tag, or, with no tag, by their exact field name. Matching is exact: a document key that differs only in case from a field's name is an unknown key, not a match. A tag of "-" excludes the field, and an excluded name is not part of the document's universe at all: a document key naming one is as unknown as any other. The only tag option the package reads is "required" (`toml:"port,required"`), which makes the key's absence an error; any other option is refused, because an option nothing reads is a silent no-op. Embedded structs promote their fields and pointer fields are allocated as they are reached.
Every value goes through the conversion table: no target decodes itself, so a field of type time.Time accepts the date-time kinds the table lists for it and refuses a string with KindTypeMismatch, exactly as Scalar.AsTime and Document.GetTime do. To read a string as a time, decode it as a string and parse it.
Decoding is strict and strictness is the only mode: an unknown key, an unknown table, a value of a refused kind, a value the target cannot hold exactly, and a missing required key are all errors. A map-typed or any-typed target matches every key by construction and so reports no unknown keys.
Every independent violation is collected and reported together as an *Errors aggregate, in document order; it renders as its first diagnostic and exposes the rest through errors.As.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
type Server struct {
Host string `toml:"host"`
Port int `toml:"port"`
}
type Config struct {
Title string `toml:"title"`
Server Server `toml:"server"`
}
doc, err := tomledit.Parse([]byte(`title = "My App"
[server]
host = "0.0.0.0"
port = 443
`))
if err != nil {
panic(err)
}
// Decode returns the value it built; the document stays available for
// comment-preserving edits.
cfg, err := tomledit.Decode[Config](doc)
if err != nil {
panic(err)
}
fmt.Println(cfg.Title, cfg.Server.Host, cfg.Server.Port)
if err := doc.Set("server.port", cfg.Server.Port+1); err != nil {
panic(err)
}
port, err := doc.GetInt("server.port")
if err != nil {
panic(err)
}
fmt.Println(port)
}
Output: My App 0.0.0.0 443 444
Example (Strict) ¶
package main
import (
"errors"
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
type Config struct {
Title string `toml:"title"`
}
doc, err := tomledit.Parse([]byte("title = \"ok\"\ntilte = \"typo\"\n"))
if err != nil {
panic(err)
}
// Strictness is the only mode: an unknown key is an error, and a failed
// decode returns no value to inspect.
cfg, err := tomledit.Decode[Config](doc)
fmt.Println(cfg == nil)
fmt.Println(errors.Is(err, tomledit.ErrUnknownKey))
fmt.Println(err)
}
Output: true true 2:1: tilte: unknown key "tilte"
func DecodeNode ¶
DecodeNode decodes a single node into a freshly allocated T. It is Decode scoped to one construct: a table, an array-table entry, an inline table or a document decodes like a table, an array decodes like an array, and a scalar node decodes into a scalar target. A key, a key-value pair and a comment carry no value of their own and are refused.
The rules are Decode's, including strictness and the unobservability of a failure: on any failure DecodeNode returns (nil, err). A diagnostic names the file the node's document was read from, which the node reaches through the parent links its container maintains; a node not attached to a document names none.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
type Server struct {
Host string `toml:"host"`
Port int `toml:"port"`
}
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
port = 5432
`))
if err != nil {
panic(err)
}
// One construct decoded on its own.
node, err := doc.Resolve("server")
if err != nil {
panic(err)
}
server, err := tomledit.DecodeNode[Server](node)
if err != nil {
panic(err)
}
fmt.Println(server.Host, server.Port)
}
Output: localhost 5432
func DecodeOver ¶
DecodeOver decodes the document over a seed: the value the factory builds receives whatever the document supplies, and every key the document does not carry keeps what the seed put there. It is how a set of defaults expressed in Go is overlaid by a configuration file.
The seed is a FACTORY, not a value, so that what the decode fills is an allocation of DecodeOver's own and never memory the caller still holds:
cfg, written, err := tomledit.DecodeOver(doc, func() Config { return defaultConfig() })
The factory must build its seed afresh on each call. A seed that shares a map, a slice or a pointer with the caller lets the decode write through it, which is exactly what the factory form exists to prevent.
The second result names the paths the decode wrote, in document order and in the library's path syntax, so a caller can tell a value the document supplied from one the seed left behind. A value written whole -- an array, an any-typed table -- is one path, not one per element inside it.
The rules are Decode's, including strictness and the unobservability of a failure: on any failure DecodeOver returns (nil, nil, err) and the seed it built is unreachable.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
type Config struct {
Host string `toml:"host"`
Port int `toml:"port"`
Verbose bool `toml:"verbose"`
}
defaults := func() Config {
return Config{Host: "localhost", Port: 8080, Verbose: false}
}
doc, err := tomledit.Parse([]byte("port = 9090\n"))
if err != nil {
panic(err)
}
// The factory builds the seed, the document overlays it, and the second
// result names the paths the document supplied.
cfg, written, err := tomledit.DecodeOver(doc, defaults)
if err != nil {
panic(err)
}
fmt.Println(cfg.Host, cfg.Port, cfg.Verbose)
fmt.Println(written)
}
Output: localhost 9090 false [port]
func FormatFloat ¶
FormatFloat returns f as a TOML float. It is TOTAL: every float64 has an output, including the ones TOML has no numeric spelling for.
A finite value is written in the shortest form that reads back as the same float64, with a float marker always present -- a ".0" is appended when the shortest form carries neither a fractional part nor an exponent, so the result never reads back as an integer. Negative zero keeps its sign.
The three non-finite values are written "nan", "inf" and "-inf". A NaN with its sign bit set is written "nan" like any other: the library never writes "+nan", "-nan" or "+inf", which no reader needs and which say nothing about the value. (The value-writing operations refuse a sign-bit NaN as input rather than silently dropping the sign; this renderer, having no input to refuse, renders it.)
Example ¶
package main
import (
"fmt"
"math"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
fmt.Println(tomledit.FormatFloat(1))
fmt.Println(tomledit.FormatFloat(0.1))
fmt.Println(tomledit.FormatFloat(1e21))
fmt.Println(tomledit.FormatFloat(math.Inf(-1)))
fmt.Println(tomledit.FormatFloat(math.NaN()))
}
Output: 1.0 0.1 1e+21 -inf nan
func JoinPath ¶
func JoinPath(segs []PathSegment) string
JoinPath renders segments as path text, and is the single quoting authority for paths: a key segment is written bare when every byte of it is legal in a bare key, and quoted (with quotes and backslashes escaped) otherwise, so that ParsePath reads back exactly the segments JoinPath was given. An index segment is written in brackets, attached to whatever precedes it.
The result of joining no segments is the empty string, which ParsePath refuses: a path names at least one step.
func PathWithoutIndices ¶ added in v0.6.0
PathWithoutIndices returns a key path without its array indices, the way a file's headers spell it: "command[0].flag[1]" gives "command.flag".
func QuoteKey ¶
QuoteKey returns s as a TOML key: bare when TOML's bare-key rule allows it (ASCII letters, digits, hyphens and underscores, and at least one character), and the basic-string form of QuoteString otherwise. The empty key is therefore written as a pair of quotes.
It quotes ONE key. A dotted path is a sequence of keys, each quoted on its own and joined with dots; JoinPath does that for the library's path syntax.
It is total on the same terms as QuoteString, and inherits its treatment of a key that is not valid UTF-8: the invalid bytes render as U+FFFD, and the key-writing operations refuse such a key before it ever reaches here.
func QuoteString ¶
QuoteString returns s as a TOML basic string: double-quoted, with the backslash, the double quote and the control characters escaped and everything else written verbatim.
The escapes are exactly TOML's own set. Backspace, tab, newline, form feed and carriage return take their short forms; every other control character, U+007F included, takes the four-digit "\u" form with LOWERCASE hex digits. Non-ASCII text is written as itself: a basic string carries it directly, and escaping it would only make the result harder to read.
It is TOTAL: every Go string has an output. For a string that is valid UTF-8 -- which is every string a write can carry, since the value-writing operations refuse anything else -- the result parses back to s, so QuoteString is the inverse of reading a string value. A string that is NOT valid UTF-8 is rendered with each invalid byte as U+FFFD, and the inverse property does not hold for it: no TOML spelling carries such a byte, and this renderer has no input to refuse.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
fmt.Println(tomledit.QuoteString(`say "hi"`))
fmt.Println(tomledit.QuoteString("tab\there"))
fmt.Println(tomledit.QuoteKey("bare_key"))
fmt.Println(tomledit.QuoteKey("not a bare key"))
}
Output: "say \"hi\"" "tab\there" bare_key "not a bare key"
func Unmarshal ¶
Unmarshal parses TOML data and decodes it into a freshly allocated T.
Decoding is strict: see Decode, whose rules Unmarshal is exactly. A parse failure is reported alone (parsing cannot continue past a syntax error); decode failures are collected and reported together as an *Errors aggregate.
On any failure Unmarshal returns a nil result: there is no state to inspect after an error.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
type Config struct {
Title string `toml:"title"`
Server struct {
Host string `toml:"host"`
Port int `toml:"port"`
} `toml:"server"`
}
cfg, err := tomledit.Unmarshal[Config]([]byte(`title = "My App"
[server]
host = "0.0.0.0"
port = 443
`))
if err != nil {
panic(err)
}
fmt.Println(cfg.Title)
fmt.Println(cfg.Server.Host)
fmt.Println(cfg.Server.Port)
}
Output: My App 0.0.0.0 443
Example (IntoAMap) ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
m, err := tomledit.Unmarshal[map[string]any]([]byte(`title = "My App"
[server]
port = 443
`))
if err != nil {
panic(err)
}
// The result is a pointer to the value Unmarshal built.
fmt.Println((*m)["title"])
fmt.Println((*m)["server"].(map[string]any)["port"])
}
Output: My App 443
Types ¶
type ArrayNode ¶
type ArrayNode struct {
// contains filtered or unexported fields
}
ArrayNode represents an array value.
func (*ArrayNode) Comment ¶
func (n *ArrayNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*ArrayNode) LeadingComments ¶
func (n *ArrayNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*ArrayNode) Raw ¶
func (n *ArrayNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*ArrayNode) Span ¶
func (n *ArrayNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type ArrayTableNode ¶
type ArrayTableNode struct {
// contains filtered or unexported fields
}
ArrayTableNode represents an [[array-table]] header and its children.
func (*ArrayTableNode) Children ¶
func (n *ArrayTableNode) Children() []Node
Children returns the constructs written under this header, in document order, as a copy.
func (*ArrayTableNode) Comment ¶
func (n *ArrayTableNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*ArrayTableNode) KeyPath ¶
func (n *ArrayTableNode) KeyPath() []string
KeyPath returns the parts of the header's key, decoded, as a copy.
func (*ArrayTableNode) LeadingComments ¶
func (n *ArrayTableNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*ArrayTableNode) Raw ¶
func (n *ArrayTableNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*ArrayTableNode) Span ¶
func (n *ArrayTableNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type BooleanNode ¶
type BooleanNode struct {
// contains filtered or unexported fields
}
BooleanNode represents a boolean value.
func (*BooleanNode) AsBool ¶
func (n *BooleanNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*BooleanNode) AsFloat ¶
func (n *BooleanNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*BooleanNode) AsInt ¶
func (n *BooleanNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*BooleanNode) AsLocalDate ¶
func (n *BooleanNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*BooleanNode) AsLocalDateTime ¶
func (n *BooleanNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*BooleanNode) AsLocalTime ¶
func (n *BooleanNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*BooleanNode) AsString ¶
func (n *BooleanNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*BooleanNode) AsTime ¶
func (n *BooleanNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*BooleanNode) Comment ¶
func (n *BooleanNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*BooleanNode) LeadingComments ¶
func (n *BooleanNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*BooleanNode) Raw ¶
func (n *BooleanNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*BooleanNode) Span ¶
func (n *BooleanNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type Change ¶
type Change struct {
Kind ChangeKind
Path string
OldValue any // nil for Added
NewValue any // nil for Removed
}
Change represents a single difference between two documents. OldValue is nil for Added changes; NewValue is nil for Removed changes.
func Diff ¶
Diff returns all differences between documents a and b.
It walks both documents to collect all leaf (scalar) values, then compares them. Container nodes (inline tables, arrays) are not compared directly; instead their individual elements are compared. Changes are sorted by path (alphabetical), then by kind (Removed, Modified, Added).
The comparison reads values, never spellings. Two documents writing one value differently -- 0x2A against 42, 1_000 against 1000, one instant in two zone offsets, a literal string against a basic one, an array-of-tables against an inline array of inline tables -- report no difference. A document therefore always compares equal to itself.
Types are not bridged: an integer and a float never compare equal, so 1 and 1.0 are a modification.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
a, _ := tomledit.Parse([]byte(`name = "Alice"
age = 30
`))
b, _ := tomledit.Parse([]byte(`name = "Bob"
email = "bob@example.com"
`))
changes := tomledit.Diff(a, b)
for _, c := range changes {
fmt.Printf("%s: %s\n", c.Kind, c.Path)
}
}
Output: removed: age added: email modified: name
type ChangeKind ¶
type ChangeKind int
ChangeKind identifies the type of difference between two documents.
const ( Added ChangeKind = iota // Added means the key exists in b but not in a. Removed // Removed means the key exists in a but not in b. Modified // Modified means the key exists in both but with different values. )
func (ChangeKind) String ¶
func (k ChangeKind) String() string
String returns the human-readable name of the change kind.
type Collector ¶ added in v0.6.0
type Collector struct {
// contains filtered or unexported fields
}
Collector accumulates diagnostics, from one file or several, and reports them together as one error sorted by file, line, path, and message, so a tool reading a set of files reports every problem in all of them at once, in an order that does not depend on the order the files were read in:
var c tomledit.Collector
if r := c.Read("a.toml", a); r != nil { readA(r) }
if r := c.Read("b.toml", b); r != nil { readB(r) }
if err := c.Err(); err != nil { return err }
The zero Collector is empty and ready to use.
func (*Collector) Add ¶ added in v0.6.0
Add records every diagnostic err carries: each one of an *Errors aggregate (a decode's result, say), or the one *Error err is or wraps. An error carrying no diagnostic is recorded as a KindConflict diagnostic wrapping it. A nil err records nothing.
func (*Collector) Err ¶ added in v0.6.0
Err returns every diagnostic recorded so far, sorted by file, line, path, and message, as an *Errors aggregate, or nil when there are none. The sort is stable, so two diagnostics equal in all four keep the order they were recorded in. Recording more after Err leaves the returned error unchanged.
func (*Collector) Errorf ¶ added in v0.6.0
Errorf records a KindInvalidValue diagnostic at a line of a file, naming the key path it concerns (in this package's path syntax, such as "command[0].flag[1].name") and a formatted message. It is the form for a problem found across tables or files, which no single Reader owns; within one table, Reader.Errorf names the file, line, and path itself.
type CommentNode ¶
type CommentNode struct {
// contains filtered or unexported fields
}
CommentNode represents a standalone comment line.
func (*CommentNode) Comment ¶
func (n *CommentNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*CommentNode) LeadingComments ¶
func (n *CommentNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*CommentNode) Raw ¶
func (n *CommentNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*CommentNode) Span ¶
func (n *CommentNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*CommentNode) Text ¶
func (n *CommentNode) Text() string
Text returns the comment line as it was written, including its "#" and its trailing newline. A comment node standing for a run of blank lines has no text of its own; Raw carries the blank bytes.
type Cursor ¶
type Cursor struct {
// contains filtered or unexported fields
}
Cursor provides a fluent, nil-safe API for navigating a TOML document. A Cursor is never nil. If navigation fails at any point, the cursor captures the error and all subsequent operations (Key, At, String, etc.) become no-ops that propagate the original error. Check Err after a chain of calls to see whether the traversal succeeded.
A cursor navigates the read-layer, so Key and At step through compound tables and array-of-tables the same way a path does: Key crosses into a table however the document spells it, and At addresses an entry of an array-of-tables or an element of an array.
func (*Cursor) Bool ¶
Bool reads the value at the cursor as a bool.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not a boolean.
func (*Cursor) Err ¶
Err returns the first error encountered during navigation, as an *Error naming the document's file when it has one.
func (*Cursor) Float ¶
Float reads the value at the cursor as a float64: a float verbatim, and an integer the target holds exactly.
The error is the navigation failure that ended the chain, KindTypeMismatch when the value is neither a float nor an integer, or KindInexact for an integer no float64 holds exactly.
func (*Cursor) Int ¶
Int reads the value at the cursor as an int64.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not an integer.
func (*Cursor) Items ¶
Items returns a range-over-func iterator over elements of the current node. Works with ArrayNode elements and array-of-tables entries. An inert cursor (with error) yields nothing.
func (*Cursor) Len ¶
Len returns the number of elements at the current cursor position. Returns -1 if the cursor has an error or the node isn't an array/array-of-tables.
func (*Cursor) Node ¶
Node returns the node at the cursor's position, or nil if the cursor has an error. A position no single node stands for -- an array-of-tables, or a table implied by a longer header or a dotted key -- has no node to return: Node reports that through Err as a KindWrongContainer diagnostic and returns nil.
func (*Cursor) String ¶
String reads the value at the cursor as a string. It is deliberately not a fmt.Stringer: a cursor is a position, not a rendering of one.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not a string.
func (*Cursor) Time ¶
Time reads the value at the cursor as a time.Time: an offset date-time verbatim, and a local date-time or local date read as UTC.
The error is the navigation failure that ended the chain, or KindTypeMismatch when the value is not one of those three flavors -- a string among them, even one spelling a valid RFC 3339 timestamp, which a time.Time decode target refuses too.
type DateTimeNode ¶
type DateTimeNode struct {
// contains filtered or unexported fields
}
DateTimeNode represents an offset date-time value.
func (*DateTimeNode) AsBool ¶
func (n *DateTimeNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*DateTimeNode) AsFloat ¶
func (n *DateTimeNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*DateTimeNode) AsInt ¶
func (n *DateTimeNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*DateTimeNode) AsLocalDate ¶
func (n *DateTimeNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*DateTimeNode) AsLocalDateTime ¶
func (n *DateTimeNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*DateTimeNode) AsLocalTime ¶
func (n *DateTimeNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*DateTimeNode) AsString ¶
func (n *DateTimeNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*DateTimeNode) AsTime ¶
func (n *DateTimeNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*DateTimeNode) Comment ¶
func (n *DateTimeNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*DateTimeNode) LeadingComments ¶
func (n *DateTimeNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*DateTimeNode) Raw ¶
func (n *DateTimeNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*DateTimeNode) Span ¶
func (n *DateTimeNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type Default ¶
Default is one entry of the EnsureDefaults input: a full PATH in this package's path syntax, and the value to seed there when the document does not carry it.
Path is a path, not a key: "server.host" reaches the "host" of the "server" table, and a key that carries a dot of its own is quoted, `server."host.name"`. Pair is the other way round -- one key, taken verbatim -- and the two are separate types so that neither grammar can be handed to the other by accident.
type Document ¶
type Document struct {
// contains filtered or unexported fields
}
Document is the root node of a TOML document.
func Parse ¶
Parse lexes and parses TOML source bytes into a Document AST.
The returned Document preserves all whitespace, comments, and formatting from the original source. Serializing it back with Bytes produces the exact original bytes (round-trip fidelity).
Every lexing or parsing failure -- including duplicate key detection and invalid TOML syntax -- is reported as an *Error of kind KindSyntax, carrying the position, span and source line of the offending construct. Parsing stops at the first failure.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
port = 8080
`))
if err != nil {
panic(err)
}
host, err := doc.GetString("server.host")
if err != nil {
panic(err)
}
port, err := doc.GetInt("server.port")
if err != nil {
panic(err)
}
fmt.Println(host)
fmt.Println(port)
}
Output: localhost 8080
func ParseWithFileName ¶ added in v0.7.0
ParseWithFileName parses src as Parse does, and records file as the document's origin, so every diagnostic it later produces -- from parsing, access or editing -- names that file. It reads nothing: file is only the name the diagnostics carry. The tomlfile subpackage reads a file and parses it through this function.
Example ¶
package main
import (
"errors"
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.ParseWithFileName([]byte("[server]\nport = \"8080\"\n"), "config.toml")
if err != nil {
panic(err)
}
// The document remembers the name it was parsed under, so a later
// diagnostic names the file, the line and the column.
_, err = doc.GetInt("server.port")
var diag *tomledit.Error
if errors.As(err, &diag) {
fmt.Println(diag.File, diag.Pos.Line, diag.Pos.Column, diag.Path, diag.Expected, diag.Got)
}
}
Output: config.toml 2 8 server.port integer string
func (*Document) AppendToArray ¶
AppendToArray appends a value to the array the path names.
The value is converted the way Set converts one: the Go types listed there, including []Pair for an ordered inline table. An array-of-tables is not an array value -- add an entry to one with NewArrayTable.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte("ports = [8080, 8081]\n"))
if err != nil {
panic(err)
}
if err := doc.AppendToArray("ports", 8082); err != nil {
panic(err)
}
if err := doc.RemoveFromArray("ports", 0); err != nil {
panic(err)
}
fmt.Print(string(doc.Bytes()))
}
Output: ports = [8081, 8082]
func (*Document) Bytes ¶
Bytes serializes the document back to TOML bytes.
A document parsed and never edited renders as the exact bytes it was parsed from. An edited one renders per FRAGMENT: every byte range an edit did not touch is written back as it was read, and only the ranges the edits invalidated are rendered anew, in the library's canonical spellings.
So a value write leaves the line's spacing, its inline comment and the quoting of its key exactly as they were; a comment write leaves the value's base and quoting; a write inside an array or an inline table leaves every sibling, separator and interior comment; and a rename leaves everything but the renamed key part. What the library writes -- and only that -- is written in the forms of QuoteString, QuoteKey and FormatFloat.
func (*Document) Children ¶
Children returns the document's top-level constructs in document order, as a copy.
func (*Document) Comment ¶
func (n *Document) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*Document) DecodeSpec ¶
DecodeSpec checks the document against the descriptor and, when it satisfies it, returns the document's values as native Go data: map[string]any for every table spelling, []any for arrays and arrays of tables, and string, int64, float64, bool, time.Time, LocalDateTime, LocalDate or LocalTime for scalars.
It is the descriptor path's decode: the same engine Validate runs, followed by a value built out of the read-layer. No reflection is involved and no consumer code runs, so the result is exactly what the document says.
DecodeSpec is ATOMIC, without exception: it returns a map only when the document has no violations at all, and (nil, err) otherwise. There is no partial map, no half-populated table, and nothing to inspect after an error -- the diagnostics are the whole answer. This is a stronger promise than the typed entry points make, and it holds because nothing outside this package participates in building the map.
The errors are Validate's: an *Errors aggregate of every independent violation in document order, or a plain error for a descriptor that describes nothing.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
port = 8080
`))
if err != nil {
panic(err)
}
spec := &tomledit.Spec{Fields: map[string]tomledit.Field{
"server": {Kind: tomledit.FieldKindTable, Table: &tomledit.Spec{
Fields: map[string]tomledit.Field{
"host": {Kind: tomledit.FieldKindString},
"port": {Kind: tomledit.FieldKindInteger},
},
}},
}}
// A descriptor decode runs no consumer code: the result is exactly what
// the document says, and it is all-or-nothing.
values, err := doc.DecodeSpec(spec)
if err != nil {
panic(err)
}
server := values["server"].(map[string]any)
fmt.Println(server["host"], server["port"])
}
Output: localhost 8080
func (*Document) Delete ¶
Delete removes the node at the given path from the document. It handles key-value pairs, tables, array-of-tables, and array elements.
Removal is idempotent: a path the document does not carry is a silent no-op, so an ensure-absent loop can call it unconditionally. A path that cannot be parsed, and a document the read-layer cannot fold at all, are still reported.
The spelling of the table the key sits in changes nothing about that. In a table no single node stands for -- one a dotted key spelled out, or one only a longer header implies -- the removal reaches the dotted pair that binds a value, or the headers that spell a table out, those of the tables nested inside it included.
Example ¶
package main
import (
"errors"
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
port = 8080
debug = true
`))
if err != nil {
panic(err)
}
err = doc.Delete("server.debug")
if err != nil {
panic(err)
}
// The key is gone, so reading it is a not-found diagnostic.
_, err = doc.GetBool("server.debug")
fmt.Println(errors.Is(err, tomledit.ErrNotFound))
}
Output: true
func (*Document) EnsureDefaults ¶
EnsureDefaults seeds the paths the document does not already carry, in the order given, and returns the paths it added.
A path the document carries in ANY spelling is left alone: a key written as a dotted key, inside a [header] table, or inside an inline table all count as present, and so does a table that only exists because a longer header implies it. Nothing is ever overwritten and no existing value, comment or spelling is touched.
A missing intermediate table is created as a standard [header] table, never an inline one, so what the document ends up spelling depends only on the list and not on the order in which the intermediates happened to be needed: running the same list against the same document twice writes the same bytes.
Partial application: the seeding stops at the first error, everything written before it stays written, and added names exactly those paths -- so a caller that must know what it changed reads them from the return value rather than from a diff. The default that failed wrote nothing at all, not even the tables leading to it, so added is exact rather than approximate.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
`))
if err != nil {
panic(err)
}
added, err := doc.EnsureDefaults([]tomledit.Default{
{Path: "server.host", Value: "0.0.0.0"}, // already there, left alone
{Path: "server.port", Value: 8080},
{Path: "logging.level", Value: "info"},
})
if err != nil {
panic(err)
}
fmt.Println(added)
fmt.Print(string(doc.Bytes()))
}
Output: [server.port logging.level] [server] host = "localhost" port = 8080 [logging] level = "info"
func (*Document) Format ¶
func (d *Document) Format(opts ...FormatOption) []byte
Format returns normalized TOML bytes. It does NOT mutate the document -- it produces a new byte slice by walking the AST and re-rendering every node with consistent formatting, ignoring all raw bytes. This is useful for enforcing a canonical style. Pass zero or more FormatOption values (e.g. WithIndentWidth, WithLineWidth) to customize the output.
The writer's blank-line grouping survives at document and table-body level: a run of blank lines becomes exactly one. The output never begins with a blank line and always ends with exactly one newline, so blank lines at either end of the document are dropped. Arrays are restructured wholesale (inline or multi-line from the configured line width), so blank lines between array elements do not survive formatting; Bytes preserves them.
The one gap the formatter opens itself is before a [table] or [[array-table]] header written flush against what precedes it: Format always inserts the missing blank line. What this library preserves is CONTENT -- the comments -- while whitespace is formatting, and Format is where the library is strict about output looking good, so the separation is not a caller's option. The insertion never removes anything and never doubles a blank line already there, so a document that separates its own tables is left as it stands.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`name="Alice"
[server]
host="localhost"
port=8080
`))
if err != nil {
panic(err)
}
formatted := doc.Format(tomledit.WithIndentWidth(2))
fmt.Print(string(formatted))
}
Output: name = "Alice" [server] host = "localhost" port = 8080
func (*Document) GetBool ¶
GetBool resolves the path and reads the value it names as a bool.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not a boolean.
func (*Document) GetComment ¶
GetComment returns the inline comment on the node at the given path, as text: without the "#" and the whitespace around it, so a line written `x = 1 # note` answers "note". A node with no inline comment answers the empty string.
The path resolves to the same node SetComment writes to -- for a key-value path the pair, not the unwrapped value; for a table path the header line -- so the two round-trip: what SetComment writes, GetComment reads back. It answers the NORMALIZED text, which is what SetComment takes, and a caller that needs the bytes as written reads Raw on the node instead.
The navigation errors are SetComment's: a path naming nothing is KindNotFound, a path naming a member of an inline table is KindWrongContainer (TOML gives an inline table nowhere to put a comment, so nothing there can carry one to read), and a malformed path is KindBadPath.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(
"# The port to bind to.\n" +
"# Any value above 1024 needs no privileges.\n" +
"port = 8080 # default HTTP port\n"))
if err != nil {
panic(err)
}
inline, err := doc.GetComment("port")
if err != nil {
panic(err)
}
leading, err := doc.GetLeadingComments("port")
if err != nil {
panic(err)
}
fmt.Printf("inline: %q\n", inline)
for _, line := range leading {
fmt.Printf("leading: %q\n", line)
}
}
Output: inline: "default HTTP port" leading: "The port to bind to." leading: "Any value above 1024 needs no privileges."
func (*Document) GetFloat ¶
GetFloat resolves the path and reads the value it names as a float64: a float verbatim, and an integer the target holds exactly.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, KindTypeMismatch when the value is neither a float nor an integer, and KindInexact for an integer no float64 holds exactly.
func (*Document) GetInt ¶
GetInt resolves the path and reads the value it names as an int64.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not an integer. A float is never an integer, however whole it is written.
func (*Document) GetLeadingComments ¶
GetLeadingComments returns the comment lines written above the node at the given path, in order, each as text: without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil.
The path resolves to the same node SetLeadingComments writes to, so the two round-trip, and the navigation errors are the same ones -- see GetComment.
func (*Document) GetString ¶
GetString resolves the path and reads the value it names as a string.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not a string.
func (*Document) GetTime ¶
GetTime resolves the path and reads the value it names as a time.Time: an offset date-time verbatim, and a local date-time or local date read as UTC, because a time.Time target declares that intent. A local time carries no date and is refused.
A STRING is refused with KindTypeMismatch, even one spelling a valid RFC 3339 timestamp; a struct field of type time.Time refuses one too. To read a string as a time, use GetString and parse it.
The error is the unified *Error: KindBadPath, KindNotFound or KindWrongContainer from the navigation, and KindTypeMismatch when the value is not one of the three date-time flavors above.
Example ¶
package main
import (
"errors"
"fmt"
"time"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`stamp = 1979-05-27T07:32:00Z
written = "1979-05-27T07:32:00Z"
`))
if err != nil {
panic(err)
}
stamp, err := doc.GetTime("stamp")
if err != nil {
panic(err)
}
fmt.Println(stamp.Format(time.RFC3339))
// A string is not a date-time, even one spelling a valid timestamp. A
// time.Time DECODE target answers the same way: both surfaces read the one
// conversion table.
_, err = doc.GetTime("written")
fmt.Println(errors.Is(err, tomledit.ErrTypeMismatch))
type Config struct {
Written time.Time `toml:"written"`
Stamp time.Time `toml:"stamp"`
}
_, err = tomledit.Decode[Config](doc)
fmt.Println(errors.Is(err, tomledit.ErrTypeMismatch))
}
Output: 1979-05-27T07:32:00Z true true
func (*Document) Has ¶
Has reports whether the path names a concrete node, on the same terms as Lookup.
func (*Document) Key ¶
Key returns a Cursor navigated to the named child of the document root. This is the entry point for the fluent cursor API. Chain additional Key or At calls to traverse deeper, then extract the value with String, Int, etc.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[database]
host = "localhost"
port = 5432
`))
if err != nil {
panic(err)
}
host, err := doc.Key("database").Key("host").String()
if err != nil {
panic(err)
}
fmt.Println(host)
port, err := doc.Key("database").Key("port").Int()
if err != nil {
panic(err)
}
fmt.Println(port)
}
Output: localhost 5432
func (*Document) LeadingComments ¶
func (n *Document) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*Document) Lookup ¶
Lookup returns the node the path names, and whether it names one. It is the comma-ok form of Resolve, and answers about CONCRETE nodes: a path naming something no single node stands for -- an array-of-tables, or a table implied by a longer header or a dotted key -- reports false, even though the read-layer carries it. Use Root to read those.
func (*Document) Merge ¶
Merge merges all values from the other document into d. Only keys that do not exist in d are set; existing values are never overwritten. Seeding a document from a list of defaults rather than from another document is EnsureDefaults.
The other document is read through the read-layer, so what merges is what it MEANS, not how it is written: a key bound by a dotted key, by a header table or by an inline table arrives the same way, and a table the source only implies is merged as the table it implies. The target is asked the same question -- a key it spells with a longer header counts as present just as much as one with a header of its own.
Comment handling:
- For existing keys: other's leading comments are appended to d's leading comments; if d has no inline comment and other does, it is copied.
- For new keys: the comments of the line that binds the key travel with it -- the comments written above it and the one written after it on the same line. Comments written INSIDE a container value do not. An array or an inline table that is new in the target is written from its values, so a comment between its elements is dropped. A table merged key by key keeps the comments of every key it brings, since each of those is a binding line with comments of its own.
Two consequences of the rules above, stated so they are read as the contract and not as an accident:
- Merging one source twice doubles its leading comments. The second merge finds every key already present, and the existing-key rule appends.
- The tables above a new nested table are written as empty headers: a source whose only content is [deep.nest] leaves an empty [deep] header in the target above it. Merge writes through SetCreate, which spells every intermediate table the path names as a header of its own.
Array-of-tables are treated atomically: if d already has anything at a given path, all of other's entries for that path are skipped.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
base, _ := tomledit.Parse([]byte(`[server]
host = "localhost"
`))
defaults, _ := tomledit.Parse([]byte(`[server]
host = "0.0.0.0"
port = 8080
`))
err := base.Merge(defaults)
if err != nil {
panic(err)
}
// host was already set, so it keeps "localhost".
host, err := base.GetString("server.host")
if err != nil {
panic(err)
}
// port was missing, so it was added from defaults.
port, err := base.GetInt("server.port")
if err != nil {
panic(err)
}
fmt.Println(host)
fmt.Println(port)
}
Output: localhost 8080
func (*Document) NewArrayTable ¶
NewArrayTable appends a new [[array-table]] entry at the given path. Multiple entries with the same path are valid in TOML and represent successive elements of the array. The path must consist of key segments only (no array indices).
A name already bound to an array-of-tables is exactly what a new entry extends. Any other binding of the name -- a value, an inline table, a table with a header of its own or one another construct implied -- is refused with KindConflict, as is a prefix of the path that holds a value.
func (*Document) NewTable ¶
NewTable creates a new [table] header at the given path and appends it to the document. The path must consist of key segments only (no array indices).
The header must be able to bind its name. It is refused with KindConflict when anything else in the document already does: a value, an inline table, a table with its own header, an array-of-tables, or a table a dotted key implied (TOML does not allow giving one of those a header of its own). A table implied only by a LONGER header -- the "a" of an earlier [a.b] -- has no header of its own yet, and this is how it gets one. A prefix of the path that holds a value is refused on the same terms.
The refusal is about the path's FINAL key only. A table a dotted key implied may not be redefined by a header, and it may still hold sub-tables of its own: with "apple.color" written under [fruit], creating [fruit.apple] is refused and [fruit.apple.texture] is not, exactly as TOML has it.
func (*Document) PermuteChildren ¶
PermuteChildren reorders the children of the container the path names. The empty path addresses the document's own children.
The order is a GATHER: order[i] is the index of the child that moves to position i, so children [A, B] with order [1, 0] end up [B, A]. It must be total -- a permutation of every index the container has, each exactly once. A wrong length, an out-of-range index and a repeated one are each refused with KindBadInput, and nothing is reordered. The two index violations name the offending index; a length mismatch is about the order as a whole and names both counts instead.
The indices address the container's children as they stand right now. Read them, compute the order and permute in one editing sequence: an edit in between that adds or removes a child shifts every index after it, and the permutation would then move the wrong nodes.
A child's own trivia travels with it -- its leading comments, its inline comment and the blank lines before it are part of the child, not of the position it used to hold.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`# about b
b = 2
a = 1
c = 3
`))
if err != nil {
panic(err)
}
// order[i] is the index of the child moving to position i, so this puts
// the second child first. A child's comments travel with it.
if err := doc.PermuteChildren("", []int{1, 0, 2}); err != nil {
panic(err)
}
fmt.Print(string(doc.Bytes()))
}
Output: a = 1 # about b b = 2 c = 3
func (*Document) Raw ¶
func (n *Document) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*Document) RemoveFromArray ¶
RemoveFromArray removes the element at index from the array the path names. A negative index counts from the end, so -1 removes the last element.
Unlike Delete, which is idempotent by contract, this operation names a position that must exist: an index outside the array is refused with KindNotFound.
func (*Document) RenameKey ¶
RenameKey changes the key name of the node at the given path to newKey.
It renames the BINDING, whatever constructs spell it out. A name bound by a value is renamed in the pair that writes it; a name bound by a table is renamed in every header that names that table, the headers of the tables nested inside it included, and in every dotted pair written under it. A name bound by an array-of-tables is renamed in every entry's header. The renamed key part is the only fragment invalidated: the brackets, the other parts, the whitespace between them and the line's comment all splice as written.
It reports KindNotFound when the path names nothing, KindWrongContainer when the last path segment is an array index (an element has no key to rename) or when the parent names an array-of-tables rather than one of its entries, KindConflict when anything in the parent already binds newKey -- a value, a table in any spelling, or an array-of-tables -- and KindBadInput when newKey is not valid UTF-8 and so is not a key TOML can carry. A refused rename changes nothing.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost" # bind address
[server.tls]
cert = "/etc/cert.pem"
`))
if err != nil {
panic(err)
}
// Renaming a table renames the binding: every header that names it,
// nested ones included.
if err := doc.RenameKey("server", "listener"); err != nil {
panic(err)
}
fmt.Print(string(doc.Bytes()))
}
Output: [listener] host = "localhost" # bind address [listener.tls] cert = "/etc/cert.pem"
func (*Document) Resolve ¶
Resolve resolves the path against the document and returns the node it names. For a key-value pair the value node is returned, not the pair.
Path syntax is ParsePath's: dots between keys ("server.host"), brackets for indices ("items[0]", "items[-1]" for the last), quotes around a key that would otherwise read as more than one segment.
The returned *Error carries KindBadPath for a syntactically invalid path, KindNotFound for a path naming nothing, and KindWrongContainer for a step that does not apply to what it addresses -- including a path that names something no single node stands for: an array-of-tables (index it, or read its entries through Root) or a table implied by a longer header or a dotted key. Lookup and Has answer the same question without an error.
func (*Document) Root ¶
Root returns the read-layer view of the document: the record holding every top-level key, in first-appearance order.
Parsing folds nothing; the layer is built the first time a logical question is asked and kept until the document is written to, so repeated reads share one fold and reads of a shared document stay safe under concurrency. A write drops it: the next call folds again and answers what the document says then. A record obtained BEFORE a write keeps answering what the document said before it -- layer handles are snapshots, and a stale one reports stale data rather than reporting an error. Mutating a document while iterating its entries is unspecified.
Root panics when the document cannot be folded -- when two constructs claim one key, for instance. A parsed document never can be: the parser refuses every such conflict. Only an editing sequence that built an invalid document can reach it.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`title = "example"
database.host = "localhost"
database.port = 5432
[[server]]
name = "alpha"
[[server]]
name = "beta"
`))
if err != nil {
panic(err)
}
// The read-layer folds the spellings away: a dotted key and an
// array-of-tables answer the same questions as anything else.
for entry := range doc.Root().Entries() {
switch entry.Kind() {
case tomledit.EntryValue:
node, _ := entry.Node()
fmt.Printf("%s = %v\n", entry.Key(), node.(tomledit.Scalar).Value())
case tomledit.EntryRecord:
rec, _ := entry.Record()
fmt.Printf("%s is a table of %d keys\n", entry.Key(), rec.Len())
case tomledit.EntryRecords:
recs, _ := entry.Records()
fmt.Printf("%s is an array of %d tables\n", entry.Key(), len(recs))
}
}
}
Output: title = example database is a table of 2 keys server is an array of 2 tables
func (*Document) RoundTripVerifiedBytes ¶ added in v0.7.0
RoundTripVerifiedBytes renders the document and returns the rendered bytes only after they have proved they survive a round trip: they must parse, and re-rendering that parse must reproduce them byte for byte. It computes over the document alone and touches no file; the tomlfile subpackage writes a document through it, so nothing that fails the check is ever written.
A failure is an *Error of kind KindRoundTrip whose Offset field carries the byte at which the two renders disagree -- or, when the rendered bytes do not parse at all, the offset at which they stopped being TOML, with the parse error wrapped so errors.Is still reaches it. The diagnostic names file, the destination the bytes are meant for; an empty file names none.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte("# kept\n[server]\nport = 8080\n"))
if err != nil {
panic(err)
}
if err := doc.Set("server.port", 9090); err != nil {
panic(err)
}
// The bytes are handed out only after they survive a re-parse and a
// re-render.
out, err := doc.RoundTripVerifiedBytes("config.toml")
if err != nil {
panic(err)
}
fmt.Print(string(out))
}
Output: # kept [server] port = 9090
func (*Document) Set ¶
Set writes a value at the given path. A key the parent does not carry yet is created there; a path whose parent does not exist is an error, which is what SetCreate relaxes.
Supported value types: string, bool, int/int8-64, uint/uint8-64, float32/64, time.Time, LocalDateTime, LocalDate, LocalTime, []any and typed slices, map[string]any (written with its keys sorted), and []Pair (written in the order given). A value is a Go value: an AST node is not one, and passing a node -- including one resolved out of this or another document -- is refused as an unsupported type. Copying a value from one key to another is a read followed by a write of the value.
A value write touches value fragments and nothing else. Where the path names a key bound by a structural construct -- a [header] table, an array-of-tables, or a table another construct only implied, by a longer header or a dotted key -- the write is refused with KindWrongContainer: those change through a structural operation, or through an explicit Delete followed by the write. A key holding an inline table is a value, and setting it replaces that value wholesale, interior comments and spellings included.
The PARENT table's spelling decides only where the write goes, never whether it happens. A table no single node stands for is written where the document already spells it out: a key it already carries is replaced through the pair that binds it; a new key of a table a dotted key spelled out joins the same region as another dotted pair ("a.b = 1" gains "a.new = 5" beside it); and a new key of a table only a longer header implies arrives under the anchoring header the write gives that table -- the one TOML allows it.
Equality ¶
Set is a no-op if and only if the bytes it would write for the value are exactly the bytes the value fragment already carries. Nothing else counts as equal, and nothing equal is written: the node stays, with the spelling, the lexeme and the span it was parsed with, and the document records no edit. A value the library wrote before carries no lexeme, and the comparison is against its canonical rendering. A container value -- a map, a []Pair or a slice -- is compared as a whole against the stored container's whole byte range, and replaced WHOLESALE when it differs: the interior comments and spellings of the old container do not survive, which is what setting a container value means.
One rule, no special cases: NaN spellings, infinities, signed zeros, integer-versus-float, date-time offsets, string quoting and integer bases alike. Two consequences worth stating plainly. An idempotent tool that writes values it read back normalises a non-canonical spelling the first time it touches it -- 0x2A set to 42 becomes 42 -- and is byte-stable from then on. And a same-content write over a literal-quoted string converts it to basic quoting. What you Set is what the file says.
Deciding not to write is not undoing a write: a no-op Set never clears dirtiness an earlier edit recorded.
A NaN whose sign bit is set is refused with KindBadInput, since TOML has one NaN spelling and writing it would drop the sign; the ordinary NaN is accepted and writes "nan".
Text that is not valid UTF-8 is refused with KindBadInput too, wherever it appears: a string value, a string inside a container value, a map or []Pair key, and a key the path itself names. A TOML document is UTF-8, so such bytes would be written as replacement characters and read back as something else.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
port = 8080
`))
if err != nil {
panic(err)
}
err = doc.Set("server.port", 9090)
if err != nil {
panic(err)
}
port, err := doc.GetInt("server.port")
if err != nil {
panic(err)
}
fmt.Println(port)
}
Output: 9090
Example (OrderedInlineTable) ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte("name = \"example\"\n"))
if err != nil {
panic(err)
}
// A map is written with its keys sorted; []Pair keeps the order given.
if err := doc.Set("tls", []tomledit.Pair{
{Key: "cert", Value: "/etc/cert.pem"},
{Key: "key", Value: "/etc/key.pem"},
}); err != nil {
panic(err)
}
fmt.Print(string(doc.Bytes()))
}
Output: name = "example" tls = {cert = "/etc/cert.pem", key = "/etc/key.pem"}
func (*Document) SetBools ¶ added in v0.8.0
SetBools writes an array of booleans at the path, as Set does.
func (*Document) SetComment ¶
SetComment sets the inline comment on the node at the given path. The comment string should NOT include the "# " prefix -- it will be added automatically. An empty string removes the comment. For table paths, the comment is set on the table header line. Returns an error if the path does not exist or targets a member of an inline table (TOML forbids comments inside inline tables).
The text has to be something a comment can carry: valid UTF-8, and no control character other than a tab -- so a newline, a carriage return and U+0000 are each refused with KindBadInput, and the document is left as it was. These are the lexer's rules for reading a comment; text breaking them would render bytes the parser cannot read back.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte("[server]\nport = 8080\n"))
if err != nil {
panic(err)
}
if err := doc.SetComment("server.port", "default HTTP port"); err != nil {
panic(err)
}
if err := doc.SetLeadingComments("server.port", []string{
"The port to bind to.",
"Any value above 1024 needs no privileges.",
}); err != nil {
panic(err)
}
fmt.Print(string(doc.Bytes()))
}
Output: [server] # The port to bind to. # Any value above 1024 needs no privileges. port = 8080 # default HTTP port
func (*Document) SetCreate ¶
SetCreate is like Set but creates the intermediate tables the path names and the document does not carry, as standard [header] tables appended to the document. It refuses exactly what Set refuses, and is a no-op on exactly the same terms -- see Set's equality rule.
A refused write creates nothing. The value is converted before a single table is made, so a value the library will not write -- and a key it will not spell -- leaves the document byte-for-byte as it was, rather than leaving the headers of the path behind.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`title = "example"
`))
if err != nil {
panic(err)
}
// SetCreate auto-creates intermediate [database] table.
err = doc.SetCreate("database.host", "db.example.com")
if err != nil {
panic(err)
}
host, err := doc.GetString("database.host")
if err != nil {
panic(err)
}
fmt.Println(host)
}
Output: db.example.com
func (*Document) SetCreateBools ¶ added in v0.8.0
SetCreateBools writes an array of booleans at the path, as SetCreate does.
func (*Document) SetCreateFloats ¶ added in v0.8.0
SetCreateFloats writes an array of floats at the path, as SetCreate does.
func (*Document) SetCreateInlineTable ¶ added in v0.8.0
func (d *Document) SetCreateInlineTable(path string, table InlineTable) error
SetCreateInlineTable writes the inline table at the path, as SetCreate does, with its keys in the order they were added.
func (*Document) SetCreateInts ¶ added in v0.8.0
SetCreateInts writes an array of integers at the path, as SetCreate does.
func (*Document) SetCreateStrings ¶ added in v0.8.0
SetCreateStrings writes an array of strings at the path, as SetCreate does.
func (*Document) SetFloats ¶ added in v0.8.0
SetFloats writes an array of floats at the path, as Set does. A NaN whose sign bit is set is refused with KindBadInput, as Set refuses one.
func (*Document) SetInlineTable ¶ added in v0.8.0
func (d *Document) SetInlineTable(path string, table InlineTable) error
SetInlineTable writes the inline table at the path, as Set does, with its keys in the order they were added.
func (*Document) SetInts ¶ added in v0.8.0
SetInts writes an array of integers at the path, as Set does.
func (*Document) SetLeadingComments ¶
SetLeadingComments sets the leading comment lines on the node at the given path. Each string should NOT include the "# " prefix -- it will be added automatically. For table paths, the comments are set on the table header. Returns an error if the path does not exist.
Each element is ONE comment line, and each is held to what SetComment holds its text to: valid UTF-8, no control character other than a tab. An element carrying a newline would be a second line it does not get to open, so it is refused with KindBadInput -- and one refused element refuses the whole call, leaving the node's comments as they were.
A nil or empty slice removes the leading comments. An empty STRING element is not removal: it writes a "# " comment line with no content.
func (*Document) SetStrings ¶ added in v0.8.0
SetStrings writes an array of strings at the path, as Set does.
func (*Document) Span ¶
func (n *Document) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*Document) Validate ¶
Validate checks the document against the descriptor and reports every independent violation it finds, in document order, as one aggregate error (see Errors): an unknown key or table, a value of a kind the field refuses, a value the field cannot hold exactly, and a required key the document does not carry. Validation continues across siblings but never descends below a construct it has already refused, so one broken table cannot bury the document in diagnostics from its interior.
A descriptor that does not describe anything -- an array field with no Elem, a table field with no Table, a sub-descriptor on a kind that has no use for one -- is refused before the document is looked at.
Validate reports nil when the document satisfies the descriptor.
Example ¶
package main
import (
"errors"
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`[server]
host = "localhost"
prot = 8080
`))
if err != nil {
panic(err)
}
spec := &tomledit.Spec{Fields: map[string]tomledit.Field{
"server": {Kind: tomledit.FieldKindTable, Required: true, Table: &tomledit.Spec{
Fields: map[string]tomledit.Field{
"host": {Kind: tomledit.FieldKindString, Required: true},
"port": {Kind: tomledit.FieldKindInteger, Required: true},
},
}},
}}
err = doc.Validate(spec)
fmt.Println(errors.Is(err, tomledit.ErrUnknownKey))
fmt.Println(errors.Is(err, tomledit.ErrMissingKey))
// Every independent violation is collected, in document order.
var all *tomledit.Errors
if errors.As(err, &all) {
for _, e := range all.Unwrap() {
var diag *tomledit.Error
if errors.As(e, &diag) {
fmt.Printf("%s: %s\n", diag.Kind, diag.Path)
}
}
}
}
Output: true true unknown key: server.prot missing key: server.port
func (*Document) Walk ¶
Walk is the SYNTACTIC traversal: it walks the syntax tree, in the order the file writes it, and hands the visitor the concrete nodes it finds -- each with the spelling, the trivia and the span it was written with. A value written as an inline table arrives as an *InlineTableNode and one written under a header arrives as the header's children, because that is what the two files contain.
The LOGICAL traversal is the read-layer: Root returns the document's records and entries with the spellings folded away, and a consumer walks it by recursing over Record.Entries. Use Walk to ask what the file contains and how it is written; use the read-layer to ask what the document means.
Walk visits every key-value pair in the document in order, calling fn with the dot-path and the value node. Tables and array-of-tables are walked into (their children are visited), not yielded as standalone entries. Inline tables and arrays are yielded first, then their children are recursed into. Comment nodes are not visited: a comment reaches the visitor as the trivia of the node it is attached to.
The mode parameter controls which nodes are visited:
- WalkLeaves: only scalar values (containers are recursed but not yielded)
- WalkAll: containers AND their children are yielded
The path uses dot-separated keys with bracket indices for array-of-tables entries (e.g. "servers[0].host"). Return ErrSkipTable from fn to skip the children of the current inline table or array. Return any other non-nil error to stop the walk immediately.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte(`name = "example"
[server]
host = "localhost"
port = 8080
`))
if err != nil {
panic(err)
}
err = doc.Walk(func(path string, node tomledit.Node) error {
fmt.Printf("%s = %v\n", path, node.(tomledit.Scalar).Value())
return nil
}, tomledit.WalkLeaves)
if err != nil {
panic(err)
}
}
Output: name = example server.host = localhost server.port = 8080
type Entry ¶
type Entry struct {
// contains filtered or unexported fields
}
Entry is one key of a record: the key, where it was written, and what it holds. Entries are values; holding one does not keep the document alive in any special way, and like the records they come from they are snapshots.
func (Entry) KeySpan ¶
KeySpan returns the source range of the key itself -- the one part of the construct the entry is anchored to that names it.
func (Entry) Node ¶
Node returns the value node the entry holds, and whether it holds one. It answers for entries of kind EntryValue -- a scalar or a plain array -- and reports false for every other kind: a record's own backing construct is Record.Node's answer, and an array-of-tables has no single node at all, only the entries Records returns.
func (Entry) Record ¶
Record returns the record this entry holds, for an entry of kind EntryRecord: a table in any spelling. It reports false for any other kind.
func (Entry) Records ¶
Records returns the entries of the array-of-tables this entry holds, for an entry of kind EntryRecords. The returned slice is a copy: appending to it or reordering it changes nothing in the document. It reports false for any other kind.
func (Entry) RecordsSpan ¶
RecordsSpan returns the synthesized range of an array-of-tables: from the first entry's header to the end of the last entry's content, or to the end of its header when that entry has no content of its own. It is the zero Span for an entry of any other kind -- no concrete node covers a collection, so this is the only range that describes one.
type EntryKind ¶
type EntryKind int
EntryKind classifies what a read-layer entry holds.
const ( // EntryValue is a scalar or a plain array: one concrete value node. EntryValue EntryKind = iota // EntryRecord is a table in any spelling -- a header table, an inline // table, or a table implied by a longer header or a dotted key. EntryRecord // EntryRecords is an array-of-tables: the entries collected under one key, // in document order. EntryRecords )
type Error ¶
type Error struct {
Kind ErrorKind // what went wrong
Path string // document path, in this package's path syntax
Pos Position // primary source position; the zero Position when unknown
Span Span // source range of the construct concerned, when known
Message string // human-readable description, without position or file
File string // source file, empty when no file is known
Snippet string // source excerpt, parse-stage diagnostics
Expected string // type-mismatch detail: what the target accepts
Got string // type-mismatch detail: what the document carries
Value any // the offending value, for inexact and bad-input kinds
Keys []string // the direct child keys, for unknown-table diagnostics
Offset int // KindRoundTrip: first divergence in the rendered bytes
Fix string // what to do about it ("write port = 8080", "remove it"); empty when unknown
// contains filtered or unexported fields
}
Error is the one diagnostic type of this package. Parse, edit, and access failures are all reported as *Error, so a caller can match them structurally (errors.As) or by kind (errors.Is against the matching Err* sentinel) instead of by message text:
var diag *tomledit.Error
if errors.As(err, &diag) {
fmt.Println(diag.Kind, diag.Pos.Line, diag.Pos.Column)
}
Which fields are populated depends on the kind and on what the reporting site knew: Pos (line, column and byte offset) and Snippet are filled for every parse-stage diagnostic; Span only where the reporting site knows the extent of the construct it concerns, which for the parse stage means the parser's token diagnostics -- the lexer's and the duplicate-definition tracker's carry the zero Span. Path is filled for path-addressed operations, File whenever the document's origin is known (see ParseWithFileName and Collector.Read), and the remaining fields by the kinds documented on ErrorKind. A diagnostic reported through a Reader carries a line without a column (Pos.Column is 0), and Fix when the reporting site knows what to write instead. An unpopulated field carries its zero value.
Example ¶
package main
import (
"errors"
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
doc, err := tomledit.Parse([]byte("[server]\nport = \"8080\"\n"))
if err != nil {
panic(err)
}
_, err = doc.GetInt("server.port")
// Every document-dependent failure is an *Error: match a kind with
// errors.Is, read the structure with errors.As.
fmt.Println(errors.Is(err, tomledit.ErrTypeMismatch))
var diag *tomledit.Error
if errors.As(err, &diag) {
fmt.Printf("%d:%d %s: expected %s, got %s\n",
diag.Pos.Line, diag.Pos.Column, diag.Path, diag.Expected, diag.Got)
}
fmt.Println(err)
}
Output: true 2:8 server.port: expected integer, got string 2:8: server.port: expected integer, got string
func (*Error) Error ¶
Error renders the diagnostic in the compiler convention: the location, the path and the message, joined with ": ", with every absent part left out, and the fix, when there is one, after the message and "; ". The location is "file:line:column" when both the file and a position are known, "line:column" with a position alone, and the file alone when there is no position; a position without a column (Pos.Column 0) leaves the column out. A diagnostic reads as one of
config.toml:3:10: expected a value, got end of input 3:10: expected a value, got end of input config.toml: server.port: key not found config.toml:4: server.port: is missing; write port = <integer> server.port: key not found key not found
type ErrorKind ¶
type ErrorKind int
ErrorKind classifies a diagnostic. The set is closed: every kind has exactly one matching sentinel (KindSyntax has ErrSyntax, and so on), and a diagnostic reports itself equal to the sentinel of its own kind through errors.Is.
const ( // KindSyntax is a lexing or parsing failure. KindSyntax ErrorKind = iota // KindUnknownKey is a decoded key matching no field of the target. KindUnknownKey // KindUnknownTable is a decoded table or array-of-tables matching no // field of the target; the diagnostic's Keys field lists the direct // child keys of the offending construct. KindUnknownTable // KindMissingKey is a required key that the document does not carry. KindMissingKey // KindTypeMismatch is a value whose kind is not acceptable for the // target; the diagnostic's Expected and Got fields name both sides. KindTypeMismatch // KindInexact is a value the target cannot hold exactly: an out-of-range // integer, a lossy conversion, or a length mismatch. The diagnostic's // Value field carries the offending value. KindInexact // KindNotFound is a path naming nothing in the document. KindNotFound // KindBadPath is a syntactically invalid path. KindBadPath // KindWrongContainer is a path step that is structurally inapplicable: // a key on an array, an index on a scalar, a concrete-node operation on // a path that names no single node. KindWrongContainer // KindBadInput is an invalid input value to an editing operation. KindBadInput // KindConflict is an edit refused because it would produce an invalid // document. KindConflict // KindRoundTrip is a write whose rendered bytes did not survive a // re-parse; the diagnostic's Offset field carries the byte offset of the // first divergence. KindRoundTrip // KindInvalidValue is a value a hand-reading caller refused: present, and // usually of an accepted type, but not a value the caller allows (an empty // list, a false that only restates an absence, a name breaking a rule). // Reader.Errorf, Reader.ErrorfAt and Collector.Errorf report this kind. KindInvalidValue )
type Errors ¶
type Errors struct {
// contains filtered or unexported fields
}
Errors is an aggregate of diagnostics reported together: in document order from a decode, and sorted by file, line, path, and message from a Collector. It renders as its first diagnostic, so a call site that prints the error reads like a single failure; the whole list is reachable through Diagnostics and through errors.Unwrap semantics:
var all *tomledit.Errors
if errors.As(err, &all) {
for _, d := range all.Diagnostics() { ... }
}
errors.As with an *Error target yields the first diagnostic, and errors.Is against a kind sentinel matches when any contained diagnostic carries that kind. An empty aggregate is never returned: no diagnostics means a nil error.
func (*Errors) Diagnostics ¶ added in v0.6.0
Diagnostics returns every diagnostic of the aggregate, in its order. The slice is a copy; the diagnostics are the aggregate's own.
type Field ¶
type Field struct {
// Kind is what the value must be.
Kind FieldKind
// Required makes the absence of the key an error.
Required bool
// Elem describes the elements of an array. It is required for
// FieldKindArray and refused for every other kind.
Elem *Field
// Table describes the keys of a table. It is required for
// FieldKindTable and refused for every other kind.
Table *Spec
}
Field is one expected value of a descriptor.
type FieldKind ¶
type FieldKind int
FieldKind names what a descriptor field expects. There is one member per TOML value type -- the four date-time flavors kept apart, never unified -- plus the two containers and the total FieldKindAny.
const ( // FieldKindString expects a string. FieldKindString FieldKind = iota // FieldKindInteger expects an integer. FieldKindInteger // FieldKindFloat expects a float, or an integer that a float holds // exactly. FieldKindFloat // FieldKindBoolean expects a boolean. FieldKindBoolean // FieldKindOffsetDateTime expects a date-time carrying an offset. FieldKindOffsetDateTime // FieldKindLocalDateTime expects a date-time without an offset. FieldKindLocalDateTime // FieldKindLocalDate expects a date. FieldKindLocalDate // FieldKindLocalTime expects a time of day. FieldKindLocalTime // FieldKindArray expects an array, or an array-of-tables. The element // descriptor is required. FieldKindArray // FieldKindTable expects a table in any spelling -- a header table, an // inline table, or one implied by a longer header or a dotted key. The // field set is required. FieldKindTable // FieldKindAny expects anything, and reports nothing about what it holds. FieldKindAny )
type FloatNode ¶
type FloatNode struct {
// contains filtered or unexported fields
}
FloatNode represents a float value.
func (*FloatNode) AsLocalDate ¶
AsLocalDate reads the node through the conversion table.
func (*FloatNode) AsLocalDateTime ¶
func (n *FloatNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*FloatNode) AsLocalTime ¶
AsLocalTime reads the node through the conversion table.
func (*FloatNode) Comment ¶
func (n *FloatNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*FloatNode) LeadingComments ¶
func (n *FloatNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*FloatNode) Raw ¶
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*FloatNode) Span ¶
func (n *FloatNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type FormatConfig ¶
type FormatConfig struct {
IndentWidth int // spaces per indent level (default: 0, no indentation of values)
LineWidth int // max line width before arrays go multi-line (default: 80)
}
FormatConfig controls how the formatter normalizes TOML output. Use DefaultFormatConfig to get sensible defaults and WithIndentWidth or WithLineWidth to override specific settings.
func DefaultFormatConfig ¶
func DefaultFormatConfig() FormatConfig
DefaultFormatConfig returns a FormatConfig with sensible defaults.
type FormatOption ¶
type FormatOption func(*FormatConfig)
FormatOption is a functional option for configuring the formatter.
func WithIndentWidth ¶
func WithIndentWidth(n int) FormatOption
WithIndentWidth sets the number of spaces per indent level for values under table headers.
func WithLineWidth ¶
func WithLineWidth(n int) FormatOption
WithLineWidth sets the maximum line width before arrays are rendered in multi-line format.
type InlineTable ¶ added in v0.8.0
type InlineTable struct {
// contains filtered or unexported fields
}
InlineTable is an ordered inline table built from typed values, the input of SetInlineTable and SetCreateInlineTable. The zero value is an empty table. Each method adds one key and returns the table, so additions chain.
A key is a SINGLE key, taken verbatim -- never a path, as a Pair's Key is not. A duplicate key and a key or string that is not valid UTF-8 are not refused when added: the write that takes the table refuses them with KindBadInput, and writes nothing.
func (*InlineTable) Bool ¶ added in v0.8.0
func (t *InlineTable) Bool(key string, value bool) *InlineTable
Bool adds a boolean value under the key.
func (*InlineTable) Bools ¶ added in v0.8.0
func (t *InlineTable) Bools(key string, values []bool) *InlineTable
Bools adds an array of booleans under the key.
func (*InlineTable) Float ¶ added in v0.8.0
func (t *InlineTable) Float(key string, value float64) *InlineTable
Float adds a float value under the key.
func (*InlineTable) Floats ¶ added in v0.8.0
func (t *InlineTable) Floats(key string, values []float64) *InlineTable
Floats adds an array of floats under the key.
func (*InlineTable) Int ¶ added in v0.8.0
func (t *InlineTable) Int(key string, value int64) *InlineTable
Int adds an integer value under the key.
func (*InlineTable) Ints ¶ added in v0.8.0
func (t *InlineTable) Ints(key string, values []int64) *InlineTable
Ints adds an array of integers under the key.
func (*InlineTable) String ¶ added in v0.8.0
func (t *InlineTable) String(key, value string) *InlineTable
String adds a string value under the key.
func (*InlineTable) Strings ¶ added in v0.8.0
func (t *InlineTable) Strings(key string, values []string) *InlineTable
Strings adds an array of strings under the key.
func (*InlineTable) Table ¶ added in v0.8.0
func (t *InlineTable) Table(key string, table InlineTable) *InlineTable
Table adds a nested inline table under the key. The table is copied: keys added to it afterwards do not reach this one.
type InlineTableNode ¶
type InlineTableNode struct {
// contains filtered or unexported fields
}
InlineTableNode represents an inline table value.
func (*InlineTableNode) Children ¶
func (n *InlineTableNode) Children() []Node
Children returns the inline table's pairs in order, as a copy.
func (*InlineTableNode) Comment ¶
func (n *InlineTableNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*InlineTableNode) LeadingComments ¶
func (n *InlineTableNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*InlineTableNode) Raw ¶
func (n *InlineTableNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*InlineTableNode) Span ¶
func (n *InlineTableNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*InlineTableNode) Type ¶
func (n *InlineTableNode) Type() NodeType
Type returns NodeInlineTable.
type IntegerBase ¶
type IntegerBase int
IntegerBase indicates the numeric base for an integer node.
const ( IntegerDecimal IntegerBase = iota // IntegerDecimal is base-10 (e.g. 42). IntegerHex // IntegerHex is base-16 (e.g. 0xFF). IntegerOctal // IntegerOctal is base-8 (e.g. 0o77). IntegerBinary // IntegerBinary is base-2 (e.g. 0b1010). )
type IntegerNode ¶
type IntegerNode struct {
// contains filtered or unexported fields
}
IntegerNode represents an integer value.
func (*IntegerNode) AsBool ¶
func (n *IntegerNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*IntegerNode) AsFloat ¶
func (n *IntegerNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*IntegerNode) AsInt ¶
func (n *IntegerNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*IntegerNode) AsLocalDate ¶
func (n *IntegerNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*IntegerNode) AsLocalDateTime ¶
func (n *IntegerNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*IntegerNode) AsLocalTime ¶
func (n *IntegerNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*IntegerNode) AsString ¶
func (n *IntegerNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*IntegerNode) AsTime ¶
func (n *IntegerNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*IntegerNode) Base ¶
func (n *IntegerNode) Base() IntegerBase
Base returns the numeric base the integer was written in.
func (*IntegerNode) Comment ¶
func (n *IntegerNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*IntegerNode) LeadingComments ¶
func (n *IntegerNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*IntegerNode) Raw ¶
func (n *IntegerNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*IntegerNode) Span ¶
func (n *IntegerNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*IntegerNode) Value ¶
func (n *IntegerNode) Value() any
Value returns the integer value as int64.
type KeyNode ¶
type KeyNode struct {
// contains filtered or unexported fields
}
KeyNode represents a (possibly dotted) key.
func (*KeyNode) Comment ¶
func (n *KeyNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*KeyNode) LeadingComments ¶
func (n *KeyNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*KeyNode) Parts ¶
Parts returns the key's parts, decoded, as a copy: "a.b" reads as ["a", "b"], and a quoted part carries no quotes.
func (*KeyNode) Raw ¶
func (n *KeyNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*KeyNode) RawParts ¶
RawParts returns the source bytes of each part, as written, as a copy: the outer slice and every part in it. It is empty for a key created programmatically.
func (*KeyNode) Span ¶
func (n *KeyNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*KeyNode) Styles ¶
func (n *KeyNode) Styles() []StringStyle
Styles returns the quoting style of each part, as a copy.
type KeyValueNode ¶
type KeyValueNode struct {
// contains filtered or unexported fields
}
KeyValueNode represents a key = value pair.
func (*KeyValueNode) Comment ¶
func (n *KeyValueNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*KeyValueNode) LeadingComments ¶
func (n *KeyValueNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*KeyValueNode) Raw ¶
func (n *KeyValueNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*KeyValueNode) Span ¶
func (n *KeyValueNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type LocalDate ¶
type LocalDate struct {
Year, Month, Day int
}
LocalDate represents a TOML local date.
type LocalDateNode ¶
type LocalDateNode struct {
// contains filtered or unexported fields
}
LocalDateNode represents a local date value.
func (*LocalDateNode) AsBool ¶
func (n *LocalDateNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*LocalDateNode) AsFloat ¶
func (n *LocalDateNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*LocalDateNode) AsInt ¶
func (n *LocalDateNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*LocalDateNode) AsLocalDate ¶
func (n *LocalDateNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*LocalDateNode) AsLocalDateTime ¶
func (n *LocalDateNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*LocalDateNode) AsLocalTime ¶
func (n *LocalDateNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*LocalDateNode) AsString ¶
func (n *LocalDateNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*LocalDateNode) AsTime ¶
func (n *LocalDateNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*LocalDateNode) Comment ¶
func (n *LocalDateNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*LocalDateNode) LeadingComments ¶
func (n *LocalDateNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*LocalDateNode) Raw ¶
func (n *LocalDateNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*LocalDateNode) Span ¶
func (n *LocalDateNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*LocalDateNode) Value ¶
func (n *LocalDateNode) Value() any
Value returns the LocalDate value.
type LocalDateTime ¶
LocalDateTime represents a TOML local date-time (no timezone).
type LocalDateTimeNode ¶
type LocalDateTimeNode struct {
// contains filtered or unexported fields
}
LocalDateTimeNode represents a local date-time value (no timezone).
func (*LocalDateTimeNode) AsBool ¶
func (n *LocalDateTimeNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*LocalDateTimeNode) AsFloat ¶
func (n *LocalDateTimeNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*LocalDateTimeNode) AsInt ¶
func (n *LocalDateTimeNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*LocalDateTimeNode) AsLocalDate ¶
func (n *LocalDateTimeNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*LocalDateTimeNode) AsLocalDateTime ¶
func (n *LocalDateTimeNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*LocalDateTimeNode) AsLocalTime ¶
func (n *LocalDateTimeNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*LocalDateTimeNode) AsString ¶
func (n *LocalDateTimeNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*LocalDateTimeNode) AsTime ¶
func (n *LocalDateTimeNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*LocalDateTimeNode) Comment ¶
func (n *LocalDateTimeNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*LocalDateTimeNode) LeadingComments ¶
func (n *LocalDateTimeNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*LocalDateTimeNode) Raw ¶
func (n *LocalDateTimeNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*LocalDateTimeNode) Span ¶
func (n *LocalDateTimeNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*LocalDateTimeNode) Type ¶
func (n *LocalDateTimeNode) Type() NodeType
Type returns NodeLocalDateTime.
func (*LocalDateTimeNode) Value ¶
func (n *LocalDateTimeNode) Value() any
Value returns the LocalDateTime value.
type LocalTimeNode ¶
type LocalTimeNode struct {
// contains filtered or unexported fields
}
LocalTimeNode represents a local time value.
func (*LocalTimeNode) AsBool ¶
func (n *LocalTimeNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*LocalTimeNode) AsFloat ¶
func (n *LocalTimeNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*LocalTimeNode) AsInt ¶
func (n *LocalTimeNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*LocalTimeNode) AsLocalDate ¶
func (n *LocalTimeNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*LocalTimeNode) AsLocalDateTime ¶
func (n *LocalTimeNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*LocalTimeNode) AsLocalTime ¶
func (n *LocalTimeNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*LocalTimeNode) AsString ¶
func (n *LocalTimeNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*LocalTimeNode) AsTime ¶
func (n *LocalTimeNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*LocalTimeNode) Comment ¶
func (n *LocalTimeNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*LocalTimeNode) LeadingComments ¶
func (n *LocalTimeNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*LocalTimeNode) Raw ¶
func (n *LocalTimeNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*LocalTimeNode) Span ¶
func (n *LocalTimeNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*LocalTimeNode) Value ¶
func (n *LocalTimeNode) Value() any
Value returns the LocalTime value.
type Node ¶
type Node interface {
Type() NodeType
// Comment returns the node's inline comment as text: without the "#" and
// the whitespace around it, and empty when there is none.
Comment() string
// LeadingComments returns the comment lines written above the node as
// text, each without its "#", its trailing newline and the whitespace
// around them.
LeadingComments() []string
// Raw returns the node's bytes as written, comments and spacing included,
// as a copy: writing into what it returns changes neither what a later
// read answers nor what the document renders.
Raw() []byte
// Span returns the source range the node covered in the most recent
// Parse. Nodes created programmatically by an edit return the
// zero Span (IsValid reports false). Edits do not update spans; see
// the Span type documentation for the exact policy.
Span() Span
// contains filtered or unexported methods
}
Node is the interface implemented by all AST nodes. Every node carries its original raw bytes, its trivia (whitespace and comments) and its source range. It carries no value: only the value-carrying kinds do, and those implement Scalar. Implementation is restricted to this package.
type NodeType ¶
type NodeType int
NodeType identifies the kind of AST node.
const ( NodeDocument NodeType = iota // NodeDocument is the root document node. NodeTable // NodeTable is a [table] header node. NodeArrayTable // NodeArrayTable is an [[array-table]] header node. NodeKeyValue // NodeKeyValue is a key = value pair. NodeKey // NodeKey is a (possibly dotted) key. NodeString // NodeString is a string value. NodeInteger // NodeInteger is an integer value. NodeFloat // NodeFloat is a float value. NodeBoolean // NodeBoolean is a boolean value. NodeDateTime // NodeDateTime is an offset date-time value. NodeLocalDateTime // NodeLocalDateTime is a local date-time value (no timezone). NodeLocalDate // NodeLocalDate is a local date value. NodeLocalTime // NodeLocalTime is a local time value. NodeArray // NodeArray is an array value. NodeInlineTable // NodeInlineTable is an inline table value. NodeComment // NodeComment is a standalone comment line. )
type Pair ¶
Pair is one key of an ordered inline table, the input any value-writing operation takes as a []Pair: Set, SetCreate, AppendToArray, EnsureDefaults, and the element and value positions inside them.
Key is a SINGLE key, taken verbatim -- never a path. "a.b" is one key spelled with a dot in it, written quoted, and not a table "a" holding a "b". Use a nested []Pair (or a Default's path) to reach into a table. A duplicate key and a key that is not valid UTF-8 are each refused with KindBadInput when the operation converts the value.
A map[string]any is the unordered alternative: its keys are written in sorted order, where a []Pair is written in the order given.
type PathSegment ¶
type PathSegment struct {
Kind SegmentKind
Key string // the key to look up, for SegmentKey
Index int // the element position, for SegmentIndex; negative counts from the end
}
PathSegment is one step of a parsed path. Kind says which of the remaining fields carries the step: Key for SegmentKey, Index for SegmentIndex.
func ParsePath ¶
func ParsePath(path string) ([]PathSegment, error)
ParsePath parses a path in this package's path syntax into its segments. Every path-addressed operation in the package -- Resolve, Lookup, Set, Delete, the comment setters -- reads its path with it, so a path that parses here is spelled the way they expect.
Syntax:
- A dot separates key segments: "server.host".
- Brackets hold an index, which may be negative: "items[0]", "items[-1]".
- Brackets may follow each other for nested arrays: "matrix[0][1]".
- A quoted segment carries a key verbatim, so a key with dots or spaces stays one segment: `server."host.name"`. The quotes belong to the path syntax, not to the key.
- A backslash escapes the next byte: `host\.name` is the single key "host.name", and inside a quoted segment `\"` is a quote.
The empty path names nothing and is reported as a KindBadPath diagnostic; so are an unclosed bracket or quote, a non-numeric index, and a trailing dot.
Example ¶
package main
import (
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
segs, err := tomledit.ParsePath(`servers[0]."host.name"`)
if err != nil {
panic(err)
}
for _, s := range segs {
if s.Kind == tomledit.SegmentKey {
fmt.Printf("key %q\n", s.Key)
} else {
fmt.Printf("index %d\n", s.Index)
}
}
// JoinPath is the quoting authority: it writes back what ParsePath read.
fmt.Println(tomledit.JoinPath(segs))
}
Output: key "servers" index 0 key "host.name" servers[0]."host.name"
type Position ¶
type Position struct {
Line int // 1-based line number
Column int // 1-based byte column within the line
Offset int // 0-based byte offset from the start of the source
}
Position is a location in TOML source: a 1-based line and column plus the 0-based byte offset of the same point. Columns count bytes (not runes), matching the convention used by diagnostics and tokens.
type Reader ¶ added in v0.6.0
type Reader struct {
// contains filtered or unexported fields
}
Reader reads one table of a document by hand, recording every problem it finds in a Collector. Every key read through Get or an accessor built on it becomes known; Done reports the keys nobody read as unknown.
The accessors share one convention. A required key that is absent is a KindMissingKey diagnostic at the table's own line; a value of the wrong type is a KindTypeMismatch diagnostic at the value's line; and each diagnostic's Fix says what to write, from the write argument the caller passes ("port = <integer>", "at least one [[command]] entry"). An accessor that reports a problem returns the zero value and false.
Example ¶
package main
import (
"errors"
"fmt"
tomledit "github.com/stricttools/go-toml-edit"
)
func main() {
// One collector gathers the problems of every file read through it.
var c tomledit.Collector
r := c.Read("tool.toml", []byte(`name = "fmt"
mode = "fast"
[[command]]
help = "Formats files."
retries = "three"
`))
if r != nil {
name, _ := r.Str("name", true, `name = "<name>"`)
fmt.Println(name)
for _, cmd := range r.Tables("command") {
cmd.Str("name", true, `name = "<command name>"`)
cmd.Str("help", true, `help = "<one sentence>"`)
cmd.Integer("retries", false, "retries = <integer>")
cmd.Done() // every key nobody read is unknown
}
r.Done()
}
// Err sorts by file, line, path, and message.
var all *tomledit.Errors
if errors.As(c.Err(), &all) {
for _, d := range all.Diagnostics() {
fmt.Println(d)
}
}
}
Output: fmt tool.toml:2: mode: is not a key of this table; remove it, or write one of command, name tool.toml:4: command[0].name: is missing; write name = "<command name>" tool.toml:6: command[0].retries: is a string, not an integer; write retries = <integer>
func (*Reader) Boolean ¶ added in v0.6.0
Boolean reads a key written as true or false; the fix it reports is "write <key> = true or <key> = false". It returns the value and whether the key was read without a problem.
func (*Reader) Child ¶ added in v0.6.0
Child returns a Reader over a table value at the path the caller names, sharing this Reader's file and collector: the form for a table met inside a list the caller walks itself. It returns nil when v is not a table.
func (*Reader) Done ¶ added in v0.6.0
func (r *Reader) Done()
Done reports every key of the table nobody read, in the order the keys were written: "is not a key of this table", with the fix "remove it, or write one of <the keys read, sorted>", or "remove it" when nothing was read. A key holding a table or a list of [[header]] tables is a KindUnknownTable diagnostic listing its direct child keys, any other a KindUnknownKey one -- the kinds the decode engine reports for the same refusals.
func (*Reader) Errorf ¶ added in v0.6.0
Errorf records a KindInvalidValue diagnostic at one key of the table: its line (the table's own when the key is absent) and its path.
func (*Reader) ErrorfAt ¶ added in v0.6.0
ErrorfAt records a KindInvalidValue diagnostic in the Reader's file at a line and path the caller names: an element of a list, say, or a key the table derives from another.
func (*Reader) Flag ¶ added in v0.6.0
Flag reads an optional key whose only value is true. Its absence is its other meaning, so false is a second spelling of the absence and is refused as a KindInvalidValue diagnostic: "is false, and its absence already means that; write <key> = true, or remove the key".
func (*Reader) Get ¶ added in v0.6.0
Get reads a key: it becomes known, and when the table holds it its line is recorded in Lines. It returns the key's value and whether the table holds it.
func (*Reader) KeyPath ¶ added in v0.6.0
KeyPath returns the path of one key of the table: "command[0].flag" for the key "flag" of the table "command[0]".
func (*Reader) Keys ¶ added in v0.6.0
Keys returns the table's keys in the order they were first written. Listing them marks none known.
func (*Reader) Line ¶ added in v0.6.0
Line returns the line a key was written on, or the table's own line when the table does not hold the key.
func (*Reader) Lines ¶ added in v0.6.0
Lines returns the line of every key read so far. The map is the Reader's own and fills as keys are read, so a caller may keep it before reading.
func (*Reader) Missing ¶ added in v0.6.0
Missing records that a required key is absent: "is missing; write <write>", a KindMissingKey diagnostic at the table's own line.
func (*Reader) NonEmpty ¶ added in v0.6.0
NonEmpty refuses a key holding an empty list, as a KindInvalidValue diagnostic: "is empty; write <write>". It neither reads the key nor reports its absence or its type.
func (*Reader) Path ¶ added in v0.6.0
Path returns the table's own key path, empty for the top-level table.
func (*Reader) Peek ¶ added in v0.6.0
Peek returns the value of a key without reading it: the key does not become known, and its line is not recorded.
func (*Reader) PeekTrue ¶ added in v0.6.0
PeekTrue reports whether the table holds key written as true, without reading it.
func (*Reader) Str ¶ added in v0.6.0
Str reads a string key. A required key that is absent is reported missing; an optional one returns false without a diagnostic.
func (*Reader) StrList ¶ added in v0.6.0
StrList reads a key holding a list of strings. An element that is not a string is reported at its own line and path (key[i]), and the read returns false.
func (*Reader) Sub ¶ added in v0.6.0
Sub reads a key holding one table and returns a Reader over it. An absent key returns nil without a diagnostic; a value that is not a table is reported, with the fix "write [<header>]", and returns nil.
func (*Reader) TableLine ¶ added in v0.6.0
TableLine returns the line the table was written on: its own header, the inline table spelling it, or the construct that implied it; line 1 for the top-level table.
type Record ¶
type Record struct {
// contains filtered or unexported fields
}
Record is a table of the read-layer: an ordered set of entries, whatever the spelling that produced it. Records are read-only; edit through the document's path API.
A record is a snapshot of the document as it was when the layer was built. It stays valid until the next mutation of that document, and reading it afterwards reports what the document held before the change. Mutating a document while iterating its entries is unspecified.
func (*Record) Node ¶
Node returns the concrete node backing the record, and whether one does. A table written as a header or as an inline table is backed by that construct, an array-of-tables entry by its own [[header]], and the root record by the document. A record no single node stands for -- one implied by a longer header or by a dotted key -- reports false; it exists in the layer, and nothing in the source stands for it alone.
type Scalar ¶
type Scalar interface {
Node
// Value returns the node's payload as the Go type the conversion table
// gives that TOML type: string, int64, float64, bool, time.Time, or one of
// LocalDateTime, LocalDate and LocalTime.
Value() any
// AsString reads the node as a string. Only a string is one.
AsString() (string, error)
// AsInt reads the node as an int64. Only an integer is one: a float is
// never an integer, however whole it is written.
AsInt() (int64, error)
// AsFloat reads the node as a float64: a float verbatim, and an integer
// the target holds exactly.
AsFloat() (float64, error)
// AsBool reads the node as a bool. Only a boolean is one.
AsBool() (bool, error)
// AsTime reads the node as a time.Time: an offset date-time verbatim, and
// a local date-time or local date read as UTC, because a time.Time target
// declares that intent. A local time carries no date and is refused.
//
// A STRING is refused too, with KindTypeMismatch, even one spelling a valid
// RFC 3339 timestamp. A struct field of type time.Time answers the same
// way: both surfaces read the one conversion table, and no target decodes
// itself. To read a string as a time, read it with AsString and parse it.
AsTime() (time.Time, error)
// AsLocalDateTime reads the node as a LocalDateTime. Only a local
// date-time is one.
AsLocalDateTime() (LocalDateTime, error)
// AsLocalDate reads the node as a LocalDate. Only a local date is one.
AsLocalDate() (LocalDate, error)
// AsLocalTime reads the node as a LocalTime. Only a local time is one.
AsLocalTime() (LocalTime, error)
}
Scalar is the sub-interface of Node the value-carrying node kinds implement: strings, integers, floats, booleans and the four date-time flavors. Every other kind holds structure rather than a value -- a document, a table, an array-of-tables, an array, an inline table, a key, a key-value pair, a comment -- and is read through its own accessors instead.
type SegmentKind ¶
type SegmentKind int
SegmentKind distinguishes the two kinds of path step: a lookup by key and a lookup by position.
const ( // SegmentKey addresses a child by name. SegmentKey SegmentKind = iota // SegmentIndex addresses an element by position. A negative index counts // from the end, so -1 is the last element. SegmentIndex )
type Span ¶
Span is the half-open source range [Start, End) covered by a node: Start is the position of the node's first byte, End is the position immediately after its last byte.
Spans reflect the most recent Parse. Edit operations (Set, Delete, RenameKey, Merge, ...) do not update spans: nodes created programmatically carry the zero Span (IsValid reports false), and nodes whose content was edited keep the span from the last parse. To obtain fresh spans after editing, serialize with Bytes and re-Parse the result.
type Spec ¶
type Spec struct {
// Fields describes the keys the table may carry, by name.
Fields map[string]Field
// Dynamic describes every key Fields does not name: it is how a table of
// arbitrary keys, all of one shape, is spelled. A nil Dynamic means no
// other key is permitted, and is the one place in a descriptor where nil
// carries a meaning of its own.
Dynamic *Field
}
Spec is the descriptor of a table: the keys it may carry, and whether keys beyond them are permitted at all.
Field values are built whole and assigned into Fields -- map elements are not addressable, so mutating one in place is not a supported spelling:
spec := &tomledit.Spec{Fields: map[string]tomledit.Field{
"host": {Kind: tomledit.FieldKindString, Required: true},
"port": {Kind: tomledit.FieldKindInteger},
}}
A missing-key diagnostic is reported in lexicographic key order, because map iteration has no order to report in.
type StringNode ¶
type StringNode struct {
// contains filtered or unexported fields
}
StringNode represents a string value.
func (*StringNode) AsBool ¶
func (n *StringNode) AsBool() (bool, error)
AsBool reads the node through the conversion table.
func (*StringNode) AsFloat ¶
func (n *StringNode) AsFloat() (float64, error)
AsFloat reads the node through the conversion table.
func (*StringNode) AsInt ¶
func (n *StringNode) AsInt() (int64, error)
AsInt reads the node through the conversion table.
func (*StringNode) AsLocalDate ¶
func (n *StringNode) AsLocalDate() (LocalDate, error)
AsLocalDate reads the node through the conversion table.
func (*StringNode) AsLocalDateTime ¶
func (n *StringNode) AsLocalDateTime() (LocalDateTime, error)
AsLocalDateTime reads the node through the conversion table.
func (*StringNode) AsLocalTime ¶
func (n *StringNode) AsLocalTime() (LocalTime, error)
AsLocalTime reads the node through the conversion table.
func (*StringNode) AsString ¶
func (n *StringNode) AsString() (string, error)
AsString reads the node through the conversion table.
func (*StringNode) AsTime ¶
func (n *StringNode) AsTime() (time.Time, error)
AsTime reads the node through the conversion table.
func (*StringNode) Comment ¶
func (n *StringNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*StringNode) LeadingComments ¶
func (n *StringNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*StringNode) Raw ¶
func (n *StringNode) Raw() []byte
Raw returns a copy of the bytes the value was written as -- its lexeme, with the spelling the document used. A value the library wrote carries the bytes it rendered.
func (*StringNode) Span ¶
func (n *StringNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
func (*StringNode) Style ¶
func (n *StringNode) Style() StringStyle
Style returns the quoting style the string was written in.
type StringStyle ¶
type StringStyle int
StringStyle indicates the quoting style for a string node.
const ( StringBasic StringStyle = iota // StringBasic is a double-quoted string ("..."). StringLiteral // StringLiteral is a single-quoted string ('...'). StringMultiLineBasic // StringMultiLineBasic is a triple-double-quoted string ("""..."""). StringMultiLineLiteral // StringMultiLineLiteral is a triple-single-quoted string (”'...”'). )
type TableNode ¶
type TableNode struct {
// contains filtered or unexported fields
}
TableNode represents a [table] header and its children.
func (*TableNode) Children ¶
Children returns the constructs written under this header, in document order, as a copy.
func (*TableNode) Comment ¶
func (n *TableNode) Comment() string
Comment returns the text of the node's inline comment, without the "#" and the whitespace around it: a node written `x = 1 # note` answers "note". A node with no inline comment answers the empty string. Raw carries the bytes as written, for a caller that needs them exactly.
func (*TableNode) LeadingComments ¶
func (n *TableNode) LeadingComments() []string
LeadingComments returns the text of each comment line written above the node, in order, each without its "#", its trailing newline and the whitespace around them. A node with no leading comments answers nil, the same spelling the path-based GetLeadingComments uses. Raw carries the bytes as written, for a caller that needs them exactly.
func (*TableNode) Raw ¶
func (n *TableNode) Raw() []byte
Raw answers with a copy. The serializer splices through rawBytes instead, so the copy is paid for only by a caller that asked for the bytes.
func (*TableNode) Span ¶
func (n *TableNode) Span() Span
Span returns the half-open source range the node occupied in the document it was parsed from, each end carrying a 1-based line and column and a 0-based byte offset. Spans reflect the most recent Parse: an edit does not recompute them, and a node the library built rather than parsed carries the zero span, for which Span.IsValid reports false.
type Value ¶ added in v0.6.0
type Value struct {
// contains filtered or unexported fields
}
Value is one value of a document as a Reader sees it: its type, the line it was written on, and what it holds. It is a view over the document's own nodes and records, not a copy of them.
func (Value) Bool ¶ added in v0.6.0
Bool returns the boolean a ValueBoolean holds, and false for any other type.
func (Value) Describe ¶ added in v0.6.0
Describe writes the value for a diagnostic: a string quoted, any other value by what it is ("an integer", "a table").
func (Value) Float ¶ added in v0.6.0
Float returns the float a ValueFloat holds, and 0 for any other type.
func (Value) Int ¶ added in v0.6.0
Int returns the integer a ValueInteger holds, and 0 for any other type.
func (Value) List ¶ added in v0.6.0
List returns the elements a ValueList holds, in document order, and nil for any other type. The entries of an array of tables are ValueTable elements, each on the line of its own [[header]].
func (Value) Node ¶ added in v0.6.0
Node returns the scalar or array node backing the value, and whether one does. A table and an array of tables report false: their contents are read through a Reader (Reader.Sub, Reader.Tables, Reader.Child).
type ValueType ¶ added in v0.6.0
type ValueType int
ValueType classifies a value a Reader hands back. A table in any spelling is ValueTable; a plain array and an array of tables -- whether written as [[header]] entries or as an array of inline tables -- are both ValueList.
type WalkMode ¶
type WalkMode int
WalkMode controls which nodes the Walk visitor function is called for. Both modes traverse the syntax tree; they differ only in whether the container nodes standing between the keys and the scalars are handed to the visitor.
const ( // WalkLeaves visits only scalar (leaf) values. Container nodes // (InlineTableNode, ArrayNode) are not passed to fn, but their // children are still recursed into. WalkLeaves WalkMode = iota // WalkAll visits containers (inline tables, arrays) AND their children. // The visitor is called for every node. WalkAll )
Source Files
¶
- access.go
- accessors.go
- comments.go
- convert.go
- cursor.go
- decode.go
- defaults.go
- diff.go
- document.go
- edit.go
- errors.go
- format.go
- fragments.go
- iterate.go
- lexer.go
- merge.go
- mutate.go
- node.go
- node_types.go
- parser.go
- path.go
- reader.go
- record.go
- render.go
- renderers.go
- resolve.go
- roundtrip.go
- scalar_accessors.go
- span.go
- spec.go
- structural.go
- token.go
- tomledit.go
- typed_setters.go
- walk.go