Skip to main content

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 which Multi to use. Default omni. Use bootstrap only when expanding mk_meta.rs.quilt to regenerate meta.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 as expand.

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:

ExtensionRunner
.rsrust-script
.pypython3 (with PYTHONPATH set to include quilt-python)
.tsnode --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 ignores NODE_PATH, so a bare import … from "quilt" resolves only against a node_modules directory above the importing file — and a temp file has none. quilt run therefore gives the script a private directory holding a node_modules with two entries, mirroring the browser demos' import map: quilt → quilt-wasm/node (the runtime plus ↓) and quilt-wasm → quilt-wasm/pkg (the raw wasm-pack package). Build them first with bin/build-ts; until then quilt run on a .ts.quilt file 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:

SuiteCovers
quilt/tests/cli.rsThe 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​

VariableUsed byDescription
RUST_LOGquilt, quilt-lsptracing log filter (e.g. debug, info)
QUILT_LSP_RUST_ANALYZERquilt-lspOverride rust-analyzer command (whitespace-separated)
PYTHONPATHquilt (Python)Extended to include the quilt-python/ directory