gen

package
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: GPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package gen is the compiler side of the plugin protocol: which backends exist, how a request is built, and what happens to the files that come back.

It is private and free to change. The `plugin` package is the surface a backend author sees.

Index

Constants

View Source
const CommandPrefix = "tdl-gen-"

CommandPrefix is what a target name is prefixed with to find its executable. A target tdl has no backend for resolves to tdl-gen-<name> on PATH, the way git and protoc find their subcommands.

View Source
const DefaultTimeout = 2 * time.Minute

DefaultTimeout bounds how long a plugin has to answer.

A hung plugin in CI should fail with a diagnosis rather than consume the job's whole time budget and report nothing. A legitimately slow backend will want to raise this, which is what tdl.toml is for; until that exists it is a constant.

View Source
const MarkerName = ".tdl-output"

MarkerName is the file tdl drops in a directory it writes to.

It is how --clean knows a directory is its own. A directory holding files but no marker belongs to someone else, and cleaning it is an error rather than a judgment call: output directories are not always exclusively owned by tdl.

View Source
const WatchInterval = time.Second

WatchInterval is how often a watched file is checked.

Polling rather than an OS notification API keeps this dependency-free and behaves the same everywhere; a second is fast enough for a person saving a file and slow enough to be free.

Variables

View Source
var ErrNotOurs = errors.New("the output directory was not written by tdl")

ErrNotOurs is returned when --clean is asked to empty a directory that has files but no marker.

Functions

func Builtin

func Builtin(name string) (plugin.Backend, bool)

Builtin returns the backend compiled in under name.

func BuiltinNames

func BuiltinNames() []string

BuiltinNames lists the compiled-in backends, sorted.

func CheckDirectives

func CheckDirectives(target string, model *ir.Model, desc plugin.Description) []*plugin.Diagnostic

CheckDirectives compares the directives a target block uses against what its backend says it understands.

A declared directive used with the wrong number or kind of arguments is an error, reported with the position in the .tdl file, before anything is generated: the alternative is a backend discovering it half way through writing files.

A directive the backend did not declare is a warning and is passed through anyway. Under-declaring is a plugin bug that should not break a working project, and a backend is free to handle more than it advertises. The warning still names it, so a typo stays visible.

func Clean

func Clean(out string) ([]string, error)

Clean empties an output directory tdl owns, leaving the marker.

A directory that does not exist is already clean. One that exists and is empty is adopted, since there is nothing there to belong to anyone else. One with contents and no marker is ErrNotOurs.

func Fatal added in v0.1.8

func Fatal(diags []*plugin.Diagnostic) bool

Fatal reports whether any diagnostic is an error rather than a warning.

func Mark

func Mark(out string) error

Mark writes the ownership marker into out.

func Owned

func Owned(out string) bool

Owned reports whether out carries the marker.

func Resolve

func Resolve(name string) (plugin.Backend, error)

Resolve returns the backend serving a target: the compiled-in one if there is one, otherwise tdl-gen-<name> on PATH.

Both speak the same protocol, so everything above this treats them the same. Preferring the built-in means a name tdl ships cannot be shadowed by whatever happens to be on PATH.

func Watch

func Watch(done <-chan struct{}, path string, onChange func())

Watch calls onChange whenever path's contents change, until ctx is done.

It compares contents rather than modification time, so an editor that rewrites a file without changing it does not trigger a regeneration.

A poll can land while a file is being written, and an editor that truncates before writing will briefly present an empty file. That reads as a change, and the regeneration it triggers fails to parse. The next poll sees the finished file and succeeds, so it corrects itself; an editor that saves by renaming never shows the intermediate state at all.

func Write

func Write(out string, files []*plugin.File) ([]string, error)

Write puts a response's files under out.

A backend returns contents rather than writing them, which is what lets this enforce where they land. A path is relative to out, and an absolute one or one climbing out with ".." is refused before anything is written: a plugin cannot reach outside the directory the project pointed it at, whatever it intends.

Types

type Mode

type Mode int

Mode is what a run does with the files a backend returns.

const (
	// ModeWrite writes them.
	ModeWrite Mode = iota
	// ModeVerify compares them against disk and writes nothing.
	ModeVerify
	// ModeClean empties the output directory first, then writes.
	ModeClean
)

type Result

type Result struct {
	Target      string
	Written     []string
	Removed     []string
	Stale       []Stale
	Diagnostics []*plugin.Diagnostic
}

Result is what one target produced.

func Run

func Run(ctx context.Context, backend plugin.Backend, target Target, model *ir.Model, mode Mode) (Result, error)

Run generates one target and does what mode says with the result.

type Session

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

Session is a plugin kept alive across generations.

A plugin that declared reuse serves more than one request on one connection, which is what makes a watch loop cheap. It must treat each request as independent; carrying state between them is a plugin bug, and nothing here can catch it.

func Open

func Open(ctx context.Context, sub *Subprocess) (*Session, error)

Open starts a plugin and holds the connection if it declared reuse.

A plugin that did not gets a fresh process per generation, which is the default: reuse is something a backend opts into by saying it can. The one handshake answers both questions, so a plugin that will not shake hands is an error here rather than a description of nothing.

func (*Session) Close

func (s *Session) Close()

Close stops a held plugin.

func (*Session) Describe

func (s *Session) Describe() plugin.Description

Describe reports what the plugin said about itself when it was opened, without starting another process to ask again.

func (*Session) Generate

func (s *Session) Generate(ctx context.Context, req *plugin.Request) (*plugin.Response, error)

Generate serves one request, over the held connection when there is one.

func (*Session) Restarts

func (s *Session) Restarts() int

Restarts counts how many times the held plugin was replaced because its binary changed.

func (*Session) Reused

func (s *Session) Reused() bool

Reused reports whether this session is holding a connection, which is what a test asserts rather than inferring from timing.

type Stale

type Stale struct {
	Path   string
	Reason string
}

Stale describes one way an output directory disagrees with what a backend would write.

func Verify

func Verify(out string, files []*plugin.File) ([]Stale, error)

Verify compares a response against what is on disk without writing.

The backend still produces contents: a dry run is about not writing, not about producing less. That is what makes the check meaningful, since the only way to know whether output is stale is to generate it.

type Subprocess

type Subprocess struct {
	Name    string
	Path    string
	Timeout time.Duration
}

Subprocess is a backend running as tdl-gen-<name>.

It implements plugin.Backend, so everything above it treats a plugin and a compiled-in backend the same way. That is the protocol's one real claim, and this type is what makes it testable rather than asserted.

func Find

func Find(name string) (*Subprocess, error)

Find locates the executable for a target name.

func (*Subprocess) Describe

func (s *Subprocess) Describe() plugin.Description

Describe starts the plugin, shakes hands, and stops it again.

Describing costs a process. Generate starts its own, so a description fetched here is not carried into it; keeping one connection across both is what phase 8's reuse is for.

func (*Subprocess) Generate

func (s *Subprocess) Generate(ctx context.Context, req *plugin.Request) (*plugin.Response, error)

Generate runs one request through the plugin.

type Target

type Target struct {
	Name  string
	Out   string
	Block *ir.TargetBlock
}

Target is one target block resolved into what a backend needs.

func Targets

func Targets(model *ir.Model, override string) ([]Target, error)

Targets returns the target blocks in a model, with their output directories read from each block's `out` directive.

The directive is where workflow.md puts it: a target block declares where its output goes, and the command line overrides it.

Jump to

Keyboard shortcuts

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