Help us shape the future of Go! Take the 2026 Go Developer Survey

gen

package
v0.6.7 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package gen defines what a generator is and how rig runs one.

A generator is a pure function from the frozen document to a set of files. Purity is the whole design: because a generator produces its output in memory rather than writing as it goes, `rig check` can run every generator and compare the result to disk without a single special case per generator, and two runs over the same document can be diffed to prove nothing changed.

Generators register themselves. Adding one touches exactly one place — its own init — rather than a switch, a config struct, and a diff function that all have to be kept in step.

Index

Constants

View Source

Banner is the first line of every file rig owns outright.

It is written by the emitters and read back by Orphans: a file carrying it is rig's, whoever last touched it. Both halves come from here so they cannot drift apart — a scan looking for a line the emitters stopped writing would quietly find nothing.

View Source
const ManifestName = ".rig/manifest.json"

ManifestName is where the manifest lives, relative to the project root.

Variables

View Source
var Default = NewRegistry()

Default is the registry the CLI uses. Built-in generators add themselves to it from their own init.

View Source
var Retired = map[string]string{
	"electric": "live-sync shapes are written by server-go now, into the API " +
		"package beside the routes they answer next to. Delete this block and move " +
		"its `electric_url` and `stub_dir` options onto server-go, adding " +
		"`api_import` with the same value service-go has. The `shape_import` " +
		"option is gone: a scoping stub names the API package it is already beside.",
}

Retired names a generator that used to exist, and says what to do instead.

A configuration is a file somebody wrote by hand and does not reread, so a generator that goes away has to answer for itself once. Without this the message is "no generator named electric", which is true and tells a reader nothing about where their shape endpoints went.

Functions

func Decode

func Decode[T any](o Options) (T, error)

Decode reads the options block into a typed struct.

Unknown keys are an error: a mistyped option that is silently ignored looks configured and behaves as though it is not, which is the worst of both.

func NeedsWork

func NeedsWork(deltas []Delta) bool

NeedsWork reports whether any delta represents a difference, which is what `rig check` exits non-zero on.

func Register

func Register(g Generator)

Register adds a generator to the default registry.

A duplicate name panics at init rather than failing later: two generators answering to one name is a build mistake, and finding out at startup beats finding out from whichever one happened to register second.

func Report

func Report(w io.Writer, root string, deltas []Delta, verbose bool)

Report writes a human-readable summary of a set of deltas.

func Sum

func Sum(content []byte) string

Sum is the digest the manifest records for a file's content.

Types

type Artifact

type Artifact struct {
	// Path is relative to the generator's output directory.
	Path string
	// Content is the complete file.
	Content []byte
	Mode    WriteMode
	// Perm defaults to 0644 when zero.
	Perm fs.FileMode
}

Artifact is one file a generator produced.

The content is fully materialized rather than streamed. That is what makes a generator testable without a filesystem and `check` possible without running a second, parallel implementation of every generator.

type Delta

type Delta struct {
	Generator string
	Path      string
	Status    Status
	// Reason explains a conflict.
	Reason string
	// contains filtered or unexported fields
}

Delta is one file's worth of difference between what a generator produced and what is on disk.

func Diff

func Diff(root string, results []Result, m *Manifest, opt DiffOptions) ([]Delta, error)

Diff compares generated artifacts against the filesystem.

Three things can be wrong with a file, and they are found three ways. What a generator produces now is compared against what is on disk. What the manifest records is what makes a hand edit distinguishable from output rig has not caught up with. And what is on disk under rig's own naming — see Orphans — is what makes a leftover visible in a checkout that has no manifest, which is every checkout CI makes.

func Orphans

func Orphans(root string, claimed map[string]bool) ([]Delta, error)

Orphans finds files on disk that rig wrote and no generator claims any more.

The manifest cannot answer this on its own. It is gitignored by default, so a clean checkout — which is what CI is — has no record of what rig wrote there. What does survive a clone is the output itself: the `.gen.` in a name and Banner on the first line are rig's claim on a file, and a claim no generator renews is a leftover. A renamed table that left its old file behind is caught on a machine that has never generated anything.

The two signals are both needed. The OpenAPI documents carry no banner, because YAML that begins with a comment is not what anyone wants to read; the TypeScript client's index.ts carries one but cannot be renamed, because a package entry point is called index.ts. Neither signal covers a hand-owned stub, which is the point: a stub has no banner and no `.gen.`, so it is never mistaken for rig's.

type DiffOptions

type DiffOptions struct {
	// Partial says some of the configured generators were held back, as --only
	// does.
	//
	// Staleness is relative to the run. A generator that did not run still owns
	// every file it wrote, and calling those leftovers would turn a narrow
	// regeneration into a proposal to delete most of the project. Which
	// generators ran is read from the results; that a generator was held back
	// is the one thing the results cannot say, because a generator nobody asked
	// leaves nothing behind to be missing from.
	Partial bool
}

DiffOptions scope what a comparison is entitled to call stale.

type Entry

type Entry struct {
	// Path is relative to the project root, in slash form, so a manifest
	// committed on one machine reads correctly on another.
	Path      string `json:"path"`
	Generator string `json:"generator"`
	Mode      string `json:"mode"`
	SHA256    string `json:"sha256"`
}

Entry is one recorded file.

type Generator

type Generator interface {
	// Name is the registry key, and what rig.yaml refers to.
	Name() string
	// Description is one line, shown by `rig generators`.
	Description() string
	// Version changes when the output format changes, so a stale manifest can
	// be recognized as stale.
	Version() string

	// Generate must be pure: the same document and options must always produce
	// byte-identical artifacts. No filesystem, no clock, no network, and no
	// iteration over a map without sorting it first.
	//
	// Whatever the output format can say about a field, it says with the
	// document's own words: ir.Field.Description is the single copy of the
	// human-readable text, and an output that has somewhere to put one — a doc
	// comment, an OpenAPI description, a JSDoc block — puts that one there
	// rather than a paraphrase of it.
	Generate(ctx context.Context, doc *ir.Document, opts Options) ([]Artifact, error)
}

Generator turns a document into files.

type Manifest

type Manifest struct {
	// Version is the manifest format, not the tool's.
	Version int `json:"version"`
	// IRHash identifies the document the recorded files were generated from.
	IRHash string `json:"ir_hash,omitempty"`
	// Generators maps a generator name to the version that last ran.
	Generators map[string]string `json:"generators,omitempty"`
	// Files is every file rig wrote, ordered by path.
	Files []Entry `json:"files"`
}

Manifest records what rig wrote, so that a later run can tell its own output from someone else's edits.

It is not a cache. Generation always runs; the manifest exists to answer two questions a cache would not: is this file still claimed by a generator, and has anyone changed it since rig wrote it.

func LoadManifest

func LoadManifest(root string) (*Manifest, error)

LoadManifest reads the manifest for a project. A missing manifest is not an error: the first run has nothing to remember.

func NewManifest

func NewManifest() *Manifest

NewManifest builds an empty manifest.

func Write

func Write(root string, results []Result, deltas []Delta, opt WriteOptions) (*Manifest, error)

Write applies the deltas and returns the manifest describing the result.

A conflict stops the whole run before anything is written. Applying half a generation and then refusing the rest would leave the tree in a state neither the tool nor the developer can reason about.

func (*Manifest) Lookup

func (m *Manifest) Lookup(rel string) (Entry, bool)

Lookup returns the recorded entry for a path relative to the root.

func (*Manifest) Save

func (m *Manifest) Save(root string) error

Save writes the manifest.

type Options

type Options struct {
	// OutDir is where the artifacts go, resolved to an absolute path.
	OutDir string
	// Root is the project root.
	//
	// Almost everything a generator writes belongs under OutDir. The exception
	// is a hand-owned scaffold, which belongs where the developer works rather
	// than beside the code it implements — so a generator that emits one needs
	// to know where the project starts.
	Root string
	// Raw is this generator's own options block from rig.yaml.
	Raw map[string]any
}

Options are what a generator is configured with.

type Registry

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

Registry holds the known generators.

func NewRegistry

func NewRegistry() *Registry

NewRegistry builds an empty registry.

func (*Registry) All

func (r *Registry) All() []Generator

All returns every registered generator, ordered by name.

func (*Registry) Get

func (r *Registry) Get(name string) (Generator, bool)

Get returns a generator by name.

func (*Registry) MustRegister

func (r *Registry) MustRegister(g Generator)

MustRegister adds a generator, panicking on a duplicate name.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns every registered generator, sorted.

type Result

type Result struct {
	Generator string
	Version   string
	Artifacts []Artifact
}

Result is what one generator produced. Paths are absolute by this point.

func Run

func Run(ctx context.Context, r *Registry, doc *ir.Document, root string, specs []Spec) ([]Result, error)

Run executes the requested generators against a document.

A document that failed validation is refused outright. Generating from a schema rig knows to be wrong would produce code that compiles and misbehaves, which is far worse than producing nothing.

type Spec

type Spec struct {
	Name    string
	OutDir  string
	Options map[string]any
}

Spec selects one generator and configures it.

type Status

type Status string

Status is what would happen, or did happen, to one file.

const (
	// Added is a file that does not exist yet.
	Added Status = "add"
	// Changed is a generated file whose content differs.
	Changed Status = "change"
	// Unchanged is already correct.
	Unchanged Status = "ok"
	// Stale is a file rig wrote that no generator claims any more, left behind
	// by a renamed table or a removed generator.
	Stale Status = "stale"
	// Conflict is a generated file someone has edited by hand. Overwriting it
	// would throw that work away without asking.
	Conflict Status = "conflict"
	// Kept is a create-once file that already exists and is left alone.
	Kept Status = "keep"
)

type WriteMode

type WriteMode int

WriteMode says who owns a generated file.

const (
	// Overwrite is rewritten on every run. Such a file is gitignored, excluded
	// from linting, and never edited by hand.
	Overwrite WriteMode = iota

	// CreateOnce is written once at its final name and never touched again.
	//
	// There is no rename step: a scaffold that needs a second command before it
	// is usable is a papercut, and the manifest remembers what was written well
	// enough to leave it alone.
	CreateOnce
)

func (WriteMode) String

func (m WriteMode) String() string

String is the mode as it appears in a manifest and in `rig generate`'s report of what it did.

type WriteOptions

type WriteOptions struct {
	// Force overwrites files that were edited by hand.
	Force bool
	// Prune deletes files no generator claims any more.
	Prune bool
	// Previous is the manifest this run started from, if there was one.
	//
	// The new manifest is built from this run's deltas, and a run narrowed by
	// --only has no deltas for the generators it held back. Without their old
	// entries carried across, a narrow regeneration would cost every other
	// generator's files the one record that tells a hand edit from output rig
	// has not caught up with.
	Previous *Manifest
}

WriteOptions tune how deltas are applied.

Jump to

Keyboard shortcuts

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