renderer

package
v0.0.0-...-99f4f86 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package renderer executes a resolver.Plan's templates into a target directory. Rendering is transactional: targetDir either ends up fully populated or is never created. See docs/architecture.md for the renderer's role and guarantees. Phase 1 scope: Go output only.

Index

Constants

This section is empty.

Variables

View Source
var ErrTargetExists = errors.New("target directory already exists")

ErrTargetExists is returned when targetDir already exists. Render refuses to touch it — no parents created, nothing written inside it.

View Source
var ErrTargetMissing = errors.New("target directory does not exist")

ErrTargetMissing is returned by RenderAdd when targetDir does not exist — the mirror image of ErrTargetExists: RenderAdd only ever writes into an already-generated project, never creates one.

Functions

This section is empty.

Types

type AddError

type AddError struct {
	FileCollisions []*FileCollisionError
}

AddError aggregates every file collision RenderAdd finds before writing anything, the same aggregate-everything-first posture as RenderError.

func (*AddError) Error

func (e *AddError) Error() string

func (*AddError) Unwrap

func (e *AddError) Unwrap() []error

Unwrap exposes the individual collisions for errors.As and errors.Is.

type AddOptions

type AddOptions struct {
	// Force skips the per-file collision check — a destination a new
	// module's template would write to that already exists on disk.
	// It never bypasses anything validatePlan finds (DependencyConflictError,
	// EnvVarConflictError) or anything resolver.Resolve itself rejects
	// (cycles, requires/conflicts, unsupported language): those represent
	// a genuine incompatibility between modules, not an incidental file
	// clash, and no flag overrides them in v1.
	Force bool
	// DryRun computes and reports what RenderAdd would do without
	// writing anything — no files, no go.mod/.env.example edits, no
	// state.json update.
	DryRun bool
}

AddOptions configures RenderAdd.

type AddResult

type AddResult struct {
	NewModules []AddedModule // modules not previously present, now rendered
	Files      []string      // template destinations written, relative to targetDir
	GoModDeps  []string      // dependency module paths newly added to go.mod
	EnvVars    []string      // env var names newly appended to .env.example
}

AddResult summarizes what RenderAdd did, or — under AddOptions.DryRun — would do. Every slice is sorted for deterministic output.

type AddedModule

type AddedModule struct {
	Name    string
	Version string
}

AddedModule is one module RenderAdd newly rendered.

type Context

type Context struct {
	ProjectName string // e.g. "myapp"
	ModulePath  string // Go import path, e.g. "github.com/user/myapp"
	GoVersion   string // e.g. "1.26" — target project's go.mod go line
}

Context is the substitution data available to every template.

func ContextFromExisting

func ContextFromExisting(targetDir string) (Context, error)

ContextFromExisting derives a Context for RenderAdd from a project that already exists on disk: ModulePath and GoVersion are read back from the real go.mod (keel add never asks the user to re-supply them — they were fixed at keel init time), and ProjectName from targetDir's own directory name.

type DependencyConflictError

type DependencyConflictError struct {
	Module   string
	VersionA string
	ViaA     string
	VersionB string
	ViaB     string
}

DependencyConflictError reports the same Go module path required at two different versions. Via names the requiring module, same provenance style as the resolver's Conflict.

func (*DependencyConflictError) Error

func (e *DependencyConflictError) Error() string

type DuplicateOutputPathError

type DuplicateOutputPathError struct {
	Path             string
	ModuleA, ModuleB string
}

DuplicateOutputPathError reports two modules writing to the same destination path.

func (*DuplicateOutputPathError) Error

func (e *DuplicateOutputPathError) Error() string

type EnvVarConflictError

type EnvVarConflictError struct {
	Name      string
	ModuleA   string
	RequiredA bool
	DefaultA  string
	ModuleB   string
	RequiredB bool
	DefaultB  string
}

EnvVarConflictError reports the same environment variable declared by two modules with different Required or Default settings. Identical declarations (same Required and Default; Description may differ, that's just prose) dedupe silently instead of producing this error.

func (*EnvVarConflictError) Error

func (e *EnvVarConflictError) Error() string

type FileCollisionError

type FileCollisionError struct {
	Module string
	Path   string
}

FileCollisionError reports a new module's template destination that already exists on disk in the target project. This is the only failure --force is allowed to bypass — a genuine conflict between modules (DependencyConflictError, EnvVarConflictError) or in the resolver's own requires/conflicts graph is never something a file flag should override.

func (*FileCollisionError) Error

func (e *FileCollisionError) Error() string

type ModuleInfo

type ModuleInfo struct {
	Name    string
	Summary string
}

ModuleInfo is the per-module data exposed to templates under the "Modules" key. It is derived by Render from plan.Modules, sorted alphabetically by name — deliberately NOT plan.Modules' own dependency order (topological, e.g. base last). Templates like base's README want a stable, human-readable listing, not build order.

type PathEscapeError

type PathEscapeError struct {
	Module string
	Path   string
}

PathEscapeError reports a template destination that would write outside the target directory. Computed independently of manifest validation — Render never trusts that a Template.To was already checked.

func (*PathEscapeError) Error

func (e *PathEscapeError) Error() string

type RenderError

type RenderError struct {
	PathEscapes          []*PathEscapeError
	DuplicateOutputPaths []*DuplicateOutputPathError
	DependencyConflicts  []*DependencyConflictError
	EnvVarConflicts      []*EnvVarConflictError
}

RenderError aggregates every failure found while validating a plan (pass 1), rather than stopping at the first.

func (*RenderError) Error

func (e *RenderError) Error() string

func (*RenderError) Unwrap

func (e *RenderError) Unwrap() []error

Unwrap exposes the individual failures for errors.As and errors.Is.

type Renderer

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

Renderer executes module templates sourced from an fs.FS. A manifest Template.From (e.g. "templates/go/x.tmpl") resolves against templates as path.Join("modules", moduleName, from).

func New

func New(templates fs.FS) *Renderer

New constructs a Renderer over templates, e.g. an embed.FS in the real CLI or a fstest.MapFS in tests.

func (*Renderer) Render

func (r *Renderer) Render(plan *resolver.Plan, ctx Context, targetDir string) error

Render executes plan against ctx and writes the result to targetDir. targetDir must not already exist. On any failure, targetDir is never created and no partial output is left behind.

Pass 1 validates the whole plan — destination paths, containment, and merged Go dependencies — without touching disk, aggregating every violation it finds. Only if that passes does pass 2 render templates into a staging directory beside targetDir and atomically rename it into place, so targetDir either appears fully populated or not at all.

func (*Renderer) RenderAdd

func (r *Renderer) RenderAdd(plan *resolver.Plan, ctx Context, targetDir string, existing []state.Module, opts AddOptions) (*AddResult, error)

RenderAdd renders newly-requested modules' templates into an already-existing project, merging go.mod and .env.example instead of overwriting them, and updates .keel/state.json. This is keel add's entry point — the mirror image of Render, which refuses to run against a directory that already exists; RenderAdd refuses to run against one that doesn't (ErrTargetMissing).

plan is the full resolved closure — existing modules (named in existing) union whatever was newly requested — exactly as resolver.Resolve/ResolveProject already produces for keel init. Nothing about conflict, cycle, or requires detection changes for keel add: it is the same Resolve call and the same validatePlan pass, over a larger requested set. A module already listed in existing is never re-rendered; its templates are assumed already on disk from a prior init or add.

Unlike Render, RenderAdd writes directly into targetDir rather than staging into a sibling directory and renaming — there is no atomic swap available for a directory that already has other content in it. If writing fails partway through, already-written new files are left in place; validation (the conflict and collision checks) happens entirely before the first write, so a failure discovered there always leaves targetDir untouched, but an I/O failure mid-write does not roll back. This asymmetry with Render's all-or-nothing guarantee is a property of adding to an existing tree, not an oversight.

type TemplateError

type TemplateError struct {
	Module string
	Path   string
	Err    error
}

TemplateError wraps a parse or execution failure for one module's template.

func (*TemplateError) Error

func (e *TemplateError) Error() string

func (*TemplateError) Unwrap

func (e *TemplateError) Unwrap() error

Jump to

Keyboard shortcuts

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