engine

package
v1.231.0-rc.4 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultMergeThreshold = 50

DefaultMergeThreshold is the default percentage of changed lines allowed in a 3-way merge before --update fails instead of applying it. Exported so cmd/scaffold and cmd/init can register their --max-changes flag's default against the same single source of truth this package's own NewProcessor uses, instead of duplicating the literal.

View Source
const FileContextKey = "File"

FileContextKey is the reserved answers key the current discovered file's own path travels under, mirroring MatrixKey: ProcessTemplateWithDelimiters hoists it onto the template root as "file", so a directory-level spec.files entry (path: a glob matching many discovered files) can write {{ .file.RelPath }} in its target: to differentiate one matched file's output from another's -- see FileContext.

View Source
const MatrixKey = "Matrix"

MatrixKey is the reserved answers key one resolved matrix combination travels under, from ExpandMatrix's result being stashed in the per-combination values map (see pkg/generator/ui) through to ProcessTemplateWithDelimiters, which hoists it onto the template root as "matrix" -- so a template writes .matrix.<axis> directly, matching the namespace the workflow matrix step's own {{ .matrix.<axis> }} uses.

Variables

This section is empty.

Functions

func ExpandMatrix added in v1.226.0

func ExpandMatrix(matrix map[string]any, answers map[string]interface{}, render AxisRenderer, delimiters []string) ([]map[string]string, error)

ExpandMatrix resolves a FileSpec's Matrix axes against answers and returns their full Cartesian product as one row (map[axis]value) per combination, expanded in a sorted, deterministic order per axis -- the same behavior pkg/workflow's own matrix step expansion has, so regenerating the same answers produces the same file set, in the same order, every time. Delimiters is the active scaffold's left/right template delimiter pair (see defaultDelimiters for what an empty/nil value defaults to).

func TargetReferencesFileContext added in v1.230.0

func TargetReferencesFileContext(target string, delimiters []string) (bool, error)

TargetReferencesFileContext reports whether target's parsed template contains a genuine field-access node reading .file.Path or .file.RelPath -- the two exported fields of FileContext, hoisted onto the template root as "file" by ProcessTemplateWithDelimiters -- under the given delimiters.

This walks target's parsed AST rather than doing a raw strings.Contains(target, ".file.") substring search (the bug this function replaces): a substring match would also accept a target that merely contains the literal text ".file." outside any template action (e.g. an output filename like "output.file.txt"), or an invalid field reference like ".file.Unknown" that can never actually differentiate matched files -- both would let a directory-matrix target: back into production that still collides on the *first* two matched files' writes. See validateDirectoryMatrixTargetsDifferentiate in pkg/generator/ui, which calls this before any file in the run is written.

Types

type AxisRenderer added in v1.226.0

type AxisRenderer func(expr string, answers map[string]interface{}, delimiters []string) ([]string, error)

AxisRenderer renders a Go-template matrix-axis expression (e.g. "{{ collectKeys answers.environments }}") against answers into its resolved list of string values, honoring the scaffold's own spec.delimiters override. A nil renderer disables template-expression axes, keeping ExpandMatrix's core algorithm free of any templating dependency -- see Processor.RenderAnswersListExpression in funcs.go for the real implementation.

type File

type File struct {
	Path        string      // Path to the file, may contain template syntax for dynamic naming
	Content     string      // File content, processed as template if IsTemplate is true
	IsTemplate  bool        // Whether to process Content as a Go template
	Permissions os.FileMode // Unix file permissions to apply when creating the file

	// OriginalSourcePath is the file's own path as discovered in the
	// template's source tree, before any spec.files[].target: templating
	// (see pkg/generator/ui's toEngineFile/writeOneOutput, which populate
	// this from the tmpl.File the discovery walk produced -- Path above is
	// overwritten with the target: template string, so this is the only
	// place that original discovered path survives). Empty for any caller
	// that doesn't distinguish source discovery from output-path
	// templating (e.g. most direct engine tests).
	//
	// determineBaseContent uses this as a secondary merge-base candidate:
	// when a spec.files[] entry's target: changes (e.g. adopting a
	// glob+.file.RelPath-based target on a file that previously rendered
	// verbatim to its own discovered path), the first `--update` after the
	// change writes the new path fresh via writeNewFile since it doesn't
	// exist yet -- but every `--update` after that finds the new path on
	// disk and looks for its merge base in git history, where only the OLD
	// path (often identical to OriginalSourcePath, when the entry
	// previously had no target: at all) was ever committed. Without this
	// fallback that lookup finds nothing and permanently, silently treats
	// the file as user-added -- freezing its content at whatever the first
	// post-migration update wrote, with no further template updates ever
	// applied and no warning. See determineBaseContent's own doc comment.
	OriginalSourcePath string
}

File represents a file to be processed by the templating engine. It contains the file path (which can itself be a template), the content, whether the content should be processed as a template, and the file permissions.

type FileContext added in v1.230.0

type FileContext struct {
	Path    string
	RelPath string
}

FileContext is the per-file template data hoisted under FileContextKey. Path is the file's own path as discovered in the template's source tree. RelPath is Path with the matching spec.files[] entry's glob literal-prefix stripped (see pkg/utils.WildcardRelPath) -- equal to Path when that entry's path has no glob metacharacter, since there's no literal prefix to strip. This is what lets a single directory-level matrix entry (e.g. path: "components/**") preserve each matched file's own relative position under a combination-specific target, e.g. target: "environments/{{ .matrix.env }}/{{ .file.RelPath }}".

type FileSkippedError

type FileSkippedError struct {
	Path         string // Original file path from the template
	RenderedPath string // Rendered path after template processing
	// Reason, when non-empty, overrides the generic "(skipped)" UI label with a
	// more specific explanation (e.g. "the user deleted this file" -- see
	// ProcessFile's update-mode deletion check). Empty for every other skip
	// reason, preserving today's generic message.
	Reason string
}

FileSkippedError represents when a file is intentionally skipped during processing. Files may be skipped when their rendered path contains empty segments, special values like "false" or "<no value>", or other indicators that the file should not be created.

func (*FileSkippedError) Error

func (e *FileSkippedError) Error() string

Error returns a formatted error message indicating the file was skipped.

type Processor

type Processor struct {
	DryRun bool // When true, compute rendering/merge but skip writing to disk
	// contains filtered or unexported fields
}

Processor handles template processing for scaffold and init commands. It provides template rendering with Gomplate and Sprig functions, file path templating, and intelligent file merging capabilities.

func NewProcessor

func NewProcessor() *Processor

NewProcessor creates a new template processor with default settings. The processor is initialized with a 50% threshold for 3-way merges, meaning merges will be rejected if more than 50% of lines would change.

func (*Processor) ContainsUnprocessedTemplates

func (p *Processor) ContainsUnprocessedTemplates(content string) bool

ContainsUnprocessedTemplates checks if the given content contains unprocessed template syntax.

func (*Processor) ContainsUnprocessedTemplatesWithDelimiters

func (p *Processor) ContainsUnprocessedTemplatesWithDelimiters(content string, delimiters []string) bool

ContainsUnprocessedTemplatesWithDelimiters checks if the given content contains unprocessed template syntax with custom delimiters.

func (*Processor) Merge

func (p *Processor) Merge(base, ours, theirs, fileName string) (*merge.MergeResult, error)

Merge performs a 3-way merge using the internal merger. Parameters:

  • base: The original template content (before any processing)
  • ours: The user's current version (what exists on disk)
  • theirs: The new template content (after processing)
  • fileName: The file name for merge strategy detection

func (*Processor) ProcessFile

func (p *Processor) ProcessFile(file File, targetPath string, force, update bool, scaffoldConfig interface{}, userValues map[string]interface{}) error

ProcessFile processes a file with templating support, handling path rendering, skip logic, and merge/overwrite behavior based on flags.

The method performs the following steps:

  1. Renders the file path as a template (supports dynamic file naming)
  2. Checks if the file should be skipped based on the rendered path
  3. Creates necessary directories
  4. Handles existing files based on force/update flags: - force: overwrites existing files - update: performs a 3-way merge with existing content - neither: returns an error if file exists
  5. Processes file content as a template if IsTemplate is true
  6. Writes the final content to disk with specified permissions

Returns FileSkippedError if the file is intentionally skipped (not considered an error).

func (*Processor) ProcessTemplate

func (p *Processor) ProcessTemplate(content string, targetPath string, scaffoldConfig interface{}, userValues map[string]interface{}) (string, error)

ProcessTemplate processes Go templates in file content.

func (*Processor) ProcessTemplateWithDelimiters

func (p *Processor) ProcessTemplateWithDelimiters(content string, targetPath string, scaffoldConfig interface{}, userValues map[string]interface{}, delimiters []string) (string, error)

ProcessTemplateWithDelimiters processes Go templates in file content with custom delimiters.

func (*Processor) RenderAnswersExpression added in v1.230.0

func (p *Processor) RenderAnswersExpression(expr string, answers map[string]interface{}, delimiters []string) (any, error)

RenderAnswersExpression renders a Go-template expression against answers and returns its resolved value decoded from JSON, whatever shape the expression produces (string, bool, number, list, or map) -- the general counterpart to RenderAnswersListExpression, which requires and decodes a list of strings specifically. Exported for callers that need an answers-derived value of arbitrary shape rather than a list.

Answers is exposed as a zero-arg function rather than a data field: Go's template grammar only chains ".field" off "." or a "$var", never off a bare identifier -- but it does chain off that identifier's call result, which is what lets "answers.environments" read as "call answers(), then select .environments".

Go's text/template can only render text, not a typed value like the []string collectKeys returns or the bool/number/map a computed field's expression might produce, so the pipeline gets "| toJson" appended internally and the result is decoded with json.Unmarshal -- transparent to scaffold.yaml authors, who never write toJson themselves.

func (*Processor) RenderAnswersListExpression added in v1.228.0

func (p *Processor) RenderAnswersListExpression(expr string, answers map[string]interface{}, delimiters []string) ([]string, error)

RenderAnswersListExpression renders a Go-template expression (e.g. "{{ collectKeys answers.environments }}") against answers and returns its resolved list of string values. Satisfies the AxisRenderer type ExpandMatrix accepts and the FieldOptionsRenderer type a scaffold field's dynamic `options:` accepts (pkg/project/config duplicates that type rather than importing this one -- see its own doc comment for the import-cycle reason) -- both a matrix axis expression and an `options:` expression get the same FuncMap and delimiters override scaffold templates already use, and the same error reporting, since neither caller is more "correct" to name this after.

A thin list-shaped specialization of RenderAnswersExpression: see that method's doc comment for the shared rendering mechanics (answers binding, the "| toJson" wrap, JSON decoding).

func (*Processor) SetConflictStrategy

func (p *Processor) SetConflictStrategy(strategy merge.ConflictStrategy)

SetConflictStrategy sets how a real ours/theirs divergence is resolved during a 3-way merge. Note: SetMaxChanges replaces p.merger wholesale, so call SetConflictStrategy after SetMaxChanges if both are used, or the strategy set here would be discarded.

func (*Processor) SetDryRun

func (p *Processor) SetDryRun(dryRun bool)

SetDryRun toggles dry-run mode. When enabled, ProcessFile still renders templates, loads the git merge base, performs the 3-way merge, and runs conflict/threshold checks — every step that can fail — but skips the final write to disk, so a dry-run preview reports real conflicts instead of only listing file paths.

func (*Processor) SetMaxChanges

func (p *Processor) SetMaxChanges(thresholdPercent int)

SetMaxChanges sets the maximum percentage of changes allowed for 3-way merge operations. A thresholdPercent of 0 disables the check entirely: the merge is never rejected for having too many changes, regardless of size. Any positive value is compared against a computed change percentage that has no upper bound (see TextMerger/YAMLMerger's calculateChangePercentage), so no positive thresholdPercent is a guaranteed bypass the way 0 is -- raising it only makes rejection less likely, never impossible.

func (*Processor) SetMergeDriver added in v1.226.0

func (p *Processor) SetMergeDriver(driver merge.Driver)

SetMergeDriver sets which merger handles every file during a 3-way merge.

func (*Processor) SetRecreateDeleted added in v1.231.0

func (p *Processor) SetRecreateDeleted(recreate bool)

SetRecreateDeleted controls whether --update recreates a file the user deleted but the template still generates. False (the default) leaves the deletion in place (see Processor.deletedByUser). True restores the pre-deletion-handling behavior of always recreating it.

This is deliberately independent of --force/--merge-strategy rather than reusing --force: --force combined with --update already means "on conflict, the template's version wins" (ResolveConflictStrategy rejects --force with an explicit non-theirs --merge-strategy as contradictory), so tying recreation to it would make "manual conflict resolution" and "recreate what I deleted" mutually exclusive, even though a user may reasonably want both at once.

func (*Processor) SetupGitStorage

func (p *Processor) SetupGitStorage(targetPath string, baseRef string) error

SetupGitStorage initializes git-based storage for 3-way merges. The targetPath is used to find the git repository and resolve relative file paths. The baseRef specifies which git reference to use as the base for merges (e.g., "main", "v1.0.0").

Returns an error if:

  • targetPath is not in a git repository
  • baseRef cannot be resolved

func (*Processor) SetupRenderedBaseStorage added in v1.230.0

func (p *Processor) SetupRenderedBaseStorage(targetPath, oldRenderRoot string)

SetupRenderedBaseStorage points the 3-way merge base at a pristine re-render of the template (UpdateStrategyRendered) instead of the target's own git history.

Note: oldRenderRoot is the root of an already-fully-rendered copy of the template at the ref that produced what's currently on disk (see pkg/generator/ui's renderPristineBase) -- unlike SetupGitStorage, there is no repository to open or ref to validate here, since the caller already did the rendering.

Note: targetPath is still required: determineBaseContent uses it (via p.targetPath) to compute each file's base-storage-relative path regardless of which base storage backs it.

func (*Processor) ShouldSkipFile

func (p *Processor) ShouldSkipFile(renderedPath string) bool

ShouldSkipFile determines if a file should be skipped based on its rendered path.

Files are skipped in the following cases:

  • Empty path or special values: "", "false", "null", "<no value>"
  • Paths with empty segments: "foo//bar", "/foo", "foo/"
  • Paths that would create invalid filesystem entries

This is useful for conditional file generation where template variables may evaluate to empty or false values, indicating the file should not be created.

func (*Processor) ValidateNoUnprocessedTemplates

func (p *Processor) ValidateNoUnprocessedTemplates(content string) error

ValidateNoUnprocessedTemplates validates that the processed content doesn't contain unprocessed template syntax.

func (*Processor) ValidateNoUnprocessedTemplatesWithDelimiters

func (p *Processor) ValidateNoUnprocessedTemplatesWithDelimiters(content string, delimiters []string) error

ValidateNoUnprocessedTemplatesWithDelimiters validates that the processed content doesn't contain unprocessed template syntax with custom delimiters.

type UpdateStrategy added in v1.230.0

type UpdateStrategy int

UpdateStrategy controls where a 3-way merge's "base" content comes from during --update. Named distinctly from merge.ConflictStrategy, which instead picks how a real ours/theirs divergence is resolved once base has already been determined -- an unrelated axis.

const (
	// UpdateStrategyTracked is the default (zero value): base is read from
	// the target's own git history at a pinned commit (see SetupGitStorage).
	UpdateStrategyTracked UpdateStrategy = iota
	// UpdateStrategyRendered: base is a pristine re-render of the template at
	// the ref that produced what's currently on disk, using the answers
	// recorded from that generation (see SetupRenderedBaseStorage). Has no
	// dependency on the target's git history.
	UpdateStrategyRendered
)

func ParseUpdateStrategy added in v1.230.0

func ParseUpdateStrategy(s string) (UpdateStrategy, error)

ParseUpdateStrategy parses a --update-strategy flag value. An empty string (flag not set) maps to the default UpdateStrategyTracked.

Jump to

Keyboard shortcuts

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