Skip to content
go-toml-edit
Edit
On this page

#go-toml-edit

Zero-dep TOML editing library for Go with comment preservation

#API Reference

The API reference documents every exported type, function, and method in the tomledit package, including the parser, document editing operations such as Set, Delete, and RenameKey, the read-layer types Record and Entry, the cursor navigation API, comment reading and writing methods, document diffing and merging, strict decoding into Go values and against a descriptor, and the Format function for canonical output styling.

#.

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.

#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. - Files: [ParseFile] remembers the filename for later diagnostics, and [Document.WriteFile] writes atomically after checking that the rendered bytes survive a round trip.

#Added

Go go
const Added    ChangeKind = iota // Added means the key exists in b but not in a.

#Removed

Go go
const Removed                    // Removed means the key exists in a but not in b.

#Modified

Go go
const Modified                   // Modified means the key exists in both but with different values.

#KindSyntax

Go go
const KindSyntax ErrorKind = iota

KindSyntax is a lexing or parsing failure.

#KindUnknownKey

Go go
const KindUnknownKey

KindUnknownKey is a decoded key matching no field of the target.

#KindUnknownTable

Go go
const KindUnknownTable

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.

#KindMissingKey

Go go
const KindMissingKey

KindMissingKey is a required key that the document does not carry.

#KindTypeMismatch

Go go
const KindTypeMismatch

KindTypeMismatch is a value whose kind is not acceptable for the target; the diagnostic's Expected and Got fields name both sides.

#KindInexact

Go go
const KindInexact

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.

#KindNotFound

Go go
const KindNotFound

KindNotFound is a path naming nothing in the document.

#KindBadPath

Go go
const KindBadPath

KindBadPath is a syntactically invalid path.

#KindWrongContainer

Go go
const KindWrongContainer

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.

#KindBadInput

Go go
const KindBadInput

KindBadInput is an invalid input value to an editing operation.

#KindConflict

Go go
const KindConflict

KindConflict is an edit refused because it would produce an invalid document.

#KindRoundTrip

Go go
const KindRoundTrip

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.

#NodeDocument

Go go
const NodeDocument      NodeType = iota // NodeDocument is the root document node.

#NodeTable

Go go
const NodeTable                         // NodeTable is a [table] header node.

#NodeArrayTable

Go go
const NodeArrayTable                    // NodeArrayTable is an [[array-table]] header node.

#NodeKeyValue

Go go
const NodeKeyValue                      // NodeKeyValue is a key = value pair.

#NodeKey

Go go
const NodeKey                           // NodeKey is a (possibly dotted) key.

#NodeString

Go go
const NodeString                        // NodeString is a string value.

#NodeInteger

Go go
const NodeInteger                       // NodeInteger is an integer value.

#NodeFloat

Go go
const NodeFloat                         // NodeFloat is a float value.

#NodeBoolean

Go go
const NodeBoolean                       // NodeBoolean is a boolean value.

#NodeDateTime

Go go
const NodeDateTime                      // NodeDateTime is an offset date-time value.

#NodeLocalDateTime

Go go
const NodeLocalDateTime                 // NodeLocalDateTime is a local date-time value (no timezone).

#NodeLocalDate

Go go
const NodeLocalDate                     // NodeLocalDate is a local date value.

#NodeLocalTime

Go go
const NodeLocalTime                     // NodeLocalTime is a local time value.

#NodeArray

Go go
const NodeArray                         // NodeArray is an array value.

#NodeInlineTable

Go go
const NodeInlineTable                   // NodeInlineTable is an inline table value.

#NodeComment

Go go
const NodeComment                       // NodeComment is a standalone comment line.

#StringBasic

Go go
const StringBasic            StringStyle = iota // StringBasic is a double-quoted string ("...").

#StringLiteral

Go go
const StringLiteral                             // StringLiteral is a single-quoted string ('...').

#StringMultiLineBasic

Go go
const StringMultiLineBasic                      // StringMultiLineBasic is a triple-double-quoted string ("""...""").

#StringMultiLineLiteral

Go go
const StringMultiLineLiteral                    // StringMultiLineLiteral is a triple-single-quoted string ('''...''').

#IntegerDecimal

Go go
const IntegerDecimal IntegerBase = iota // IntegerDecimal is base-10 (e.g. 42).

#IntegerHex

Go go
const IntegerHex                        // IntegerHex is base-16 (e.g. 0xFF).

#IntegerOctal

Go go
const IntegerOctal                      // IntegerOctal is base-8 (e.g. 0o77).

#IntegerBinary

Go go
const IntegerBinary                     // IntegerBinary is base-2 (e.g. 0b1010).

#SegmentKey

Go go
const SegmentKey SegmentKind = iota

SegmentKey addresses a child by name.

#SegmentIndex

Go go
const SegmentIndex

SegmentIndex addresses an element by position. A negative index counts from the end, so -1 is the last element.

#EntryValue

Go go
const EntryValue EntryKind = iota

EntryValue is a scalar or a plain array: one concrete value node.

#EntryRecord

Go go
const EntryRecord

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.

#EntryRecords

Go go
const EntryRecords

EntryRecords is an array-of-tables: the entries collected under one key, in document order.

#FieldKindString

Go go
const FieldKindString FieldKind = iota

FieldKindString expects a string.

#FieldKindInteger

Go go
const FieldKindInteger

FieldKindInteger expects an integer.

#FieldKindFloat

Go go
const FieldKindFloat

FieldKindFloat expects a float, or an integer that a float holds exactly.

#FieldKindBoolean

Go go
const FieldKindBoolean

FieldKindBoolean expects a boolean.

#FieldKindOffsetDateTime

Go go
const FieldKindOffsetDateTime

FieldKindOffsetDateTime expects a date-time carrying an offset.

#FieldKindLocalDateTime

Go go
const FieldKindLocalDateTime

FieldKindLocalDateTime expects a date-time without an offset.

#FieldKindLocalDate

Go go
const FieldKindLocalDate

FieldKindLocalDate expects a date.

#FieldKindLocalTime

Go go
const FieldKindLocalTime

FieldKindLocalTime expects a time of day.

#FieldKindArray

Go go
const FieldKindArray

FieldKindArray expects an array, or an array-of-tables. The element descriptor is required.

#FieldKindTable

Go go
const FieldKindTable

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.

#FieldKindAny

Go go
const FieldKindAny

FieldKindAny expects anything, and reports nothing about what it holds.

#WalkLeaves

Go go
const WalkLeaves WalkMode = iota

WalkLeaves visits only scalar (leaf) values. Container nodes (InlineTableNode, ArrayNode) are not passed to fn, but their children are still recursed into.

#WalkAll

Go go
const WalkAll

WalkAll visits containers (inline tables, arrays) AND their children. The visitor is called for every node.

#ErrSyntax

Go go
var ErrSyntax         error = kindError(KindSyntax)         // a lexing or parsing failure

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.

#ErrUnknownKey

Go go
var ErrUnknownKey     error = kindError(KindUnknownKey)     // a key matching no field of the target

#ErrUnknownTable

Go go
var ErrUnknownTable   error = kindError(KindUnknownTable)   // a table matching no field of the target

#ErrMissingKey

Go go
var ErrMissingKey     error = kindError(KindMissingKey)     // a required key the document does not carry

#ErrTypeMismatch

Go go
var ErrTypeMismatch   error = kindError(KindTypeMismatch)   // a value whose kind the target refuses

#ErrInexact

Go go
var ErrInexact        error = kindError(KindInexact)        // a value the target cannot hold exactly

#ErrNotFound

Go go
var ErrNotFound       error = kindError(KindNotFound)       // a path naming nothing

#ErrBadPath

Go go
var ErrBadPath        error = kindError(KindBadPath)        // a syntactically invalid path

#ErrWrongContainer

Go go
var ErrWrongContainer error = kindError(KindWrongContainer) // a structurally inapplicable path step

#ErrBadInput

Go go
var ErrBadInput       error = kindError(KindBadInput)       // an invalid input to an editing operation

#ErrConflict

Go go
var ErrConflict       error = kindError(KindConflict)       // an edit that would produce an invalid document

#ErrRoundTrip

Go go
var ErrRoundTrip      error = kindError(KindRoundTrip)      // rendered bytes that did not survive a re-parse

#ErrSkipTable

Go go
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.

#Cursor

Go go
type Cursor struct

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.

#Default

Go go
type Default struct

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.

#ChangeKind

Go go
type ChangeKind int

ChangeKind identifies the type of difference between two documents.

#Change

Go go
type Change struct

Change represents a single difference between two documents. OldValue is nil for Added changes; NewValue is nil for Removed changes.

#Pair

Go go
type Pair struct

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.

#ErrorKind

Go go
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.

#Error

Go go
type Error struct

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 ParseFile), and the remaining fields by the kinds documented on ErrorKind. An unpopulated field carries its zero value.

#Errors

Go go
type Errors struct

Errors is an aggregate of diagnostics reported together, in document order. 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 errors.Unwrap semantics:

var all *tomledit.Errors if errors.As(err, &all) { for _, d := range all.Unwrap() { ... } }

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.

#FormatConfig

Go go
type FormatConfig struct

FormatConfig controls how the formatter normalizes TOML output. Use DefaultFormatConfig to get sensible defaults and WithIndentWidth or WithLineWidth to override specific settings.

#FormatOption

Go go
type FormatOption func(*FormatConfig)

FormatOption is a functional option for configuring the formatter.

#NodeType

Go go
type NodeType int

NodeType identifies the kind of AST node.

#Node

Go go
type Node interface

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.

#Scalar

Go go
type Scalar interface

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.

#LocalDateTime

Go go
type LocalDateTime struct

LocalDateTime represents a TOML local date-time (no timezone).

#LocalDate

Go go
type LocalDate struct

LocalDate represents a TOML local date.

#LocalTime

Go go
type LocalTime struct

LocalTime represents a TOML local time.

#StringStyle

Go go
type StringStyle int

StringStyle indicates the quoting style for a string node.

#IntegerBase

Go go
type IntegerBase int

IntegerBase indicates the numeric base for an integer node.

#Document

Go go
type Document struct

Document is the root node of a TOML document.

#TableNode

Go go
type TableNode struct

TableNode represents a [table] header and its children.

#ArrayTableNode

Go go
type ArrayTableNode struct

ArrayTableNode represents an [[array-table]] header and its children.

#KeyValueNode

Go go
type KeyValueNode struct

KeyValueNode represents a key = value pair.

#KeyNode

Go go
type KeyNode struct

KeyNode represents a (possibly dotted) key.

#StringNode

Go go
type StringNode struct

StringNode represents a string value.

#IntegerNode

Go go
type IntegerNode struct

IntegerNode represents an integer value.

#FloatNode

Go go
type FloatNode struct

FloatNode represents a float value.

#BooleanNode

Go go
type BooleanNode struct

BooleanNode represents a boolean value.

#DateTimeNode

Go go
type DateTimeNode struct

DateTimeNode represents an offset date-time value.

#LocalDateTimeNode

Go go
type LocalDateTimeNode struct

LocalDateTimeNode represents a local date-time value (no timezone).

#LocalDateNode

Go go
type LocalDateNode struct

LocalDateNode represents a local date value.

#LocalTimeNode

Go go
type LocalTimeNode struct

LocalTimeNode represents a local time value.

#ArrayNode

Go go
type ArrayNode struct

ArrayNode represents an array value.

#InlineTableNode

Go go
type InlineTableNode struct

InlineTableNode represents an inline table value.

#CommentNode

Go go
type CommentNode struct

CommentNode represents a standalone comment line.

#SegmentKind

Go go
type SegmentKind int

SegmentKind distinguishes the two kinds of path step: a lookup by key and a lookup by position.

#PathSegment

Go go
type PathSegment struct

PathSegment is one step of a parsed path. Kind says which of the remaining fields carries the step: Key for SegmentKey, Index for SegmentIndex.

#EntryKind

Go go
type EntryKind int

EntryKind classifies what a read-layer entry holds.

#Record

Go go
type Record struct

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.

#Entry

Go go
type Entry struct

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.

#Position

Go go
type Position struct

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.

#Span

Go go
type Span struct

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.

#FieldKind

Go go
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.

#Spec

Go go
type Spec struct

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.

#Field

Go go
type Field struct

Field is one expected value of a descriptor.

#WalkMode

Go go
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.

#Diff

Go go
func Diff(a, b *Document) []Change

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.

#DefaultFormatConfig

Go go
func DefaultFormatConfig() FormatConfig

DefaultFormatConfig returns a FormatConfig with sensible defaults.

#WithIndentWidth

Go go
func WithIndentWidth(n int) FormatOption

WithIndentWidth sets the number of spaces per indent level for values under table headers.

#WithLineWidth

Go go
func WithLineWidth(n int) FormatOption

WithLineWidth sets the maximum line width before arrays are rendered in multi-line format.

#Parse

Go go
func Parse(src []byte) (*Document, error)

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.

#ParseFile

Go go
func ParseFile(path string) (*Document, error)

ParseFile reads the file at path and parses it. The document remembers the filename, so every diagnostic it later produces -- from parsing, access or editing -- names the file it came from.

A file that cannot be read is reported as the underlying read error (an fs.PathError, matchable with errors.Is against fs.ErrNotExist and the rest), not as an Error: nothing was parsed, so there is nothing to diagnose.

#ParsePath

Go go
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.

#JoinPath

Go go
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.

#QuoteString

Go go
func QuoteString(s string) string

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.

#QuoteKey

Go go
func QuoteKey(s string) string

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.

#FormatFloat

Go go
func FormatFloat(f float64) string

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.)

#FieldAny

Go go
func FieldAny() Field { return Field{Kind: FieldKindAny} }

FieldAny returns the descriptor of a value of any kind: the explicit spelling of "whatever is here, I am not describing it".

#Document.GetString

Go go
func (d *Document) GetString(path string) (string, error) { return getAs[string](d, path) }

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.

#Document.GetInt

Go go
func (d *Document) GetInt(path string) (int64, error) { return getAs[int64](d, path) }

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.

#Document.GetBool

Go go
func (d *Document) GetBool(path string) (bool, error) { return getAs[bool](d, path) }

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.

#Document.GetFloat

Go go
func (d *Document) GetFloat(path string) (float64, error) { return getAs[float64](d, path) }

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.

#Document.GetTime

Go go
func (d *Document) GetTime(path string) (time.Time, error) { return getAs[time.Time](d, path) }

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.

#Document.Children

Go go
func (n *Document) Children() []Node { return copyNodes(n.children) }

Children returns the document's top-level constructs in document order, as a copy.

#TableNode.KeyPath

Go go
func (n *TableNode) KeyPath() []string { return copyStrings(n.keyPath) }

KeyPath returns the parts of the header's key, decoded, as a copy.

#TableNode.Children

Go go
func (n *TableNode) Children() []Node { return copyNodes(n.children) }

Children returns the constructs written under this header, in document order, as a copy.

#ArrayTableNode.KeyPath

Go go
func (n *ArrayTableNode) KeyPath() []string { return copyStrings(n.keyPath) }

KeyPath returns the parts of the header's key, decoded, as a copy.

#ArrayTableNode.Children

Go go
func (n *ArrayTableNode) Children() []Node { return copyNodes(n.children) }

Children returns the constructs written under this header, in document order, as a copy.

#KeyValueNode.Key

Go go
func (n *KeyValueNode) Key() *KeyNode { return n.key }

Key returns the pair's key node.

#KeyValueNode.Val

Go go
func (n *KeyValueNode) Val() Node { return n.val }

Val returns the pair's value node.

#KeyNode.Parts

Go go
func (n *KeyNode) Parts() []string { return copyStrings(n.parts) }

Parts returns the key's parts, decoded, as a copy: "a.b" reads as ["a", "b"], and a quoted part carries no quotes.

#KeyNode.RawParts

Go go
func (n *KeyNode) RawParts() [][]byte { return copyByteSlices(n.frag.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.

#KeyNode.Styles

Go go
func (n *KeyNode) Styles() []StringStyle

Styles returns the quoting style of each part, as a copy.

#StringNode.Style

Go go
func (n *StringNode) Style() StringStyle { return n.style }

Style returns the quoting style the string was written in.

#IntegerNode.Base

Go go
func (n *IntegerNode) Base() IntegerBase { return n.base }

Base returns the numeric base the integer was written in.

#ArrayNode.Elements

Go go
func (n *ArrayNode) Elements() []Node { return copyNodes(n.elements) }

Elements returns the array's elements in order, as a copy.

#InlineTableNode.Children

Go go
func (n *InlineTableNode) Children() []Node { return copyNodes(n.children) }

Children returns the inline table's pairs in order, as a copy.

#CommentNode.Text

Go go
func (n *CommentNode) Text() string { return n.text }

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.

#Document.SetComment

Go go
func (d *Document) SetComment(path string, comment string) error

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.

#Document.SetLeadingComments

Go go
func (d *Document) SetLeadingComments(path string, comments []string) error

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.

#Document.GetComment

Go go
func (d *Document) GetComment(path string) (string, error)

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.

#Document.GetLeadingComments

Go go
func (d *Document) GetLeadingComments(path string) ([]string, error)

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.

#valueKind.String

Go go
func (k valueKind) String() string

String returns the human-readable name of the value kind.

#Document.Key

Go go
func (d *Document) Key(name string) *Cursor

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.

#Cursor.Key

Go go
func (c *Cursor) Key(name string) *Cursor

Key navigates to a named child within the current scope.

#Cursor.At

Go go
func (c *Cursor) At(index int) *Cursor

At navigates to an array index. Supports negative indices.

#Cursor.Node

Go go
func (c *Cursor) Node() 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.

#Cursor.Err

Go go
func (c *Cursor) Err() error

Err returns the first error encountered during navigation, as an *Error naming the document's file when it has one.

#Cursor.String

Go go
func (c *Cursor) String() (string, error) { return cursorAs[string](c) }

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.

#Cursor.Int

Go go
func (c *Cursor) Int() (int64, error) { return cursorAs[int64](c) }

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.

#Cursor.Bool

Go go
func (c *Cursor) Bool() (bool, error) { return cursorAs[bool](c) }

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.

#Cursor.Float

Go go
func (c *Cursor) Float() (float64, error) { return cursorAs[float64](c) }

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.

#Cursor.Time

Go go
func (c *Cursor) Time() (time.Time, error) { return cursorAs[time.Time](c) }

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.

#Document.EnsureDefaults

Go go
func (d *Document) EnsureDefaults(defaults []Default) (added []string, err error)

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.

#ChangeKind.String

Go go
func (k ChangeKind) String() string

String returns the human-readable name of the change kind.

#Document.Set

Go go
func (d *Document) Set(path string, value any) error

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.

#Document.SetCreate

Go go
func (d *Document) SetCreate(path string, value any) error

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.

#Document.Delete

Go go
func (d *Document) Delete(path string) error

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.

#Document.RenameKey

Go go
func (d *Document) RenameKey(path string, newKey string) error

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.

#Document.NewTable

Go go
func (d *Document) NewTable(path string) error

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.

#Document.NewArrayTable

Go go
func (d *Document) NewArrayTable(path string) error

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.

#ErrorKind.String

Go go
func (k ErrorKind) String() string

String returns the human-readable name of the kind.

#kindError.Error

Go go
func (k kindError) Error() string { return "tomledit: " + ErrorKind(k).String() }

#Error.Error

Go go
func (e *Error) Error() string

Error renders the diagnostic in the compiler convention: the location, the path and the message, joined with ": ", with every absent part left out. 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 -- so 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 server.port: key not found key not found

#Error.Unwrap

Go go
func (e *Error) Unwrap() error { return e.err }

Unwrap returns the underlying error this diagnostic reports, or nil.

#Error.Is

Go go
func (e *Error) Is(target error) bool

Is reports whether the diagnostic matches target, which is true for the kind sentinel of its own kind.

#Errors.Error

Go go
func (e *Errors) Error() string

Error renders the first diagnostic of the aggregate, or "no diagnostics" when it holds none. This package never produces an empty aggregate, but the type is exported, so rendering a zero value reports rather than panics.

#Errors.Unwrap

Go go
func (e *Errors) Unwrap() []error

Unwrap returns every diagnostic of the aggregate, in document order.

#Document.Format

Go go
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.

#Cursor.Items

Go go
func (c *Cursor) Items() iter.Seq2[int, Node]

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.

#Cursor.Len

Go go
func (c *Cursor) Len() int

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.

#Document.Merge

Go go
func (d *Document) Merge(other *Document) error

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.

#StringNode.Raw

Go go
func (n *StringNode) Raw() []byte { return copyBytes(n.val.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.

#IntegerNode.Raw

Go go
func (n *IntegerNode) Raw() []byte { return copyBytes(n.val.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.

#FloatNode.Raw

Go go
func (n *FloatNode) Raw() []byte { return copyBytes(n.val.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.

#BooleanNode.Raw

Go go
func (n *BooleanNode) Raw() []byte { return copyBytes(n.val.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.

#DateTimeNode.Raw

Go go
func (n *DateTimeNode) Raw() []byte { return copyBytes(n.val.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.

#LocalDateTimeNode.Raw

Go go
func (n *LocalDateTimeNode) Raw() []byte { return copyBytes(n.val.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.

#LocalDateNode.Raw

Go go
func (n *LocalDateNode) Raw() []byte { return copyBytes(n.val.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.

#LocalTimeNode.Raw

Go go
func (n *LocalTimeNode) Raw() []byte { return copyBytes(n.val.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.

#NodeType.String

Go go
func (n NodeType) String() string

String returns the human-readable name of the node type.

#nodeBase.Raw

Go go
func (n *nodeBase) 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.

#nodeBase.Span

Go go
func (n *nodeBase) 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.

#nodeBase.Comment

Go go
func (n *nodeBase) 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.

#nodeBase.LeadingComments

Go go
func (n *nodeBase) 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.

#Document.Type

Go go
func (n *Document) Type() NodeType { return NodeDocument }

Type returns NodeDocument.

#TableNode.Type

Go go
func (n *TableNode) Type() NodeType { return NodeTable }

Type returns NodeTable.

#ArrayTableNode.Type

Go go
func (n *ArrayTableNode) Type() NodeType { return NodeArrayTable }

Type returns NodeArrayTable.

#KeyValueNode.Type

Go go
func (n *KeyValueNode) Type() NodeType { return NodeKeyValue }

Type returns NodeKeyValue.

#KeyNode.Type

Go go
func (n *KeyNode) Type() NodeType { return NodeKey }

Type returns NodeKey.

#StringNode.Type

Go go
func (n *StringNode) Type() NodeType { return NodeString }

Type returns NodeString.

#StringNode.Value

Go go
func (n *StringNode) Value() any { return n.val.get() }

Value returns the decoded string.

#IntegerNode.Type

Go go
func (n *IntegerNode) Type() NodeType { return NodeInteger }

Type returns NodeInteger.

#IntegerNode.Value

Go go
func (n *IntegerNode) Value() any { return n.val.get() }

Value returns the integer value as int64.

#FloatNode.Type

Go go
func (n *FloatNode) Type() NodeType { return NodeFloat }

Type returns NodeFloat.

#FloatNode.Value

Go go
func (n *FloatNode) Value() any { return n.val.get() }

Value returns the float value as float64.

#BooleanNode.Type

Go go
func (n *BooleanNode) Type() NodeType { return NodeBoolean }

Type returns NodeBoolean.

#BooleanNode.Value

Go go
func (n *BooleanNode) Value() any { return n.val.get() }

Value returns the boolean value.

#DateTimeNode.Type

Go go
func (n *DateTimeNode) Type() NodeType { return NodeDateTime }

Type returns NodeDateTime.

#DateTimeNode.Value

Go go
func (n *DateTimeNode) Value() any { return n.val.get() }

Value returns the time.Time value.

#LocalDateTimeNode.Type

Go go
func (n *LocalDateTimeNode) Type() NodeType { return NodeLocalDateTime }

Type returns NodeLocalDateTime.

#LocalDateTimeNode.Value

Go go
func (n *LocalDateTimeNode) Value() any { return n.val.get() }

Value returns the LocalDateTime value.

#LocalDateNode.Type

Go go
func (n *LocalDateNode) Type() NodeType { return NodeLocalDate }

Type returns NodeLocalDate.

#LocalDateNode.Value

Go go
func (n *LocalDateNode) Value() any { return n.val.get() }

Value returns the LocalDate value.

#LocalTimeNode.Type

Go go
func (n *LocalTimeNode) Type() NodeType { return NodeLocalTime }

Type returns NodeLocalTime.

#LocalTimeNode.Value

Go go
func (n *LocalTimeNode) Value() any { return n.val.get() }

Value returns the LocalTime value.

#ArrayNode.Type

Go go
func (n *ArrayNode) Type() NodeType { return NodeArray }

Type returns NodeArray.

#InlineTableNode.Type

Go go
func (n *InlineTableNode) Type() NodeType { return NodeInlineTable }

Type returns NodeInlineTable.

#CommentNode.Type

Go go
func (n *CommentNode) Type() NodeType { return NodeComment }

Type returns NodeComment.

#EntryKind.String

Go go
func (k EntryKind) String() string

String returns the human-readable name of the entry kind.

#Document.Root

Go go
func (d *Document) Root() *Record

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.

#Record.Len

Go go
func (r *Record) Len() int { return len(r.entries) }

Len returns the number of entries in the record.

#Record.Span

Go go
func (r *Record) Span() Span { return r.span }

Span returns the record's anchoring range: its own header where it has one, the inline table that spells it out, or the construct that implied it.

#Record.Node

Go go
func (r *Record) Node() (Node, bool)

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.

#Record.Entries

Go go
func (r *Record) Entries() iter.Seq[Entry]

Entries iterates the record's entries in first-appearance order.

#Record.Get

Go go
func (r *Record) Get(key string) (Entry, bool)

Get returns the entry with the given key, and whether the record has one.

#Entry.Key

Go go
func (e Entry) Key() string { return e.key }

Key returns the entry's key, decoded (quotes and escapes resolved).

#Entry.KeySpan

Go go
func (e Entry) KeySpan() Span { return e.keySpan }

KeySpan returns the source range of the key itself -- the one part of the construct the entry is anchored to that names it.

#Entry.Kind

Go go
func (e Entry) Kind() EntryKind { return e.kind }

Kind reports what the entry holds.

#Entry.Record

Go go
func (e Entry) Record() (*Record, bool)

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.

#Entry.Records

Go go
func (e Entry) Records() ([]*Record, bool)

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.

#Entry.RecordsSpan

Go go
func (e Entry) RecordsSpan() Span { return e.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.

#Entry.Node

Go go
func (e Entry) Node() (Node, bool)

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.

#Document.Bytes

Go go
func (d *Document) Bytes() []byte

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.

#Document.Resolve

Go go
func (d *Document) Resolve(path string) (Node, error)

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.

#Document.Lookup

Go go
func (d *Document) Lookup(path string) (Node, bool)

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.

#Document.Has

Go go
func (d *Document) Has(path string) bool

Has reports whether the path names a concrete node, on the same terms as Lookup.

#StringNode.AsString

Go go
func (n *StringNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#StringNode.AsInt

Go go
func (n *StringNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#StringNode.AsFloat

Go go
func (n *StringNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#StringNode.AsBool

Go go
func (n *StringNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#StringNode.AsTime

Go go
func (n *StringNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#StringNode.AsLocalDateTime

Go go
func (n *StringNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#StringNode.AsLocalDate

Go go
func (n *StringNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#StringNode.AsLocalTime

Go go
func (n *StringNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#IntegerNode.AsString

Go go
func (n *IntegerNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#IntegerNode.AsInt

Go go
func (n *IntegerNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#IntegerNode.AsFloat

Go go
func (n *IntegerNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#IntegerNode.AsBool

Go go
func (n *IntegerNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#IntegerNode.AsTime

Go go
func (n *IntegerNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#IntegerNode.AsLocalDateTime

Go go
func (n *IntegerNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#IntegerNode.AsLocalDate

Go go
func (n *IntegerNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#IntegerNode.AsLocalTime

Go go
func (n *IntegerNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#FloatNode.AsString

Go go
func (n *FloatNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#FloatNode.AsInt

Go go
func (n *FloatNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#FloatNode.AsFloat

Go go
func (n *FloatNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#FloatNode.AsBool

Go go
func (n *FloatNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#FloatNode.AsTime

Go go
func (n *FloatNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#FloatNode.AsLocalDateTime

Go go
func (n *FloatNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#FloatNode.AsLocalDate

Go go
func (n *FloatNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#FloatNode.AsLocalTime

Go go
func (n *FloatNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#BooleanNode.AsString

Go go
func (n *BooleanNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#BooleanNode.AsInt

Go go
func (n *BooleanNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#BooleanNode.AsFloat

Go go
func (n *BooleanNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#BooleanNode.AsBool

Go go
func (n *BooleanNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#BooleanNode.AsTime

Go go
func (n *BooleanNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#BooleanNode.AsLocalDateTime

Go go
func (n *BooleanNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#BooleanNode.AsLocalDate

Go go
func (n *BooleanNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#BooleanNode.AsLocalTime

Go go
func (n *BooleanNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#DateTimeNode.AsString

Go go
func (n *DateTimeNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#DateTimeNode.AsInt

Go go
func (n *DateTimeNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#DateTimeNode.AsFloat

Go go
func (n *DateTimeNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#DateTimeNode.AsBool

Go go
func (n *DateTimeNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#DateTimeNode.AsTime

Go go
func (n *DateTimeNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#DateTimeNode.AsLocalDateTime

Go go
func (n *DateTimeNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#DateTimeNode.AsLocalDate

Go go
func (n *DateTimeNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#DateTimeNode.AsLocalTime

Go go
func (n *DateTimeNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#LocalDateTimeNode.AsString

Go go
func (n *LocalDateTimeNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#LocalDateTimeNode.AsInt

Go go
func (n *LocalDateTimeNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#LocalDateTimeNode.AsFloat

Go go
func (n *LocalDateTimeNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#LocalDateTimeNode.AsBool

Go go
func (n *LocalDateTimeNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#LocalDateTimeNode.AsTime

Go go
func (n *LocalDateTimeNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#LocalDateTimeNode.AsLocalDateTime

Go go
func (n *LocalDateTimeNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#LocalDateTimeNode.AsLocalDate

Go go
func (n *LocalDateTimeNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#LocalDateTimeNode.AsLocalTime

Go go
func (n *LocalDateTimeNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#LocalDateNode.AsString

Go go
func (n *LocalDateNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#LocalDateNode.AsInt

Go go
func (n *LocalDateNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#LocalDateNode.AsFloat

Go go
func (n *LocalDateNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#LocalDateNode.AsBool

Go go
func (n *LocalDateNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#LocalDateNode.AsTime

Go go
func (n *LocalDateNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#LocalDateNode.AsLocalDateTime

Go go
func (n *LocalDateNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#LocalDateNode.AsLocalDate

Go go
func (n *LocalDateNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#LocalDateNode.AsLocalTime

Go go
func (n *LocalDateNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#LocalTimeNode.AsString

Go go
func (n *LocalTimeNode) AsString() (string, error) { return nodeAs[string](n) }

AsString reads the node through the conversion table.

#LocalTimeNode.AsInt

Go go
func (n *LocalTimeNode) AsInt() (int64, error) { return nodeAs[int64](n) }

AsInt reads the node through the conversion table.

#LocalTimeNode.AsFloat

Go go
func (n *LocalTimeNode) AsFloat() (float64, error) { return nodeAs[float64](n) }

AsFloat reads the node through the conversion table.

#LocalTimeNode.AsBool

Go go
func (n *LocalTimeNode) AsBool() (bool, error) { return nodeAs[bool](n) }

AsBool reads the node through the conversion table.

#LocalTimeNode.AsTime

Go go
func (n *LocalTimeNode) AsTime() (time.Time, error) { return nodeAs[time.Time](n) }

AsTime reads the node through the conversion table.

#LocalTimeNode.AsLocalDateTime

Go go
func (n *LocalTimeNode) AsLocalDateTime() (LocalDateTime, error) { return nodeAs[LocalDateTime](n) }

AsLocalDateTime reads the node through the conversion table.

#LocalTimeNode.AsLocalDate

Go go
func (n *LocalTimeNode) AsLocalDate() (LocalDate, error) { return nodeAs[LocalDate](n) }

AsLocalDate reads the node through the conversion table.

#LocalTimeNode.AsLocalTime

Go go
func (n *LocalTimeNode) AsLocalTime() (LocalTime, error) { return nodeAs[LocalTime](n) }

AsLocalTime reads the node through the conversion table.

#Position.IsValid

Go go
func (p Position) IsValid() bool { return p.Line >= 1 }

IsValid reports whether the position was populated from source (Line >= 1). The zero Position is invalid.

#Span.IsValid

Go go
func (s Span) IsValid() bool { return s.Start.IsValid() }

IsValid reports whether the span was populated by Parse. The zero Span is invalid; it is returned by nodes created programmatically by an edit.

#FieldKind.String

Go go
func (k FieldKind) String() string

String returns the human-readable name of the field kind.

#Document.Validate

Go go
func (d *Document) Validate(spec *Spec) error

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.

#Document.DecodeSpec

Go go
func (d *Document) DecodeSpec(spec *Spec) (map[string]any, error)

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.

#Document.PermuteChildren

Go go
func (d *Document) PermuteChildren(path string, order []int) error

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.

#Document.AppendToArray

Go go
func (d *Document) AppendToArray(path string, value any) error

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.

#Document.RemoveFromArray

Go go
func (d *Document) RemoveFromArray(path string, index int) error

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.

#tokenType.String

Go go
func (t tokenType) String() string

String returns the internal name of the token type. It is the lexer's own vocabulary, for test failures and debugging output; a diagnostic a caller reads renders describe instead.

#Document.Walk

Go go
func (d *Document) Walk(fn func(path string, node Node) error, mode WalkMode) error

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.

#Document.WriteFile

Go go
func (d *Document) WriteFile(path string) error

WriteFile renders the document and writes it to path, atomically, and only after the rendered bytes have proved they survive a round trip.

The write is atomic in the sense a rename gives: the bytes go to a temporary file in the DESTINATION'S OWN DIRECTORY -- so the rename cannot cross a filesystem boundary -- and that file replaces the destination in one step. A failure anywhere before the rename leaves the destination exactly as it was and no temporary file behind. (A machine that loses power mid-write is the kernel's business, not this function's.)

The round trip is checked before anything is written: the rendered bytes must parse, and re-rendering that parse must reproduce them byte for byte. A failure is an *Error of kind KindRoundTrip whose Offset field carries the byte at which the two 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. Nothing is written in either case.

The destination's file mode is preserved when it already exists. A new file is created with mode 0o644, as an ordinary create would, so the process umask applies to it.

A filesystem failure is reported as the underlying error -- an fs.PathError, matchable with errors.Is -- and not as an Error, on the same terms as ParseFile: nothing about the document is wrong, so there is nothing to diagnose about it.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search