API reference¶
Every exported symbol in gitlab.com/phpboyscout/go/yamldoc, what it returns, and
what happens when the input is wrong.
Import path and version:
Go 1.26 or later. One dependency, github.com/goccy/go-yaml.
The generated godoc is on pkg.go.dev; this page adds the behaviour that a signature does not show.
Parse¶
Reads a YAML stream and returns the file it describes. src may hold several
documents separated by ---.
Parsing never fails because a document contains something that cannot be
round-tripped safely — those are reported by File.Unsupported
and left for you to judge. It fails when the bytes are not YAML the parser
accepts:
| Input | Result |
|---|---|
| Well-formed YAML | *File, nil |
| Tab-indented YAML | error: found character '\t' that cannot start any token |
| A duplicate mapping key | error: mapping key "a" already defined at [1:1] |
| Empty input | a *File with one document that has no root mapping |
| A comments-only file | a *File with one document whose body is the comment group |
The two empty-ish cases parse successfully and then refuse every path operation
with ErrNotFound, because there is no mapping to
address into. yamldoc edits documents; it does not create them from nothing.
Duplicate keys are worth calling out because the source says otherwise: the
ReasonUndecodable doc comment lists a duplicate mapping key as something
reported through Unsupported. With goccy/go-yaml v1.19.2 the parser rejects it
first, so Parse returns an error and you never see an Unsupported entry for it.
File¶
A parsed YAML stream. Not safe for concurrent use — Set and Remove mutate a
tree that every *Document from the same *File shares, and nothing in the
library locks. Confine a *File to one goroutine, or guard it yourself.
File.Documents¶
The file's documents, in the order they appear in the source.
The slice is a copy, so reordering or truncating it changes nothing; the
*Document values inside it are shared, which is what makes editing one visible
in Bytes. There is no way to add or delete a document — the set is fixed by the
source.
The library assigns no meaning to document order. Treating later documents as overriding earlier ones is a caller's convention; see Mechanism, not policy.
File.Unsupported¶
Constructs found in the source that cannot be safely round-tripped. Empty means the file can be emitted.
The slice is a copy. See Unsupported constructs for what lands here and why.
File.Bytes¶
Re-emits the whole file, all documents.
On any error the returned slice is nil — this call never hands back damaged
bytes. Three outcomes:
| Condition | Returns |
|---|---|
| Nothing unsupported, output re-parses and decodes | the bytes, nil |
Unsupported() is non-empty |
nil, ErrUnsupported wrapping the first report |
| Output does not parse, or parses but does not decode | nil, ErrEmitInvalid |
The second check is the one that is easy to miss: the output is fully decoded, not just parsed, because a dangling alias is invisible to a parser and only surfaces when aliases are resolved.
Emission converges immediately. Emitting a file, re-parsing it and emitting again produces identical bytes, so a tool that writes on every keystroke does not accumulate drift.
Document¶
One document within a *File. All four methods take a dotted path; see
Path syntax for what a path can and cannot address.
Document.Index¶
The document's position in the file, starting at zero.
Document.Get¶
The node at path, read-only. ok is false — with no error and no distinction
between the cases — when:
- the path is malformed (empty, or with an empty segment);
- the document has no root mapping (an empty file, or a root that is a sequence or a scalar);
- any segment does not exist;
- a segment before the last one is not a mapping.
A Node points into the parsed document rather than copying out of it. A Node
held across a later Set on the same path reads the new value; one held
across a Remove keeps reading the value it had, now detached from the document
and about to be garbage. Fetch the node when you need it rather than caching it
across edits.
Document.Keys¶
The immediate child keys of the mapping at path, in document order. An empty
path returns the document's top-level keys.
ok is false when the path does not resolve, or resolves to something that is not
a mapping (a scalar, a sequence, an alias).
Two things the list includes that callers tend not to expect:
- A merge key is a key. A mapping written
<<: *basereports<<alongside its ordinary keys. - Keys that no path can address. A key containing a dot is listed verbatim as
a.b, and there is no escape that letsGet,SetorRemovereach it.
Keys whose value is an empty collection are included: emptiness is a value.
Document.Set¶
Assigns value at path, creating the key and any missing intermediate mappings.
value is anything yaml.Marshal accepts — a scalar of any width, a string,
bool, nil, a map, a slice, or a struct with yaml tags. Scalars replacing
scalars are mutated in place; everything else is marshalled and spliced in.
| Situation | Behaviour |
|---|---|
| Existing scalar, scalar value | mutated in place; inline comment and quoting style survive |
| Existing collection, any value | replaced wholesale; comments, anchors and block styles inside it are gone |
| Missing final segment | appended as the last key of the deepest existing ancestor |
| Missing intermediate segments | synthesised as nested mappings |
Value under an &anchor |
replaced under the anchor, so *alias references keep resolving |
Value that is an *alias |
refused, ErrUnsupported |
| Replacement that would orphan an alias | refused, ErrUnsupported, naming the alias path |
| Path running through a non-mapping | refused, ErrNotFound, naming the blocking prefix |
| Malformed path | ErrInvalidPath |
New keys are appended rather than inserted, which is what leaves the existing keys' comments and order untouched.
Newly synthesised nesting is indented two spaces per level regardless of the file's own indentation, because it comes from the marshaller rather than from the document. Existing indentation is never rewritten. A new key added to a four-space file lands at four spaces; a new sub-mapping under it lands at six.
Setting a string over an existing plain scalar has a sharp edge — see
Assigning a string that needs quoting
before you pass user input to Set.
Document.Remove¶
Deletes the entry at path and the comments that belong to it. Removing a key
removes everything beneath it.
| Situation | Behaviour |
|---|---|
| Path resolves | the entry is removed, nil |
| Path does not resolve | ErrNotFound — removing something already absent is an error, not a no-op |
| Malformed path | ErrInvalidPath |
| The subtree defines an anchor an alias still uses | refused, ErrUnsupported, naming the alias that would dangle |
| The entry was the last in its mapping | the mapping becomes {} — the parent is never removed for you |
Which nearby comments go with the key is decided positionally; the rules are in Comment ownership, and their one failure case is in Limitations.
Node¶
A read-only view of one addressed entry. A value type — copying it is free and copies nothing but a pointer to the parsed entry.
Node.Path¶
The dotted path the node was addressed by, exactly as it was passed to Get.
Node.Position¶
Where the node's key starts in the source. Line and Column are both
1-based. A key with no token yields the zero Position{0, 0}.
Positions describe the source that was parsed. After a Set that adds or replaces
nodes, positions of untouched entries are still the original ones, and positions
inside newly built nodes are synthetic.
Node.Comments¶
The comments attached to the node, with the leading # and surrounding
whitespace stripped — the marker is syntax, not content.
Head is every comment line above the key, one entry per line. It does not
apply the ownership rules Remove uses: a file header or a section heading
separated by a blank line still appears in the Head of the key below it. If you
need "the comment that belongs to this key", the blank-line rule is yours to
apply.
Line is the comment on the key's own line, empty when there is none.
Node.String¶
The node's value as text. Quoting is syntax, so it is not part of the value: a
scalar written "hello # world" reads back as hello # world, hash included,
because inside quotes that hash is a literal character.
| Value in the document | String() returns |
|---|---|
plain or "quoted" or 'single' |
the decoded scalar, unquoted, escapes resolved |
A \| or > block |
the block's content, without the indicator or its indentation |
| An integer, float or boolean | the token text as written |
a: with nothing after it |
null |
~ |
~ |
| A mapping or sequence | the collection rendered as it appears in the source |
*alias |
*alias — the reference, unresolved |
A tagged value such as !!str 5 |
!!str 5 — the tag is included in the text |
Node.IsCollection¶
True when the value is a mapping or a sequence, in block or flow style. False for scalars, nulls and aliases — including an alias that resolves to a mapping, since the node itself is a reference.
Comments¶
type Comments struct {
Head []string // comment block above the key, one entry per line
Line string // comment on the key's own line, empty when there is none
}
Position¶
Unsupported¶
type Unsupported struct {
Reason Reason
Line int // 1-based line where the construct starts
Detail string
}
func (u Unsupported) String() string
String() formats as line 12: <reason> (<detail>), dropping the parenthesised
detail when there is none. The two Reason values and what triggers them are in
Unsupported constructs.
Errors¶
Five sentinels, all matched with errors.Is. Each has its own section in
Errors, including what to do about it.
var (
ErrNotFound = errors.New("yamldoc: path not found")
ErrUnsupported = errors.New("yamldoc: document contains an unsupported construct")
ErrInvalidPath = errors.New("yamldoc: invalid path")
ErrEmitInvalid = errors.New("yamldoc: refusing to emit invalid YAML")
ErrInternal = errors.New("yamldoc: internal invariant violated")
)