CLI & Scripts
quilt — the main CLI
File: quilt/src/bin.rs
The quilt binary has three subcommands; run is the default, so quilt <file> [args…] is shorthand for quilt run <file> [args…].
quilt expand <file.rs.quilt>
Expands a .quilt file to the corresponding source file (strips .quilt suffix).
quilt expand path/to/foo.rs.quilt # produces foo.rs
quilt expand path/to/foo.py.quilt # produces foo.py
quilt expand path/to/foo.wgsl.rs.quilt # produces foo.wgsl.rs
Options:
-m, --multi <omni|bootstrap>— select whichMultito use. Defaultomni. Usebootstraponly when expandingmk_meta.rs.quiltto regeneratemeta.rs.
The generated file starts with a comment //! DO NOT EDIT. GENERATED BY quilt expand .... The marker is the one the generated language uses, not always Rust's — # for Python, -- for Lean — asked of the language registry (langs::header_comment, backed by each language's Comments::HEADER), keyed by the output file's extension.
expand is the one subcommand that does require the .quilt suffix, because the output name is the input name with the suffix sliced off — there is nothing to write without one. A file without it is refused with expected a .quilt file.
quilt check <file.rs.quilt>…
Validates one or more .quilt files without writing anything: each file is parsed and expanded exactly as expand would, but the result is discarded. Prints <file>: ok per valid file, reports errors for invalid ones (checking every file before failing), and exits non-zero if any file fails — useful for CI pipelines and pre-commit hooks where you don't want generated files checked into git.
quilt check path/to/foo.rs.quilt
quilt check src/*.quilt
Options:
-m, --multi <omni|bootstrap>— same asexpand.
A leading shebang line is stripped before checking (as run does), so executable scripts check clean.
The language chain is derived the same way run derives it, through one shared helper: the path is resolved (following symlinks) and the chain read off the resolved file's own name, with .quilt stripped if it is there. So an extension-less entry point — bin/issues is a symlink to examples/issue_triage.html.py.quilt — checks as Python, and only the file name counts, so a dot in a parent directory can't leak into the chain. check used to require a literal .quilt suffix on the path it was handed, which meant a script the repo ships to be run could never be validated in CI (issue #188). A name that resolves to no registered language still fails, naming the language rather than the suffix.
quilt check bin/issues # an extension-less `#!/usr/bin/env quilt` script
bin/check-examples checks examples/**/*.quilt plus every #!/usr/bin/env quilt script under bin/, which is what keeps the entry points from rotting.
quilt run <file.rs.quilt> [args…]
Expand a .quilt file to a temp file and run it immediately. The file's inner extension determines the runner:
| Extension | Runner |
|---|---|
.rs | rust-script |
.py | python3 (with PYTHONPATH set to include quilt-python) |
.ts | node --experimental-strip-types (with a node_modules binding the runtime) |
The runner is read from Language::hashbang(). If the language does not return a hashbang, quilt errors.
Trailing arguments after the filename are forwarded to the script.
For Rust scripts, quilt injects a //! [dependencies] cargo manifest block in the generated file pointing at the local quilt crate with the correct feature set.
Since run is the default subcommand, the run keyword can be omitted:
quilt examples/hello.rs.quilt World
quilt examples/hello.py.quilt
quilt examples/staged_pow.ts.quilt
quilt examples/countdown.rs.quilt 5
Binding the runtime
Each host needs its runtime importable, and each language wants that spelled its own way:
- Rust gets the cargo manifest above.
- Python gets
PYTHONPATH. - TypeScript gets a
node_modules. Node's ESM resolver ignoresNODE_PATH, so a bareimport … from "quilt"resolves only against anode_modulesdirectory above the importing file — and a temp file has none.quilt runtherefore gives the script a private directory holding anode_moduleswith two entries, mirroring the browser demos' import map:quilt→quilt-wasm/node(the runtime plus↓) andquilt-wasm→quilt-wasm/pkg(the raw wasm-pack package). Build them first withbin/build-ts; until thenquilt runon a.ts.quiltfile errors telling you so.
Python and TypeScript runs also inherit $QUILT (the path of the running expander) and, for TypeScript, $QUILT_CHAIN (the file's language chain, ground first). That is what lets ↓ re-expand a generated stage — one whose source still contains Quilt glyphs — without needing quilt on PATH. See Reduce on the CLI.
Shebang scripts
.quilt files can be used directly as executables with:
#!/usr/bin/env quilt
as their first line (#!/usr/bin/env quilt also works, but the bare form is portable: Linux passes everything after the interpreter as a single argument, so env would look for a program literally named quilt run). quilt strips the shebang before expansion so the language parser doesn't see #!.
Such a script usually wants no extension at all — bin/issues, a symlink to examples/issue_triage.html.py.quilt. Both run and check resolve the link and take the chain from the target's name, so an entry point can be validated as well as executed.
quilt repl [chain]
An interactive machine session (see docs/design/machines.md): each line is
Quilt source, parsed with the chain, expanded by the ground language's
meta-language, and fed to that language's default machine from the park —
so definitions persist across lines the same way they persist across reduces.
A line that classifies as an expression is a query and prints the value's
literal; definitions and statements feed silently; an error ends the line,
not the session.
$ quilt repl py
quilt repl — ground language py; ctrl-D to exit
py> x = 5
py> x * 8 + 2
42
chain reads like a file stem (py, wgsl.py — rightmost is ground) and
defaults to py. The machine behind the prompt is whatever the language
declares: a persistent shell process for bash/zsh (state is real process
state), the replayed-history script machine for py/ts. Quilt meta-code in
a py session (quotes, ↑) needs the quilt Python runtime importable —
build it once with bin/build-py; plain Python needs nothing.
bin/ scripts
All scripts in bin/ expect the direnv environment to be active (direnv allow from the repo root). They work from any directory when the env is active.
quilt
quilt expand path/to/file.rs.quilt
quilt path/to/file.rs.quilt [args]
A thin wrapper around cargo run -p quiltlang --. Passes all arguments to the quilt binary.
bootstrap
Runs the full two-stage bootstrap that regenerates quilt/src/langs/rust/meta.rs. Both stages run the generator program mk_meta.rs.quilt via quilt, which writes meta.rs; see Bootstrap.
bootstrap
Equivalent to running bootstrap0 then bootstrap1 in order.
bootstrap0
Stage 0 only: quilt -m bootstrap mk_meta.rs.quilt — expands the generator with BootstrapMetaLanguage (built with --no-default-features -F bootstrap) and runs it.
bootstrap0
bootstrap1
Stage 1 only: quilt -m omni mk_meta.rs.quilt — expands the generator with the freshly generated RustMetaLanguage (self-hosted) and runs it. A clean run leaves meta.rs unchanged.
bootstrap1
ctest / lint
Wrappers around cargo test and cargo clippy --tests that work from any directory (they pass --manifest-path for the repo root).
ctest -p quiltlang node
lint
build-py
Build the quilt_python PyO3 extension module. Required before running .py.quilt files. Rebuild after editing the Python bindings.
build-py
Builds with maturin and installs the abi3 module as quilt-python/quilt/_quilt.abi3.so, which Python can import as import quilt.
build-ts
The TypeScript counterpart: build the quilt-wasm runtime for Node. Required before running .ts.quilt files. Rebuild after editing the wasm bindings.
build-ts
Runs wasm-pack build quilt-wasm --target nodejs --out-dir pkg — CommonJS with the wasm instantiated on require, so a CLI run needs no fetch/init dance. (The npm package and the browser demos use --target web, which examples/web/build.mjs puts in pkg-web/; the API surface is the same.) quilt run layers quilt-wasm/node on top, which is what adds ↓.
test-py / test-ts
Exercise each runtime end to end: test-py runs the pytest binding tests and the self-contained .py.quilt examples; test-ts runs the Node ↓ tests (quilt-wasm/test/reduce.mjs) and the self-contained .ts.quilt examples. Each needs its runtime built first.
Both also re-run cargo test -p quiltlang --test cli, because the quilt run tests in quilt/tests/cli.rs skip themselves when their runtime isn't built — a plain cargo test stays green without maturin or wasm-pack, so these scripts are the only place the Python and Node runner paths execute for real.
build-py && test-py
build-ts && test-ts
install_tools
Build and install the editor tooling: cargo install --path quilt-lsp, npm install for the VS Code extension, and symlink tools/quilt into ~/.vscode/extensions/quiltlang. Idempotent; warns if rust-analyzer or rust-script is missing. See Editor Setup.
install_tools
Cargo workspace commands
Run from the repo root (the Cargo workspace root):
cargo build # build all workspace members
cargo test # run all tests
cargo test -p quiltlang node # run tests matching "node" in the quilt crate
cargo clippy # lint
cargo fmt # format
cargo build -p quilt-lsp # build the LSP server
cargo test -p quilt-lsp # run LSP tests
Testing the CLI
Two suites, splitting the work between the real binary and the rendering:
| Suite | Covers |
|---|---|
quilt/tests/cli.rs | The real binary, through CARGO_BIN_EXE_quilt: the happy paths and the exit codes — check, expand (sibling file, header comment, chain derivation), run for each of the three runners, -m omni vs -m bootstrap, --help. |
quilt/tests/ui.rs + quilt/tests/ui/ | A corpus of deliberately invalid inputs whose full rendered miette diagnostic is snapshotted — error text, help, and above all where the caret lands. |
The ui/ corpus is what regression-tests spans and error wording. Every case must fail with a diagnostic: an input that expands cleanly is reported as an error, so a case that starts passing (because the capability it probes got implemented) fails loudly and asks to be reclassified.
It renders in-process with an explicitly configured GraphicalReportHandler rather than by capturing the binary's stderr — the binary's rendering is miette's global hook, whose width, colour and box-drawing charset are all sniffed from the environment, so a committed snapshot would differ between a laptop and a CI container. cli.rs separately asserts that the real binary renders a snippet with a line:col, which covers the other half.
Adding a case is a fixture in tests/ui/ plus a line in the corpus_is_complete roster saying which error kind it pins — the two are checked against each other, so neither can exist without the other.
cargo test -p quiltlang --test ui # run the corpus
cargo insta review # accept a changed diagnostic
Environment variables
| Variable | Used by | Description |
|---|---|---|
RUST_LOG | quilt, quilt-lsp | tracing log filter (e.g. debug, info) |
QUILT_LSP_RUST_ANALYZER | quilt-lsp | Override rust-analyzer command (whitespace-separated) |
PYTHONPATH | quilt (Python) | Extended to include the quilt-python/ directory |