yamldoc

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 28 Imported by: 0

README

yamldoc

yamldoc

Edit a YAML document without destroying it.

Parse bytes, change one key, write bytes back. Comments stay where they were, key order holds, quoting and block styles survive, and every byte you did not touch comes back exactly as it went in.

f, err := yamldoc.Parse(src, yamldoc.Options{})
if err != nil {
    return err
}

d, _ := f.Snapshot().Document(0)
if err := d.Set("server.port", 9090); err != nil {
    return err
}

out, err := f.Snapshot().Bytes()
 server:
   host: localhost      # where we bind
-  port: 8080
+  port: 9090
   # end of the server block

Why this exists

Go has the YAML value half many times over and the editing half not at all.

Marshalling a map[string]any back to YAML throws away every comment the author wrote, reorders keys, and reformats the file, which is fine for machine-written data and unacceptable for a hand-authored config file someone has to keep maintaining.

Every other ecosystem splits format-preserving editing from value decoding into separate libraries: Rust's toml_edit/toml, HashiCorp's hclwrite/hclsyntax, Python's tomlkit/tomli. This is that split, for Go and YAML, with its own parser rather than a borrowed one. The earlier goccy-backed version could only re-emit the tree and hope; this one edits the source and proves the result. Why not just re-marshal? has the argument.

What it guarantees

Every untouched byte comes back. Comment alignment, blank lines, CRLF, a missing final newline, a BOM, multi-line flow collections, indentation width: none of it is re-emitted, so none of it can drift. A parse followed by an emit is the identity function on the bytes, and an edit changes the footprint of the edit and nothing else.

Comments keep their owners. A comment directly above an entry, or on its line, belongs to it and goes when it goes. One separated by a blank line belongs to the section and stays. The rules are decided, written down and tested, not inferred from the words.

Values keep their meaning. Reading 0.1 gives you 0.1; writing it back is a no-op. A string "true" stays a string. A float64(1) is written as a float. Anchors keep their aliases, and an edit that would leave an alias dangling is refused.

Nothing is thrown away on the way in. A projected value carries the line and column it came from and the aliases it came through; a merged mapping says which << supplied each entry; every comment has an owner you can ask for its text and location; a value's spelling, its schema type and its native form are three separate questions with three answers.

Nothing commits unchecked. Every transaction re-parses its own output and compares it against an independent statement of what the edit meant, byte footprint and comment ownership included. A mismatch rolls the whole batch back and returns an error; a damaged document is never the result.

What it deliberately does not do

No file I/O. No merging, precedence or overlay semantics. No application schema. It assigns no meaning to the order of documents in a multi-document file. The library moves text and interprets YAML; what the YAML is for belongs to whoever is using it. Mechanism, not policy draws the line.

Two limits are worth knowing before you adopt it: a dotted path has no quoting, so servers[0].port reaches into a list but a key containing a dot or a bracket is reached with StringStep rather than a path, and a document that cannot be parsed is an error rather than a warning. The rest is in What yamldoc does not do.

Install

go get gitlab.com/phpboyscout/go/yamldoc

Requires Go 1.27+. One dependency: gitlab.com/phpboyscout/go/errors. No YAML parser: the engine is the library's own.

Usage

f, err := yamldoc.Parse(src, yamldoc.Options{})

d, _ := f.Snapshot().Document(0)

// Read: typed, from an immutable snapshot.
if port, err := d.Get("server.port"); err == nil {
    n, _ := port.Int64()
}

// Write, one change at a time: each helper is its own transaction.
_ = d.Set("server.port", 9090)          // in place; keeps the inline comment
_ = d.Set("server.tls.cert", "/etc/c")  // creates missing intermediates, in block style
_ = d.Set("servers[0].port", 9090)      // into a list; [len] appends
_ = d.Remove("server.debug")            // comment ownership rules apply

// Write, several changes together: the callback is the transaction.
err = f.Edit(func(tx *yamldoc.Transaction) error {
    s, _ := tx.Snapshot()
    root, _ := s.Document(0)
    server, err := root.Get("server")
    if err != nil {
        return err
    }
    return tx.Set(server, yamldoc.StringStep("port"), 9090)
})

out, err := f.Snapshot().Bytes()

A Snapshot is immutable and survives later edits, so a reader never sees a half-applied change. Inside Edit, every command produces a new working revision and expires the node handles into the old one; select again from tx.Snapshot() before the next command.

Removal and comments
server:
  host: localhost

  # ==== TLS ====        ← blank line between it and the key: a section comment, survives
  tls_cert: /etc/cert.pem
  # the port we bind to  ← directly above a key: belongs to it, goes with it
  port: 8080             # inline: goes with it
  # end of the block     ← after the last entry: belongs to the mapping, survives
Empty is a value

Removing the last entry of a mapping leaves {} on the former child's line, not nothing. Code may require a parent to exist while it holds no entries, so the parent is never removed on your behalf.

Documentation

Full documentation: https://yamldoc.go.phpboyscout.uk

Should you use yamldoc? The alternatives measured, the benefits, and the pitfalls
Getting started Change a config file end to end
API overview The types, what each is for, and where the detail lives
Paths and steps What a dotted path can address, and what needs a step
Reading a document Typed reads, spelling, provenance, merge keys, comments and partial validation
How a written value is spelt Every rule for the text a write produces, pinned by a test
Errors Each sentinel and what to do about it
What yamldoc does not do Absent by design, and out of reach

Generated godoc: pkg.go.dev. Decision records: the wiki.

Contributing

Every behaviour is test-first, and adversarial fixtures are mandatory alongside realistic ones. A corpus of eight real project config files validates the common path and finds nothing else; every corruption case this library guards against was found by constructing it deliberately.

just ci          # tidy, test, race, lint
just fuzz-edit   # fuzz the public mutation path

License

MIT, see LICENSE.

Documentation

Overview

Package engine assembles the owned parser and semantic graph into immutable read views. Resource limits remain explicit while public defaults are measured.

Index

Examples

Constants

View Source
const (
	Core       Base = schema.Core
	Strings    Base = schema.Strings
	JSON       Base = schema.JSON
	Failsafe   Base = schema.Failsafe
	Standard11 Base = schema.Standard11

	StandardTypes      Extensions = schema.StandardTypes
	ImplicitTimestamps Extensions = schema.ImplicitTimestamps
	MergeSupport       Extensions = schema.MergeSupport
)
View Source
const (
	KindScalar   Kind = syntax.Scalar
	KindSequence Kind = syntax.Sequence
	KindMapping  Kind = syntax.Mapping
	KindAlias    Kind = syntax.Alias

	StyleEmpty        Style = syntax.Empty
	StylePlain        Style = syntax.Plain
	StyleSingleQuoted Style = syntax.SingleQuoted
	StyleDoubleQuoted Style = syntax.DoubleQuoted
	StyleFlow         Style = syntax.Flow
	StyleFlowPair     Style = syntax.FlowPair
	StyleBlock        Style = syntax.Block
	StyleLiteral      Style = syntax.Literal
	StyleFolded       Style = syntax.Folded
)

Variables

View Source
var (
	// ErrCommand reports a command that is incomplete or contradicts itself,
	// such as a comment copy with no destination.
	ErrCommand = errors.NewSentinel("yamldoc.command", "edit command is incomplete or contradictory")
	// ErrPlacement reports a comment destination the source cannot express
	// with the requested owner, or two groups competing for one site.
	ErrPlacement = errors.NewSentinel("yamldoc.placement", "comment placement cannot be represented")
)
View Source
var (
	ErrEditLimit    = errors.NewSentinel("yamldoc.edit_limit", "transaction resource limit exhausted")
	ErrPreservation = errors.NewSentinel("yamldoc.preservation", "edit changes a required preserved source dependency")
	ErrTagIntent    = errors.NewSentinel("yamldoc.tag_intent", "value requires an explicit change of the authored tag")
)
View Source
var (
	ErrInput      = errors.NewSentinel("yamldoc.input", "unsupported Go input type")
	ErrInputCycle = errors.NewSentinel("yamldoc.input_cycle", "Go input contains an active pointer or container cycle")
	ErrInputLimit = errors.NewSentinel("yamldoc.input_limit", "input normalization budget exhausted")
)
View Source
var (
	ErrView         = errors.NewSentinel("yamldoc.view", "invalid read view")
	ErrNotFound     = errors.NewSentinel("yamldoc.not_found", "selection does not exist")
	ErrInvalidIndex = errors.NewSentinel("yamldoc.invalid_index", "sequence index must be nonnegative")
	ErrStep         = errors.NewSentinel("yamldoc.step", "invalid selection step")
	ErrAlias        = errors.NewSentinel("yamldoc.alias", "alias requires explicit target access")
	ErrAmbiguous    = errors.NewSentinel("yamldoc.ambiguous", "selection matches multiple mapping entries")
	ErrOptions      = errors.NewSentinel("yamldoc.options", "invalid engine options")
	ErrInternal     = errors.NewSentinel("yamldoc.internal", "unexpected engine failure")
)
View Source
var (
	// ErrSyntax: the bytes are not YAML under the selected language.
	ErrSyntax = syntax.ErrSyntax
	// ErrUnsupportedVersion: a %YAML directive names a version this engine
	// does not implement (D27).
	ErrUnsupportedVersion = syntax.ErrUnsupportedVersion
	// ErrVersionConflict: a %YAML directive disagrees with Options.Version.
	ErrVersionConflict = syntax.ErrVersionConflict
	// ErrKind: the node is not the kind the operation needs.
	ErrKind = schema.ErrKind
	// ErrContent: a scalar's text is not valid for its resolved tag.
	ErrContent = schema.ErrContent
	// ErrResolution: a plain scalar matches nothing in the selected schema.
	ErrResolution = schema.ErrResolution
	// ErrUnsupportedType: the selected profile has no such type.
	ErrUnsupportedType = schema.ErrUnsupported
	// ErrProfile: the profile combination is outside the built-in inventory.
	ErrProfile = schema.ErrProfile
	// ErrSemantic: the graph has invalid or unavailable semantic content, so
	// the operation cannot proceed; Validate reports the diagnostics.
	ErrSemantic = compose.ErrSemantic
	// ErrDanglingAlias: an alias has no prior anchor in its document.
	ErrDanglingAlias = compose.ErrAlias
	// ErrDuplicateKey: a mapping has two semantically equal keys.
	ErrDuplicateKey = compose.ErrDuplicate
	// ErrReference: the edit would remove an anchor a surviving alias needs.
	ErrReference = expect.ErrReference
	// ErrMismatch: the candidate source differs from the independent edit
	// intention; nothing was committed.
	ErrMismatch = expect.ErrMismatch
	// ErrRange and ErrPrecision: a typed read cannot represent the value.
	ErrRange     = scalar.ErrRange
	ErrPrecision = scalar.ErrPrecision
)

Sentinels raised inside the engine that a caller may need to match. Each is the internal value itself, so errors.Is agrees on either name.

View Source
var (
	ErrResolverInput     = errors.NewSentinel("yamldoc.resolver.input", "application resolution requires a valid explicitly tagged literal node")
	ErrResolverLimit     = errors.NewSentinel("yamldoc.resolver.limit", "application resolution resource limit exceeded")
	ErrResolverCycle     = errors.NewSentinel("yamldoc.resolver.cycle", "application resolution revisits an invocation ancestor")
	ErrResolverExpired   = errors.NewSentinel("yamldoc.resolver.expired", "nested resolution helper has expired")
	ErrResolverRevision  = errors.NewSentinel("yamldoc.resolver.revision", "nested resolution requires the initiating source revision")
	ErrResolverPanic     = errors.NewSentinel("yamldoc.resolver.panic", "application resolver panicked")
	ErrResolverLifecycle = errors.NewSentinel("yamldoc.resolver.lifecycle", "resolver owner returned with unfinished admitted calls")
)
View Source
var (
	ErrResolverRegistry      = errors.NewSentinel("yamldoc.resolver.registry", "invalid resolver registry or registration")
	ErrResolverConflict      = errors.NewSentinel("yamldoc.resolver.conflict", "resolver identity is already registered")
	ErrResolverUnhandled     = errors.NewSentinel("yamldoc.resolver.unhandled", "no resolver is attached for this exact tag identity")
	ErrResolverConfiguration = errors.NewSentinel("yamldoc.resolver.configuration", "invalid resolver attachment configuration")
	ErrResolverProvider      = errors.NewSentinel("yamldoc.resolver.provider", "resolver provider construction failed")
	ErrResolverCleanup       = errors.NewSentinel("yamldoc.resolver.cleanup", "resolver resource cleanup failed")
)
View Source
var (
	ErrExpired           = errors.NewSentinel("yamldoc.engine.expired", "working view has expired")
	ErrActiveTransaction = errors.NewSentinel("yamldoc.engine.active_transaction", "file already has an active transaction")
	ErrCallback          = errors.NewSentinel("yamldoc.engine.callback", "transaction callback panicked")
	ErrStale             = errors.NewSentinel("yamldoc.engine.stale", "edit target belongs to another revision")
)
View Source
var ErrDocumentExists = errors.NewSentinel("yamldoc.document_exists", "stream already contains a document")
View Source
var ErrExists = errors.NewSentinel("yamldoc.exists", "mapping key already exists")
View Source
var ErrPath = errors.NewSentinel("yamldoc.path", "invalid dotted path")
View Source
var ErrPresentation = errors.NewSentinel("yamldoc.presentation", "requested scalar presentation cannot represent this value")

Functions

func Normalize added in v0.6.0

func Normalize(input any, limits InputLimits) (result *value.Graph, err error)

Types

type AliasHop added in v0.6.0

type AliasHop struct{ Alias, Target Node }

AliasHop preserves the literal reference and its already bound target. It follows one edge only, including when the graph contains a cycle.

type AliasHopLocation added in v0.6.0

type AliasHopLocation = compose.AliasHop

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

type Base added in v0.6.0

type Base = schema.Base

Profile is a schema profile from the built-in inventory (D157): a Base with optional Extensions. NewProfile refuses combinations outside that inventory.

type CheckKind added in v0.6.0

type CheckKind = compose.CheckKind

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

const (
	CheckTagContent        CheckKind = compose.TagContent
	CheckKeyUniqueness     CheckKind = compose.KeyUniqueness
	CheckCollectionContent CheckKind = compose.CollectionContent
	CheckAliasContent      CheckKind = compose.AliasContent
)

type Comment added in v0.6.0

type Comment struct {
	// contains filtered or unexported fields
}

Comment is an immutable occurrence view. Its text, location and owner always describe the same revision; working comments expire with the callback.

func (Comment) Content added in v0.6.0

func (c Comment) Content() (result string, err error)

Content is the comment's text after its "#" marker, spacing included; Raw is the whole token as authored.

func (Comment) Location added in v0.6.0

func (c Comment) Location() (result source.Location, err error)

func (Comment) Owner added in v0.6.0

func (c Comment) Owner() (result CommentOwner, err error)

func (Comment) Raw added in v0.6.0

func (c Comment) Raw() (result []byte, err error)

type CommentChanges added in v0.7.0

type CommentChanges struct {
	Moves  []CommentMove
	Copies []CommentCopy
}

CommentChanges is one command's moves and copies, validated and applied together against the revision their handles came from (D66–D76).

type CommentCopy added in v0.7.0

type CommentCopy struct {
	Sources []Comment
	To      []Destination
}

CommentCopy writes new occurrences of the selected comments, in source order, at every destination, and leaves the originals where they are.

type CommentKind added in v0.6.0

type CommentKind = comment.Kind

CommentKind says what a comment belongs to (D39–D125).

const (
	CommentOnStream       CommentKind = comment.Stream
	CommentOnDocument     CommentKind = comment.Document
	CommentOnContainer    CommentKind = comment.Container
	CommentOnMappingEntry CommentKind = comment.MappingEntry
	CommentOnSequenceItem CommentKind = comment.SequenceItem
)

type CommentLimits added in v0.6.0

type CommentLimits = comment.Limits

The limit types are the internal ones, named here so a caller can set them.

type CommentMove added in v0.7.0

type CommentMove struct {
	Sources []Comment
	To      Destination
}

CommentMove moves the selected occurrences, in source order, to one destination.

type CommentOwner added in v0.6.0

type CommentOwner struct {
	// contains filtered or unexported fields
}

func (CommentOwner) Comments added in v0.6.0

func (o CommentOwner) Comments() (result []Comment, err error)

func (CommentOwner) Document added in v0.6.0

func (o CommentOwner) Document() (result Document, err error)

func (CommentOwner) Kind added in v0.6.0

func (o CommentOwner) Kind() (result comment.Kind, err error)

func (CommentOwner) Node added in v0.6.0

func (o CommentOwner) Node() (result Node, err error)

Node returns the entry's key, the sequence item's literal value or the owned container. Stream and document owners have no corresponding syntax node.

type CommentPreservationFailure added in v0.6.0

type CommentPreservationFailure struct {
	// contains filtered or unexported fields
}

CommentPreservationFailure identifies the original occurrence whose presence, source correspondence or owner failed final validation. Candidate is absent when there is no corresponding output occurrence.

func (*CommentPreservationFailure) Candidate added in v0.6.0

func (f *CommentPreservationFailure) Candidate() (source.Location, bool)

func (*CommentPreservationFailure) Error added in v0.6.0

func (*CommentPreservationFailure) Original added in v0.6.0

func (*CommentPreservationFailure) Unwrap added in v0.6.0

func (f *CommentPreservationFailure) Unwrap() error

type ComposeLimits added in v0.6.0

type ComposeLimits = compose.Limits

The limit types are the internal ones, named here so a caller can set them.

type Destination added in v0.7.0

type Destination struct {
	// contains filtered or unexported fields
}

Destination is an owner and a placement at it.

func AtEndMarker added in v0.7.0

func AtEndMarker(doc Document) Destination

AtEndMarker places one comment at the end of the document's existing `...` line.

func AtOwner added in v0.7.0

func AtOwner(owner CommentOwner, placement Placement) Destination

AtOwner is a destination at owner. Entries and sequence items admit Leading, BeforeComment, AfterComment and Inline; a block mapping or sequence admits Tail.

func AtStartMarker added in v0.7.0

func AtStartMarker(doc Document) Destination

AtStartMarker places one comment at the end of the document's existing `---` line, where the document owns it (D53, D72). No marker is created.

func InDocumentGap added in v0.7.0

func InDocumentGap(before, after Document) Destination

InDocumentGap places comments between before's `...` and after's directives or `---`, after any notes already there, where the stream owns them (D54). The documents must be neighbours, with both markers already in the file.

func InReplacement added in v0.8.0

func InReplacement(path []Step, kind CommentKind, placement Placement) Destination

InReplacement is a destination inside the value a ReplaceWithComments command writes: the entry or item at the end of path, or the mapping or sequence at path, with an empty path naming the value itself. It names an owner that does not exist yet, so it is refused anywhere else.

func InStreamFooter added in v0.7.0

func InStreamFooter(snapshot Snapshot) Destination

InStreamFooter places comments at the end of the file, after the last document's `...`, which must be there.

func InStreamHeader added in v0.7.0

func InStreamHeader(snapshot Snapshot) Destination

InStreamHeader places comments after the file's header, before the first document's directives or `---`, which must be there (D55, D115).

type Diagnostic added in v0.6.0

type Diagnostic struct {
	Message          string
	Cause            error
	Location         source.Location
	RelatedLocations []source.Location
}

type Directive added in v0.7.0

type Directive struct {
	Name       string
	Parameters []string
	Location   Location
}

Directive is one directive a document declared: its name without the `%`, its parameters as spelt, and its location from the `%` to its last parameter.

type Document

type Document struct {
	// contains filtered or unexported fields
}

func (Document) CommentOwner added in v0.7.0

func (d Document) CommentOwner() (result CommentOwner, err error)

CommentOwner is the document, which owns comments on its markers and directives and those of a scalar root.

func (Document) Comments added in v0.6.0

func (d Document) Comments() ([]Comment, error)

func (Document) Directives added in v0.7.0

func (d Document) Directives() (result []Directive, err error)

Directives returns this document's directives in source order, reserved ones included (D190). Refusing them is the caller's policy.

func (Document) Get

func (d Document) Get(path string) (result Node, err error)

Get resolves a dotted path of mapping keys and "[n]" sequence indexes against this document's revision. An empty path is the root.

Example

Reading a node exposes its typed value, where it came from, and what was written about its entry.

package main

import (
	"fmt"
	"log"
	"strings"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	src := []byte(`# how chatty to be
log:
  level: info   # one of debug, info, warn
`)

	f, err := yamldoc.Parse(src, yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	d, _ := f.Snapshot().Document(0)

	level, err := d.Get("log.level")
	if err != nil {
		log.Fatal(err)
	}

	value, _ := level.String()
	info, _ := level.Syntax()

	logNode, _ := d.Get("log")
	entries, _ := logNode.Entries()
	comments, _ := entries[0].Comments()
	// Comment text comes back without the '#' but with its spacing: the
	// marker is syntax, the rest is the author's.
	comment, _ := comments[0].Content()

	fmt.Println("value:  ", value)
	fmt.Println("line:   ", info.Location.Start.Line)
	fmt.Println("comment:", strings.TrimSpace(comment))
}
Output:
value:   info
line:    3
comment: one of debug, info, warn

func (Document) Index

func (d Document) Index() int

Index is the document's position in its stream.

func (Document) Remove

func (d Document) Remove(path string) (err error)

Remove deletes the entry at a dotted path as one isolated transaction, with ErrNotFound when the path does not resolve. The same lifecycle rules as Set apply.

Example

Removing a key takes the comment that describes it and leaves the one that describes the section.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	src := []byte(`server:
  host: localhost

  # ==== TLS ====
  tls_cert: /etc/cert.pem
  # the port we bind to
  port: 8080
`)

	f, err := yamldoc.Parse(src, yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	d, _ := f.Snapshot().Document(0)
	if err := d.Remove("server.port"); err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
server:
  host: localhost

  # ==== TLS ====
  tls_cert: /etc/cert.pem
Example (EmptyIsAValue)

Removing the last entry of a mapping leaves an empty mapping on the former child's line. Emptiness is a value: code may need the parent to exist while it holds nothing.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	f, err := yamldoc.Parse([]byte("feature:\n  beta: true\nother: 1\n"), yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	d, _ := f.Snapshot().Document(0)
	if err := d.Remove("feature.beta"); err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
feature:
  {}
other: 1

func (Document) Root added in v0.6.0

func (d Document) Root() (result Node, err error)

func (Document) Set

func (d Document) Set(path string, value any) (err error)

Set writes value at a dotted path as one isolated transaction on the document's file, creating missing mapping ancestors. It addresses this document's index in the file's current revision, commits or rolls back on its own, and cannot run inside an Edit callback (D183). Several edits that must land together belong in one Edit.

Example

Changing a value leaves everything around it alone, including the comment on the very line being edited and the column it was aligned to.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	src := []byte(`server:
  host: localhost   # where we bind
  port: 8080
`)

	f, err := yamldoc.Parse(src, yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	d, err := f.Snapshot().Document(0)
	if err != nil {
		log.Fatal(err)
	}

	if err := d.Set("server.port", 9090); err != nil {
		log.Fatal(err)
	}

	out, err := f.Snapshot().Bytes()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Print(string(out))
}
Output:
server:
  host: localhost   # where we bind
  port: 9090
Example (CreatesIntermediates)

Setting a path whose parents do not exist creates them, in the block style and indentation of the document around them.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	f, err := yamldoc.Parse([]byte("server:\n  host: localhost\n"), yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	d, _ := f.Snapshot().Document(0)
	if err := d.Set("server.tls.cert", "/etc/cert.pem"); err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
server:
  host: localhost
  tls:
    cert: /etc/cert.pem

func (Document) Version added in v0.5.0

func (d Document) Version() (result Version, err error)

Version is the YAML language this document was parsed under: its own directive, else Options.Version, else negotiated (D126).

type EditLimits added in v0.6.0

type EditLimits struct {
	Input                         InputLimits
	Patch                         patch.Limits
	Expected                      expect.Limits
	Revision                      syntax.RevisionLimits
	Commands, Work, RevisionBytes int
}

EditLimits are development allowances, required only when submitting commands. RevisionBytes charges copied source arrays across the complete callback. It is not allocator/RSS accounting; all numerical public defaults remain unselected.

type Encoding added in v0.6.0

type Encoding = source.Encoding

Location, Position and Span place syntax in the source: byte offsets in Span, one-based line and column in Position.

type Entry added in v0.4.0

type Entry struct{ Key, Value Node }

func (Entry) CommentOwner added in v0.7.0

func (e Entry) CommentOwner() (result CommentOwner, err error)

CommentOwner is this mapping entry, which owns its leading documentation and the comments on its line.

func (Entry) Comments added in v0.4.0

func (e Entry) Comments() ([]Comment, error)

type ExpectedLimits added in v0.6.0

type ExpectedLimits = expect.Limits

The limit types are the internal ones, named here so a caller can set them.

type Extensions added in v0.6.0

type Extensions = schema.Extensions

Profile is a schema profile from the built-in inventory (D157): a Base with optional Extensions. NewProfile refuses combinations outside that inventory.

type File

type File struct {
	// contains filtered or unexported fields
}

File publishes immutable revisions. A Snapshot retains its revision even after another revision is committed to the File.

func Parse

func Parse(input []byte, options Options) (file *File, err error)

func (*File) Edit added in v0.6.0

func (f *File) Edit(callback func(*Transaction) error) (err error)
Example

Several edits that must land together go in one Edit: the callback is the transaction, and a failure anywhere in it leaves the file untouched.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	f, err := yamldoc.Parse([]byte("server:\n  host: localhost\n  port: 8080\n"), yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	err = f.Edit(func(tx *yamldoc.Transaction) error {
		snapshot, err := tx.Snapshot()
		if err != nil {
			return err
		}

		d, err := snapshot.Document(0)
		if err != nil {
			return err
		}

		server, err := d.Get("server")
		if err != nil {
			return err
		}

		if err := tx.Set(server, yamldoc.StringStep("port"), 9090); err != nil {
			return err
		}

		// Every command produces a new working revision and expires the
		// handles into the old one, so select again from a fresh snapshot.
		snapshot, _ = tx.Snapshot()
		d, _ = snapshot.Document(0)

		server, err = d.Get("server")
		if err != nil {
			return err
		}

		return tx.Remove(server, yamldoc.StringStep("host"))
	})
	if err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
server:
  port: 9090

func (*File) Snapshot added in v0.6.0

func (f *File) Snapshot() Snapshot

type Graph added in v0.6.0

type Graph = value.Graph

Graph is a finite, detached semantic value graph (D6): the closed form every write enters and Detach returns. Number is an exact YAML number (D34).

type GraphLimits added in v0.6.0

type GraphLimits = value.Limits

The limit types are the internal ones, named here so a caller can set them.

type InputEntry added in v0.6.0

type InputEntry struct {
	Key   Key
	Value any
}

type InputFault added in v0.6.0

type InputFault struct {
	// contains filtered or unexported fields
}

InputFault retains a structured path without stringifying application values or invoking formatting hooks. It describes submitted input, not YAML source.

func (*InputFault) Error added in v0.6.0

func (f *InputFault) Error() string

func (*InputFault) Path added in v0.6.0

func (f *InputFault) Path() []InputStep

func (*InputFault) Unwrap added in v0.6.0

func (f *InputFault) Unwrap() error

type InputLimits added in v0.6.0

type InputLimits struct {
	Graph        value.Limits
	Scalars      schema.Limits
	Depth, Bytes int
}

type InputStats added in v0.6.0

type InputStats struct{ Work, Bytes int }

InputStats reports deterministic work and payload allowances, not exact Go allocator costs. It lets a transaction share one allowance across commands.

func NormalizeWithStats added in v0.6.0

func NormalizeWithStats(input any, limits InputLimits) (result *value.Graph, stats InputStats, err error)

type InputStep added in v0.6.0

type InputStep struct {
	Key     string
	Index   int
	IsIndex bool
}

type Key added in v0.6.0

type Key struct {
	// contains filtered or unexported fields
}

Key is a detached semantic key. Deliberate non-comparability prevents Go's pointer equality from silently substituting for YAML key equality.

func BigIntKey added in v0.6.0

func BigIntKey(input *big.Int, digitLimit int) (result Key, err error)

func BinaryKey added in v0.6.0

func BinaryKey(input []byte, limit int) (result Key, err error)

func BoolKey added in v0.6.0

func BoolKey(input bool) Key

func FloatKey added in v0.6.0

func FloatKey(input float64) (Key, error)

func GraphKey added in v0.6.0

func GraphKey(graph *value.Graph) (result Key, err error)

func IntKey added in v0.6.0

func IntKey(input int64) Key

func NullKey added in v0.6.0

func NullKey() Key

func NumberKey added in v0.6.0

func NumberKey(input number.Number) (result Key, err error)

func StringKey added in v0.6.0

func StringKey(input string) (result Key, err error)

func TimeKey added in v0.6.0

func TimeKey(input time.Time) (result Key, err error)

func UintKey added in v0.6.0

func UintKey(input uint64) Key

func (Key) Equal added in v0.6.0

func (k Key) Equal(other Key, limits value.Limits) (result bool, err error)

type Kind added in v0.6.0

type Kind = syntax.Kind

Kind and Style describe a node's syntax, independently of schema resolution.

type Limits added in v0.6.0

type Limits struct {
	Comments   comment.Limits
	Edit       EditLimits
	Syntax     syntax.Limits
	Compose    compose.Limits
	Projection compose.ProjectionLimits
}

func DefaultLimits added in v0.6.0

func DefaultLimits() Limits

DefaultLimits are provisional limits sized for configuration files, so a zero Options parses. They are not the measured release limits D182's fourth gate requires; that review replaces them. An explicit nonzero limit always wins.

type Location added in v0.6.0

type Location = source.Location

Location, Position and Span place syntax in the source: byte offsets in Span, one-based line and column in Position.

type Mapping added in v0.6.0

type Mapping struct {
	// contains filtered or unexported fields
}

func (Mapping) Entries added in v0.6.0

func (m Mapping) Entries() ([]Entry, error)

func (Mapping) Lookup added in v0.6.0

func (m Mapping) Lookup(key Key) (result Node, err error)

func (Mapping) LookupString added in v0.6.0

func (m Mapping) LookupString(key string) (Node, error)

type MappingInput added in v0.6.0

type MappingInput []InputEntry

type MergeHop added in v0.6.0

type MergeHop = compose.MergeHop

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

type MergeLimits added in v0.6.0

type MergeLimits = compose.MergeLimits

The limit types are the internal ones, named here so a caller can set them.

type MergeStats added in v0.6.0

type MergeStats = compose.MergeStats

Graph is a finite, detached semantic value graph (D6): the closed form every write enters and Detach returns. Number is an exact YAML number (D34).

type MergedEntry added in v0.6.0

type MergedEntry struct {
	Entry
	Owner Node
	Via   []compose.MergeHop
}

type MergedMapping added in v0.6.0

type MergedMapping struct {
	// contains filtered or unexported fields
}

MergedMapping is an explicit evaluated view over original occurrences. It preserves the source revision and does not imply any edit destination.

func (MergedMapping) Entries added in v0.6.0

func (m MergedMapping) Entries() (result []MergedEntry, err error)

func (MergedMapping) Stats added in v0.6.0

func (m MergedMapping) Stats() (result compose.MergeStats, err error)

type Node

type Node struct {
	// contains filtered or unexported fields
}

func (Node) AliasTarget added in v0.6.0

func (n Node) AliasTarget() (result AliasHop, err error)

func (Node) BigInt added in v0.6.0

func (n Node) BigInt() (result *big.Int, err error)

func (Node) Binary added in v0.6.0

func (n Node) Binary() (result []byte, err error)

func (Node) Bool added in v0.5.0

func (n Node) Bool() (result bool, err error)

func (Node) CommentOwner added in v0.7.0

func (n Node) CommentOwner() (result CommentOwner, err error)

CommentOwner is this mapping or sequence as a container: its section and tail comments, not its entries' or items' documentation.

func (Node) Comments

func (n Node) Comments() ([]Comment, error)

Comments enumerates this container's section, internal and tail comments. Entry documentation is obtained from Entry.Comments; sequence item documentation is obtained from the sequence's ItemComments.

func (Node) Detach added in v0.6.0

func (n Node) Detach() (result *value.Graph, err error)

func (Node) DetachedKey added in v0.6.0

func (n Node) DetachedKey() (result Key, err error)

func (Node) Entries added in v0.5.0

func (n Node) Entries() (result []Entry, err error)

Entries and Items inspect literal collection structure, including opaque or invalid collections, without asking the semantic composer to finish.

func (Node) EvaluateMerge added in v0.6.0

func (n Node) EvaluateMerge(limits compose.MergeLimits) (result MergedMapping, err error)

func (Node) Float32 added in v0.6.0

func (n Node) Float32() (result float32, err error)

func (Node) Float64 added in v0.6.0

func (n Node) Float64() (result float64, err error)

func (Node) Get added in v0.6.0

func (n Node) Get(steps ...Step) (result Node, err error)

func (Node) Int64 added in v0.6.0

func (n Node) Int64() (result int64, err error)

func (Node) ItemCommentOwner added in v0.7.0

func (n Node) ItemCommentOwner(index int) (result CommentOwner, err error)

ItemCommentOwner is this sequence's item at index, which owns its leading documentation and the comments on its line.

func (Node) ItemComments added in v0.6.0

func (n Node) ItemComments(index int) ([]Comment, error)

func (Node) Items added in v0.5.0

func (n Node) Items() (result []Node, err error)
Example

A sequence is opaque to a dotted path; its items are read with Items or addressed with IndexStep, and each keeps the comments written against it.

package main

import (
	"fmt"
	"log"
	"strings"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	src := []byte(`tag_also: # directories to tag alongside the root
  # the CLI lives here
  - cli/colophon   # and it ships first
  - tools/render
`)

	f, err := yamldoc.Parse(src, yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	d, _ := f.Snapshot().Document(0)
	seq, _ := d.Get("tag_also")
	items, _ := seq.Items()

	for i, item := range items {
		value, _ := item.String()
		comments, _ := seq.ItemComments(i)

		texts := make([]string, len(comments))
		for j, c := range comments {
			content, _ := c.Content()
			texts[j] = strings.TrimSpace(content)
		}

		fmt.Printf("%d %-14s %q\n", i, value, texts)
	}
}
Output:
0 cli/colophon   ["the CLI lives here" "and it ships first"]
1 tools/render   []

func (Node) LiteralText added in v0.6.0

func (n Node) LiteralText() (result string, err error)

LiteralText returns decoded scalar content before schema or application resolution. Collection and alias occurrences return a kind error.

func (Node) Mapping added in v0.6.0

func (n Node) Mapping() (result Mapping, err error)

func (Node) Number added in v0.6.0

func (n Node) Number() (result number.Number, err error)

func (Node) Project added in v0.6.0

func (n Node) Project(expandAliases bool) (result *compose.Projection, err error)

func (Node) Rat added in v0.6.0

func (n Node) Rat() (result *big.Rat, err error)

func (Node) Raw added in v0.6.0

func (n Node) Raw() (result []byte, err error)

func (Node) Sequence added in v0.6.0

func (n Node) Sequence() (result Sequence, err error)

func (Node) String

func (n Node) String() (result string, err error)

func (Node) Syntax added in v0.6.0

func (n Node) Syntax() (result SyntaxInfo, err error)

func (Node) TagIdentity added in v0.6.0

func (n Node) TagIdentity() (result string, err error)

TagIdentity returns the selected YAML interpretation's expanded, decoded tag, including inferred standard identities, without application resolution.

func (Node) Time added in v0.6.0

func (n Node) Time() (result time.Time, err error)

func (Node) Uint64 added in v0.6.0

func (n Node) Uint64() (result uint64, err error)

func (Node) Value added in v0.6.0

func (n Node) Value() (result any, err error)

type Number added in v0.6.0

type Number = number.Number

Graph is a finite, detached semantic value graph (D6): the closed form every write enters and Detach returns. Number is an exact YAML number (D34).

type Options added in v0.6.0

type Options struct {
	Version  source.Version
	Profiles []schema.Profile
	Limits   Limits
}

type Origin added in v0.6.0

type Origin = compose.Origin

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

type ParseFailure added in v0.6.0

type ParseFailure struct {
	// contains filtered or unexported fields
}

ParseFailure retains admitted source when document-profile admission fails after syntax parsing. It deliberately exposes no editable partial File.

func (*ParseFailure) Bytes added in v0.6.0

func (f *ParseFailure) Bytes() []byte

func (*ParseFailure) Error added in v0.6.0

func (f *ParseFailure) Error() string

func (*ParseFailure) Unwrap added in v0.6.0

func (f *ParseFailure) Unwrap() error

type PatchLimits added in v0.6.0

type PatchLimits = patch.Limits

The limit types are the internal ones, named here so a caller can set them.

type Placement added in v0.7.0

type Placement struct {
	// contains filtered or unexported fields
}

Placement says where at its owner a comment lands. The zero Placement is Leading.

func AfterComment added in v0.7.0

func AfterComment(anchor Comment) Placement

AfterComment places comments directly below an existing standalone comment of the same owner.

func BeforeComment added in v0.7.0

func BeforeComment(anchor Comment) Placement

BeforeComment places comments directly above an existing standalone comment of the same owner.

func Inline added in v0.7.0

func Inline() Placement

Inline places one comment at the end of an entry's or item's line: after a scalar value, a block scalar's header, or the key of a block collection. A line that already ends in a comment is refused.

func Leading added in v0.7.0

func Leading() Placement

Leading places comments after an entry's or item's existing leading documentation, directly above its key or dash (D73).

func Tail added in v0.7.0

func Tail() Placement

Tail places comments after a block container's last member and any comments already at its tail, at its members' indentation.

type Position

type Position = source.Position

Location, Position and Span place syntax in the source: byte offsets in Span, one-based line and column in Position.

type Profile added in v0.6.0

type Profile = schema.Profile

Profile is a schema profile from the built-in inventory (D157): a Base with optional Extensions. NewProfile refuses combinations outside that inventory.

func DefaultProfile added in v0.6.0

func DefaultProfile(version Version) (Profile, error)

func NewProfile added in v0.6.0

func NewProfile(base Base, extensions Extensions) (Profile, error)

NewProfile builds a profile from the inventory; DefaultProfile is the one a version selects without a caller override.

type Projection added in v0.6.0

type Projection = compose.Projection

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

type ProjectionLimits added in v0.6.0

type ProjectionLimits = compose.ProjectionLimits

The limit types are the internal ones, named here so a caller can set them.

type ProjectionStats added in v0.6.0

type ProjectionStats = compose.ProjectionStats

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

type ProjectionStep added in v0.6.0

type ProjectionStep = compose.ProjectionStep

Projection is a native Go projection of a subtree (D37), with the origin of each value and the work it cost. Origin places one projected value and its key in the source and lists the alias hops taken to reach it; MergeHop is one inheritance edge in an evaluated merge (D143). Both carry source locations, not node handles, so they outlive the revision.

type RecoveredFault added in v0.6.0

type RecoveredFault struct {
	// contains filtered or unexported fields
}

RecoveredFault preserves an error-valued panic without rendering its payload. Formatting uses only the engine-owned classification. Non-error payloads are not retained; callers still receive the same distinguishable fault category.

func (*RecoveredFault) Error added in v0.6.0

func (f *RecoveredFault) Error() string

func (*RecoveredFault) StackTrace added in v0.6.0

func (f *RecoveredFault) StackTrace() errors.StackTrace

func (*RecoveredFault) Unwrap added in v0.6.0

func (f *RecoveredFault) Unwrap() []error

type ResolutionOrigin added in v0.6.0

type ResolutionOrigin struct {
	Source, Request        uint64
	Call, Parent, Document int
	Tag                    string
	Location               source.Location
}

ResolutionOrigin is detached provenance. IDs distinguish in-process source, request and invocation identities; they are not persistent or portable hashes.

type Resolved added in v0.6.0

type Resolved struct {
	// contains filtered or unexported fields
}

Resolved is an immutable envelope around an opaque, caller-owned payload. The engine neither clones nor closes that payload, which may itself be nil.

func (*Resolved) Origin added in v0.6.0

func (r *Resolved) Origin() (ResolutionOrigin, error)

func (*Resolved) Payload added in v0.6.0

func (r *Resolved) Payload() (any, error)

type ResolverCleanup added in v0.6.0

type ResolverCleanup func() error

ResolverCleanup belongs to the consumer after successful attachment. Invoke it after all uses of that attachment finish. Registry changes never invoke it.

type ResolverHandler added in v0.6.0

type ResolverHandler func(context.Context, ResolverInput, ResolverScope) (any, error)

type ResolverInput added in v0.6.0

type ResolverInput struct {
	Node Node
	Tag  string
}

ResolverInput carries literal structure and the selected expanded tag identity. Inspection does not imply schema validity of independently tagged descendants.

type ResolverLimits added in v0.6.0

type ResolverLimits struct{ Calls, Depth, Work int }

type ResolverProvider added in v0.6.0

type ResolverProvider func(configuration any) (ResolverHandler, ResolverCleanup, error)

ResolverProvider constructs one consumer's handler. It validates the opaque configuration, owns any necessary copies, and cleans newly acquired resources itself on failure. Supplied shared clients remain caller-owned.

type ResolverRegistry added in v0.6.0

type ResolverRegistry struct {
	// contains filtered or unexported fields
}

ResolverRegistry's zero value is ready for use. Mutation and coherent binding capture are synchronized; handler/client concurrency remains caller-owned.

Example

A custom tag is an extension point. Register a handler for it, attach the registry, and resolve a tagged node when you want its application value. Parsing, editing and emitting never call the handler, and the document keeps its literal text.

package main

import (
	"context"
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	src := []byte("db:\n  password: !secret vault/database # rotated weekly\n")

	var registry yamldoc.ResolverRegistry
	_ = registry.RegisterKeyword("secret", func(_ context.Context, in yamldoc.ResolverInput, _ yamldoc.ResolverScope) (any, error) {
		path, _ := in.Node.LiteralText()
		return "value-from-vault-for-" + path, nil // a real handler would call Vault
	})

	resolvers, cleanup, err := registry.Attach(nil)
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = cleanup() }()

	f, _ := yamldoc.Parse(src, yamldoc.Options{})
	d, _ := f.Snapshot().Document(0)
	password, _ := d.Get("db.password")

	resolved, err := resolvers.Resolve(context.Background(), password, yamldoc.ResolverLimits{Calls: 10, Depth: 4, Work: 1000})
	if err != nil {
		fmt.Println(err)

		return
	}

	payload, _ := resolved.Payload()
	literal, _ := password.LiteralText()
	out, _ := f.Snapshot().Bytes()

	fmt.Println("resolved:", payload)
	fmt.Println("literal: ", literal)
	fmt.Print(string(out))
}
Output:
resolved: value-from-vault-for-vault/database
literal:  vault/database
db:
  password: !secret vault/database # rotated weekly

func DefaultResolverRegistry added in v0.6.0

func DefaultResolverRegistry() *ResolverRegistry

func (*ResolverRegistry) Attach added in v0.6.0

func (r *ResolverRegistry) Attach(configuration map[string]any) (result *ResolverSet, cleanup ResolverCleanup, err error)

Attach captures bindings before invoking providers. Configuration keys are exact tag identities; direct handlers are already configured. Configuration objects remain caller/provider-owned and must not be concurrently modified.

func (*ResolverRegistry) MustRegisterProvider added in v0.6.0

func (r *ResolverRegistry) MustRegisterProvider(tag string, provider ResolverProvider)

MustRegisterProvider is exclusively for init-time provider registration.

func (*ResolverRegistry) MustRegisterTag added in v0.6.0

func (r *ResolverRegistry) MustRegisterTag(tag string, handler ResolverHandler)

MustRegisterTag is exclusively an init-time convenience, where an initializer cannot return an error. Runtime registration must use RegisterTag instead.

func (*ResolverRegistry) RegisterKeyword added in v0.6.0

func (r *ResolverRegistry) RegisterKeyword(keyword string, handler ResolverHandler) error

RegisterKeyword binds !keyword. Keywords are Unicode letters/digits and _, -, . or /, starting with a letter; other identities use RegisterTag.

func (*ResolverRegistry) RegisterProvider added in v0.6.0

func (r *ResolverRegistry) RegisterProvider(tag string, provider ResolverProvider) error

func (*ResolverRegistry) RegisterTag added in v0.6.0

func (r *ResolverRegistry) RegisterTag(tag string, handler ResolverHandler) error

RegisterTag accepts an already expanded, decoded identity. It performs no handle expansion, percent decoding, URI normalization or case folding.

func (*ResolverRegistry) ReplaceProvider added in v0.6.0

func (r *ResolverRegistry) ReplaceProvider(tag string, provider ResolverProvider) error

func (*ResolverRegistry) ReplaceTag added in v0.6.0

func (r *ResolverRegistry) ReplaceTag(tag string, handler ResolverHandler) error

type ResolverScope added in v0.6.0

type ResolverScope struct {
	// contains filtered or unexported fields
}

func (ResolverScope) Resolve added in v0.6.0

func (scope ResolverScope) Resolve(node Node) (result *Resolved, err error)

type ResolverSet added in v0.6.0

type ResolverSet struct {
	// contains filtered or unexported fields
}

func (*ResolverSet) Resolve added in v0.6.0

func (set *ResolverSet) Resolve(ctx context.Context, node Node, limits ResolverLimits) (result *Resolved, err error)

Resolve invokes the captured handler synchronously. Cancellation is cooperative: an invoked handler and its admitted descendants finish before this call returns.

type RevisionLimits added in v0.6.0

type RevisionLimits = syntax.RevisionLimits

The limit types are the internal ones, named here so a caller can set them.

type ScalarLimits added in v0.6.0

type ScalarLimits = schema.Limits

The limit types are the internal ones, named here so a caller can set them.

type Sequence added in v0.6.0

type Sequence struct {
	// contains filtered or unexported fields
}

func (Sequence) Items added in v0.6.0

func (s Sequence) Items() ([]Node, error)

type Snapshot added in v0.6.0

type Snapshot struct {
	// contains filtered or unexported fields
}

func (Snapshot) Bytes added in v0.6.0

func (s Snapshot) Bytes() (result []byte, err error)

func (Snapshot) CommentOwner added in v0.7.0

func (s Snapshot) CommentOwner() (result CommentOwner, err error)

CommentOwner is the stream, which owns headers, footers and notes between documents.

func (Snapshot) Comments added in v0.6.0

func (s Snapshot) Comments() ([]Comment, error)

Comments enumerates directly owned stream comments, excluding documents.

func (Snapshot) Document added in v0.6.0

func (s Snapshot) Document(index int) (result Document, err error)

func (Snapshot) Documents added in v0.6.0

func (s Snapshot) Documents() (result []Document, err error)
Example

Every document of a stream is addressable, and an edit to one leaves the others and the separators untouched.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	f, err := yamldoc.Parse([]byte("a: 1\n---\nb: 2\n"), yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	documents, _ := f.Snapshot().Documents()
	if err := documents[1].Set("b", 3); err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
a: 1
---
b: 3

func (Snapshot) Validate added in v0.6.0

func (s Snapshot) Validate(options ValidationOptions) (report ValidationReport, err error)

type Span added in v0.6.0

type Span = source.Span

Location, Position and Span place syntax in the source: byte offsets in Span, one-based line and column in Position.

type Step added in v0.6.0

type Step struct {
	// contains filtered or unexported fields
}

Step is a structured selection. String keys are literal, including dots.

func IndexStep added in v0.6.0

func IndexStep(index int) Step

func KeyStep added in v0.6.0

func KeyStep(key Key) Step

func PathSteps added in v0.6.0

func PathSteps(path string) ([]Step, error)

PathSteps parses a dotted path into steps: a segment is a literal mapping key followed by any number of "[n]" sequence indexes, and a path may begin with an index to address a root sequence. There is no quoting or escaping: a key containing ".", "[" or "]" is addressed with StringStep, never by path.

path    = segment ( "." segment )*
segment = key index* | index+        (index+ alone only as the first segment)
index   = "[" digits "]"

func StringStep added in v0.6.0

func StringStep(key string) Step

type Style added in v0.6.0

type Style = syntax.Style

Kind and Style describe a node's syntax, independently of schema resolution.

type SyntaxInfo added in v0.6.0

type SyntaxInfo struct {
	Kind                            syntax.Kind
	Style                           syntax.Style
	Location                        source.Location
	Tag, ExpandedTag, Anchor, Alias string
}

SyntaxInfo describes authored syntax independently of semantic completion. ExpandedTag retains percent escapes; it is not a resolved tag identity.

type SyntaxLimits added in v0.6.0

type SyntaxLimits = syntax.Limits

The limit types are the internal ones, named here so a caller can set them.

type Transaction added in v0.6.0

type Transaction struct {
	// contains filtered or unexported fields
}

Transaction holds lifecycle state independently of the callback stack. Its working snapshots are immutable but expire together when the callback ends.

func (*Transaction) ChangeComments added in v0.7.0

func (tx *Transaction) ChangeComments(changes CommentChanges) error

ChangeComments moves and copies comments. Every handle must come from the current working revision. Repeated handles select one occurrence, two groups for one site are refused, and an anchor must stay where it is. The resulting owners are checked against a parse of the new bytes.

Example

A comment follows its owner through every edit; moving it to another owner is a command of its own. Here the note on retries becomes documentation for server, so it survives server becoming a scalar in the same transaction.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	f, err := yamldoc.Parse([]byte("# Connection settings\nserver:\n  # Managed by deployment automation\n  retries: 3\n"), yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	err = f.Edit(func(tx *yamldoc.Transaction) error {
		snapshot, _ := tx.Snapshot()
		doc, _ := snapshot.Document(0)
		root, _ := doc.Root()
		server, _ := root.Entries()
		retries, _ := server[0].Value.Entries()

		notes, _ := retries[0].Comments()
		owner, _ := server[0].CommentOwner()

		if err := tx.ChangeComments(yamldoc.CommentChanges{
			Moves: []yamldoc.CommentMove{{Sources: notes, To: yamldoc.AtOwner(owner, yamldoc.Leading())}},
		}); err != nil {
			return err
		}

		snapshot, _ = tx.Snapshot()
		doc, _ = snapshot.Document(0)
		root, _ = doc.Root()

		return tx.Set(root, yamldoc.StringStep("server"), "disabled")
	})
	if err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
# Connection settings
# Managed by deployment automation
server: disabled

func (*Transaction) CreateDocument added in v0.6.0

func (tx *Transaction) CreateDocument(input any) error

CreateDocument creates the first document, using the selected YAML version's default schema. Existing documents, including explicit empty ones, fail.

func (*Transaction) CreateDocumentWithProfile added in v0.6.0

func (tx *Transaction) CreateDocumentWithProfile(input any, profile schema.Profile) error

CreateDocumentWithProfile selects an explicit schema for the new document. Its version must match the stream's selected language (1.2 when automatic).

func (*Transaction) Insert added in v0.6.0

func (tx *Transaction) Insert(container Node, step Step, input any) error

Insert adds a new mapping key at the end or a sequence item at an insertion index (including Len). Existing keys fail instead of becoming value writes. Block and flow containers retain existing child and comment occurrences.

func (*Transaction) Remove added in v0.6.0

func (tx *Transaction) Remove(container Node, step Step) error

Remove deletes an existing mapping entry or sequence item selected from the current container view. Missing selections fail and poison the transaction.

func (*Transaction) RemoveIfPresent added in v0.6.0

func (tx *Transaction) RemoveIfPresent(container Node, step Step) error

RemoveIfPresent permits an absent mapping key. Wrong kinds, invalid selectors, invalid semantics and out-of-range sequence indices remain errors.

func (*Transaction) Replace added in v0.6.0

func (tx *Transaction) Replace(target Node, input any) error

Replace submits a value write against the current working revision. This command supports scalar edits, fresh finite value trees/graphs, complete sequence replacement, recursive mapping replacement with exact key and alias correspondence, and exact ordered-collection no-ops. How each value is spelt, and what an existing scalar's style retains, is docs/reference/spelling.md. Every command failure poisons the callback, even if its return is ignored.

func (*Transaction) ReplaceEntry added in v0.6.0

func (tx *Transaction) ReplaceEntry(container Node, key Step, input any) error

ReplaceEntry updates an existing mapping entry. A missing key fails instead of creating it. Sequence indices require item selection and Replace.

func (*Transaction) ReplaceWithComments added in v0.8.0

func (tx *Transaction) ReplaceWithComments(target Node, value any, changes CommentChanges) error

ReplaceWithComments replaces target with value and moves or copies comments in the same command, so a comment can go to an entry, item or collection the replacement introduces, named with InReplacement (D62). Moved comments leave before the replacement is written, so they do not hold it to the layout that would have kept them; nothing is visible between the steps, and a failure in any of them fails the command.

Example

A replacement and the comments it would otherwise drop can be one command. The root comment becomes the new entry's, on its line.

package main

import (
	"fmt"
	"log"

	"gitlab.com/phpboyscout/go/yamldoc"
)

func main() {
	f, err := yamldoc.Parse([]byte("5 # Retry budget\n"), yamldoc.Options{})
	if err != nil {
		log.Fatal(err)
	}

	err = f.Edit(func(tx *yamldoc.Transaction) error {
		snapshot, _ := tx.Snapshot()
		doc, _ := snapshot.Document(0)
		root, _ := doc.Root()
		notes, _ := doc.Comments()

		to := yamldoc.InReplacement([]yamldoc.Step{yamldoc.StringStep("retries")}, yamldoc.CommentOnMappingEntry, yamldoc.Inline())

		return tx.ReplaceWithComments(root, map[string]any{"retries": 5}, yamldoc.CommentChanges{
			Moves: []yamldoc.CommentMove{{Sources: notes, To: to}},
		})
	})
	if err != nil {
		log.Fatal(err)
	}

	out, _ := f.Snapshot().Bytes()
	fmt.Print(string(out))
}
Output:
retries: 5 # Retry budget

func (*Transaction) Set added in v0.6.0

func (tx *Transaction) Set(container Node, key Step, input any) error

Set updates a present mapping entry or appends a missing one. Key equality is typed YAML equality; existing keys keep their spelling, position and comments. It is one command and does not create missing ancestor containers.

func (*Transaction) Snapshot added in v0.6.0

func (tx *Transaction) Snapshot() (result Snapshot, err error)

type Type added in v0.6.0

type Type = schema.Type

Type names a YAML type in the tag:yaml.org,2002 namespace: what a resolved node is, and what Profile.Supports is asked about.

const (
	TypeStr       Type = schema.Str
	TypeNull      Type = schema.Null
	TypeBool      Type = schema.Bool
	TypeInt       Type = schema.Int
	TypeFloat     Type = schema.Float
	TypeTimestamp Type = schema.Timestamp
	TypeBinary    Type = schema.Binary
	TypeMap       Type = schema.Map
	TypeSeq       Type = schema.Seq
	TypeOMap      Type = schema.OMap
	TypePairs     Type = schema.Pairs
	TypeSet       Type = schema.Set
	TypeMerge     Type = schema.Merge
)

type UnavailableCheck added in v0.6.0

type UnavailableCheck = compose.UnavailableCheck

Graph is a finite, detached semantic value graph (D6): the closed form every write enters and Detach returns. Number is an exact YAML number (D34).

type ValidationMode added in v0.6.0

type ValidationMode uint8
const (
	FullValidation ValidationMode = iota
	PartialValidation
)

type ValidationOptions added in v0.6.0

type ValidationOptions struct{ Mode ValidationMode }

type ValidationReport added in v0.6.0

type ValidationReport struct {
	Snapshot          Snapshot
	Mode              ValidationMode
	Complete          bool
	Diagnostics       []Diagnostic
	UnavailableChecks []compose.UnavailableCheck
}

type Version added in v0.5.0

type Version = source.Version

Version selects a YAML language for a stream. Zero lets each document negotiate its own from its directives (D126).

const (
	YAML11 Version = source.YAML11
	YAML12 Version = source.YAML12
)

Directories

Path Synopsis
internal
comment
Package comment classifies actual comment tokens by source-recoverable ownership.
Package comment classifies actual comment tokens by source-recoverable ownership.
compose
Package compose binds syntax occurrences into finite semantic graphs while retaining literal source, alias occurrences and independently located errors.
Package compose binds syntax occurrences into finite semantic graphs while retaining literal source, alias occurrences and independently located errors.
expect
Package expect records edit intentions independently of source rendering.
Package expect records edit intentions independently of source rendering.
number
Package number retains exact numeric values independently of source spelling.
Package number retains exact numeric values independently of source spelling.
partition
Package partition compares finite labeled graphs by recursive content.
Package partition compares finite labeled graphs by recursive content.
patch
Package patch composes immutable byte edits while retaining original-source correspondence.
Package patch composes immutable byte edits while retaining original-source correspondence.
scalar
Package scalar implements detached exact scalar values and schema resolution.
Package scalar implements detached exact scalar values and schema resolution.
scanner
Package scanner recognizes source productions for the owned YAML engine.
Package scanner recognizes source productions for the owned YAML engine.
schema
Package schema resolves syntax under an explicit built-in profile, without expanding aliases, evaluating merges, or invoking application resolvers.
Package schema resolves syntax under an explicit built-in profile, without expanding aliases, evaluating merges, or invoking application resolvers.
source
Package source owns the original encoded bytes used by the YAML engine.
Package source owns the original encoded bytes used by the YAML engine.
syntax
Package syntax builds immutable source syntax for the owned YAML engine.
Package syntax builds immutable source syntax for the owned YAML engine.
value
Package value stores finite, source-independent YAML values.
Package value stores finite, source-independent YAML values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL