Skip to content

feat(template): a declared template rule, and the tmpl applet on the same renderer - #440

Open
raphaelvigee wants to merge 1 commit into
raphaelvigee/coreutils-rest-of-toolboxfrom
raphaelvigee/coreutils-template-driver
Open

raphaelvigee wants to merge 1 commit into
raphaelvigee/coreutils-rest-of-toolboxfrom
raphaelvigee/coreutils-template-driver

Conversation

@raphaelvigee

@raphaelvigee raphaelvigee commented Aug 29, 2026 •

Copy link
Copy Markdown
Member

Filling in a config file should not require a shell. template(name, src, out, vars) renders a declared template with declared variables, in-process — inputs and outputs fully declared, no sed pipeline, no quoting rules to get wrong.

One feature, two surfaces

The renderer lives in crates/template so the rule and the tmpl applet are the same code rather than two implementations of one syntax:

  • template() — the rule, and the one to reach for. Its inputs are in the cache key.
  • tmpl — the applet, for the mid-recipe case, and it covers envsubst.

They land together because splitting them would put a crate in trunk with one caller and review the same rendering semantics twice.

Two constraints the renderer carries deliberately

  • No non-deterministic builtins. No now(), no randomness — a template that can render differently from identical inputs is a target that can never be a cache hit.
  • TEMPLATE_FORMAT_VERSION in the def hash, so a minijinja upgrade that changes rendering invalidates rather than silently diverging.

vars is hashed from a BTreeMap because it arrives as a HashMap, and hashing it in iteration order would move the def hash on every run.


The stack

Merge bottom-up, and gh stack sync after each one lands — master is squash-only, so the rebase will conflict and the resolution rule in CLAUDE.md applies.

PR What
6 #446 drop the host directories from a target's PATH — breaking
5 #445 the toolbox on by default
4 #440 the template rule and the tmpl applet
3 #453 grep, find, xargs, sed, tar, gzip, zstd
2 #438 the crate, the entry points, the shim directory
1 #451 the runner PATH seam ← base, targets master

Only #451 builds automatically: since #449 a stacked PR is skipped unless it carries ci/force-ci. Every layer was checked locally on its own — cargo build --workspace --all-targets, cargo clippy --workspace --all-targets, and its unit tests — not just at the top of the stack.

🤖 Generated with Claude Code

https://claude.ai/code/session_0181d7hhbYWXT42Z1KQPM29Q

@raphaelvigee
raphaelvigee force-pushed the raphaelvigee/coreutils-template-driver branch from d386399 to 168264d Compare August 29, 2026 21:35
@raphaelvigee
raphaelvigee force-pushed the raphaelvigee/coreutils-template-driver branch from 168264d to 7ae3435 Compare September 3, 2026 16:26
@raphaelvigee
raphaelvigee force-pushed the raphaelvigee/coreutils-template-driver branch from 7ae3435 to cceac42 Compare September 3, 2026 16:56
…same renderer

Filling in a config file should not require a shell. `template(name, src, out,
vars)` renders a declared template with declared variables, in-process
(`crates/builtins/src/plugintemplate`) — inputs and outputs fully declared, no
`sed` pipeline, no quoting rules to get wrong.

The renderer itself lives in `crates/template` so the rule and the `tmpl`
applet are the same code rather than two implementations of one syntax. `tmpl`
covers the mid-recipe case (and `envsubst`); the rule is the one to reach for,
because a rule's inputs are in the cache key and a mid-recipe render's are not.

Two constraints the renderer carries deliberately:

- **No non-deterministic builtins.** No `now()`, no randomness — a template
  that can render differently from the same inputs is a target that can never
  be a cache hit.
- **`TEMPLATE_FORMAT_VERSION` in the def hash**, so a minijinja upgrade that
  changes rendering invalidates rather than silently diverging.

`vars` is hashed from a `BTreeMap` because it arrives as a `HashMap`, and
hashing it in iteration order would move the def hash on every run.

The rule and the applet land together because they are one feature: a shared
renderer used two ways. Splitting them would put a crate in trunk with one
caller and review the same rendering semantics twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0181d7hhbYWXT42Z1KQPM29Q
@raphaelvigee
raphaelvigee force-pushed the raphaelvigee/coreutils-template-driver branch from cceac42 to 31f29a2 Compare September 3, 2026 17:30
@raphaelvigee
raphaelvigee changed the base branch from raphaelvigee/core-utils-templating-engine to raphaelvigee/coreutils-rest-of-toolbox September 3, 2026 17:32
@raphaelvigee raphaelvigee changed the title feat(template): render a declared template, without a shell feat(template): a declared template rule, and the tmpl applet on the same renderer Sep 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant