Wit

A report with live data

Build a release note whose version, contributor list and metrics table come from programs and files run at build time. Write the prose once; the numbers refresh on every build. A developer wires the sources once.

Step 1 — Name each input by alias

The document names only an alias, never a command. That is what keeps it safe to share.

@load meta load@
@load team load@
@load downloads load@
@load env load@

Each line binds the data to a name of the same spelling. Use @load(as: stats) downloads load@ to bind it under another name.

Step 2 — Wire the aliases

A developer maps each alias to a program in wit.sources.json. Every entry has a run (the command as an argument array) and a format — json or csv.

{
  "sources": {
    "meta":      { "run": ["node", "scripts/meta.js"], "format": "json" },
    "team":      { "run": ["node", "scripts/team.js"], "format": "json" },
    "downloads": { "run": ["cat", "data/downloads.csv"], "format": "csv" }
  }
}

A JSON object becomes a record, a JSON array a collection, and a CSV file a collection of records keyed by its header row. The built-in env alias needs no entry: it exposes environment variables, read from the file you pass with --env.

Step 3 — A loaded record in prose

(h1 Release @meta.version h1)

Cut @meta.date on branch @env.WIT_BRANCH.

Step 4 — Iterate a loaded collection

(h2 Contributors h2)

@ul
(each @team as person)
(li @person.name — @person.commits commits li)
(end)
ul@

Step 5 — A CSV straight into a table

source
@table(rows: @downloads)
result

Step 6 — An image beside prose

row is an invisible flex band; a col with a fixed size sits beside one that flows.

@row
@col(size: 260)
@img(src: assets/downloads.svg, alt: Downloads chart)
col@
@col
macOS leads this cycle, with Windows a steady second and Linux climbing.
col@
row@

Step 7 — Build with the sources config

wit build report.wit --sources wit.sources.json --env .env --allow-exec --fragment

Result. With meta.js printing {"version":"0.4.0","date":"2026-07-05"} and WIT_BRANCH=main in .env:

<h1>Release 0.4.0</h1>
<p>Cut 2026-07-05 on branch main.</p>
<h2>Contributors</h2>
<ul><li>TauraJ — 42 commits</li><li>Ada Lovelace — 17 commits</li></ul>
<table><thead><tr><th>platform</th><th>downloads</th><th>change</th></tr></thead>…</table>

Swap --fragment for -o report.pdf to get a styled PDF.

The trust boundary

The document names only an alias. Only the operator's wit.sources.json maps it to a command, and --allow-exec must be passed for any program to run. Without it, only env resolves. A document you receive can never run a program you didn't wire yourself.

Pitfalls

  • Forgetting --allow-exec. The build stops with E_LOAD_FAILED at the first @load of a program alias.
  • Forgetting @load env load@. Every alias, env included, must be loaded before @env.NAME resolves; otherwise it is E_UNRESOLVED_REFERENCE.
  • Noisy sources. A JSON source must print only JSON to standard output — a stray log line breaks the parse.

Variations

Any program that prints JSON or CSV works — a Python script, a SQL client, a compiled binary. To run the same data flow inside your own app, see Embed the Wit parser.

See also

Load a CSV into a table is a focused excerpt; External data documents the loader seam.