phpboyscout · go toolkit
Edit YAML without destroying it
Parse bytes, change one key, write bytes back. Every byte you did not touch comes back exactly as it went in, every comment keeps the key it belongs to, every value keeps its meaning, and nothing commits until the result has been checked. A YAML engine of its own, with no parser underneath.
The thirty-second version¶
A user changes one setting. Here is what that does to their file.
One line changed. Or the comment alignment gone and the blank line with it (goccy keeps the blank line and collapses the alignment). Or everything gone: comments, order, the lot. Those outputs are real, from the libraries named, and the rest of the comparison is measured the same way in Should you use yamldoc?
That is not a bug in any of them. A tree that is re-emitted cannot leave alone what it does not model. yamldoc edits the source instead: the bytes of the edit's footprint are replaced, and every other byte is returned untouched.
What you get¶
Every untouched byte, back
Comments and their alignment, blank lines, indentation width, key order, quoting and block styles, multi-line flow, anchors, aliases, merge keys, directives, CRLF, a missing final newline, a BOM, UTF-16. No list of exceptions, because nothing is re-emitted.
Comments keep their owners
A comment above a key or on its line goes when the key goes; one separated by a blank line, or after the last entry, stays. Decided by rule, written down, tested, and enforced on every commit. The rules.
Nothing commits unchecked
Every transaction re-parses its own output and compares it with an independent statement of the edit, untouched bytes and comment ownership included. A batch lands whole or not at all. How an edit is made.
Values keep their meaning
Read 0.1, write it back, nothing changes. A string "true" stays a string; a float stays a float; a 30-digit integer reads exactly. New values are spelt the way a person writes them, multiline strings as literal blocks. Every rule.
Edits that reach everything
Set, insert, replace and remove entries and items; replace a subtree with the keys you name keeping their comments; turn a scalar into a collection; fill an empty file; detach a subtree from one file and write it into another. Dotted paths with [n] indexes for one change, a transaction for several. Edit a value.
YAML as the document declares it
YAML 1.1 and 1.2 chosen per document from its %YAML directive, nineteen schema profiles, %TAG handles, explicit and verbatim tags, keys matched by value so 8080 finds 0x1F90. No other Go library reads the directive.
The spelling, the type and the value, apart
Three questions with three answers on every node: Raw gives 0x1F90, TagIdentity gives tag:yaml.org,2002:int, Value gives 8080. Syntax adds kind, style, tag, anchor and line. A 30-digit integer is a *big.Int, a decimal is a Rat. Reading a document.
Every value knows where it came from
Project a subtree to map[string]any and get, beside it, the line and column of every value and every key, the alias hops it arrived through, and for a << merge which entry was inherited from which anchor. Error messages that say server.timeout at config.yaml:5 after the value is a plain Go map. Reading a document.
Comments are data, not debris
Ask any entry, item, container, document or stream for its comments and get each one's text, location and owner. The comment above a key is the key's documentation; a linter can require one, a generator can render them, a tool can answer "what does this setting do" from the file itself. The comment views.
Tags as an extension point
Register a handler for !secret, !env or a full tag URI and resolve a node when you want its value. Providers with configuration and cleanup, nested resolution, cancellation, limits, provenance. The file keeps its literal text. Unique to yamldoc.
Its own engine, one dependency
No parser underneath. A grammar of its own checked against every valid and invalid case in the official YAML test suite, a composer that follows the standard's own rules, and a single dependency, go/errors. Behaviour is decided here, not inherited from a library with other priorities. The owned engine.
Safe with hostile input
Immutable snapshots that survive later edits. Bounded source size, depth, nodes, comments, alias expansion and work per transaction. Invisible and bidirectional characters escaped in what it writes. Validation that names a dangling alias or a duplicate key with both locations, walks what it can of a file it cannot prove, and says which checks it could not run. Validate a document.
At a glance¶
f, err := yamldoc.Parse(src, yamldoc.Options{})
d, _ := f.Snapshot().Document(0)
port, _ := d.Get("server.port") // typed reads from an immutable snapshot
n, _ := port.Int64()
_ = d.Set("server.port", 9090) // one change, one transaction
err = f.Edit(func(tx *yamldoc.Transaction) error { // several changes, one transaction
s, _ := tx.Snapshot()
root, _ := s.Document(0)
server, _ := root.Get("server")
return tx.Set(server, yamldoc.StringStep("port"), 9090)
})
out, _ := f.Snapshot().Bytes()
Scope, and what to read next¶
The library moves text and interprets YAML; what the YAML is for belongs to whoever is using it. No file I/O, no merging or precedence, no application schema, no meaning to the order of documents. See Mechanism, not policy.
It is the editing half of the split every other ecosystem made; pair it with a value library for decoding into structs. It is strict where the standard is strict, an edit costs several times what a value library's does (why, and what the time buys), and a dotted path has no quoting for a key with a dot in it. All of that, with numbers, is in Should you use yamldoc?, and the rest in What yamldoc does not do.
| Getting started | Change a config file end to end, in fifteen minutes |
| Edit a value | Scalars, new keys, subtrees, sequence items, kind changes |
| API overview | The types, what each is for, where the detail lives |
| Reading a document | Typed reads, spelling, provenance, merge keys, comments and partial validation, worked on one file |
| How a written value is spelt | Every rule for the text a write produces, pinned by a test |
| What is preserved | The guarantee, stated precisely |
Further reading¶
The blog carries a curated route through this subject: Building a command-line tool in Go collects everything written about it, ordered so you can start at the beginning rather than newest-first.
Ask phpbotscout

He answers questions about the projects over on the Discord, citing the docs where they already cover it, and offering to raise an issue where they don't. Bring a bug, an idea, or a questionable engineering decision.