exlab_wizard.template.lint#

Single source of truth for template (copier.yml) validation.

The §5.1 checks that used to live inline in exlab_wizard.template.copier_driver.TemplateEngine.resolve() are factored out here so resolve-time and author-time validation share one rule set. The GUI authoring form calls lint_template() / lint_manifest_dict() to gate saves; TemplateEngine.resolve calls lint_manifest_dict() and re-raises its ERROR findings as the existing TemplateLoadError / TemplateCoreFieldRedeclaredError.

Findings are returned as LintFinding (a small, lint-specific shape) rather than the validator’s run-output Finding – the latter carries rule / run_path / offending_path / offending_kind fields that are meaningless for a copier.yml.

Two tiers:

  • error – the manifest/template is unusable; a save is refused and resolve raises.

  • warn – the manifest is usable but deviates from convention; a save proceeds with a banner.

lint_manifest_dict() validates an already-parsed manifest mapping (no file I/O). lint_template() adds the file-level checks (copier.yml existence / readability / YAML-parse) and a Jinja2 syntax check across every *.jinja file under the template root.

Functions

has_errors(findings)

Return True if any finding has severity == "error".

lint_manifest_dict(manifest, manifest_path)

Validate an already-parsed copier.yml mapping.

lint_template(template_dir)

Validate a template directory end-to-end.

Classes

LintFinding(code, message, severity[, path])

One template-validation finding.

class exlab_wizard.template.lint.LintFinding(code, message, severity, path=None)[source]#

Bases: object

One template-validation finding.

code#

Stable machine code (e.g. "template_type_missing").

message#

Human-readable description (carries the same wording TemplateEngine.resolve historically raised, so the re-raised exceptions are message-identical).

severity#

"error" (refuse / raise) or "warn" (proceed).

path#

Relative path of the offending file for file-scoped findings (e.g. a *.jinja with a syntax error); None for manifest-level findings.

Parameters:
code: str#
message: str#
path: str | None = None#
severity: Literal['error', 'warn']#
to_dict()[source]#

Return a JSON-serialisable mapping of this finding.

Return type:

dict[str, Any]

exlab_wizard.template.lint.has_errors(findings)[source]#

Return True if any finding has severity == "error".

Parameters:

findings (list[LintFinding])

Return type:

bool

exlab_wizard.template.lint.lint_manifest_dict(manifest, manifest_path)[source]#

Validate an already-parsed copier.yml mapping.

Ports the §5.1 metadata checks out of TemplateEngine.resolve and _extract_readme_fields. Does not check file existence or YAML-parse the file – those are file-level and handled by lint_template(). Messages match the wording TemplateEngine.resolve historically raised so re-raised exceptions are byte-identical.

Parameters:
  • manifest (dict[str, Any]) – The parsed copier.yml body.

  • manifest_path (Path) – Path to the manifest, used only to prefix messages.

Return type:

list[LintFinding]

Returns:

All findings (ERROR + WARN), in a stable order.

exlab_wizard.template.lint.lint_template(template_dir)[source]#

Validate a template directory end-to-end.

Runs the file-level checks (copier.yml existence, readability, YAML-parse), then delegates the manifest checks to lint_manifest_dict(), then parses every *.jinja file under template_dir with Jinja2 and reports syntax errors.

Parameters:

template_dir (Path) – The template root (directory containing copier.yml).

Return type:

list[LintFinding]

Returns:

All findings (ERROR + WARN). A fatal copier.yml problem short-circuits the manifest checks (but the Jinja scan still runs).