Tooling

One binary, and everything below is a subcommand or a flag of it.

figure.lini lini SVG HTML compile fmt · desugar
{
  gap: 110; clearance: 22;
  |box| { fill: --purple-wash; stroke: --purple-deep; color: --purple-ink; }
  |out::box| { fill: --rose-wash; stroke: --rose-deep; color: --rose-ink; }
  |-| { font-size: 11; stroke: --gray-deep; }
  .round { stroke: --teal-deep; color: --teal-ink; }
}

|box#src| "figure.lini" [
  |icon| "file-text" { fill: none; stroke: --purple-deep; width: 20; height: 20; }
]
|box#cli| "lini" { font-weight: bold; font-size: 16; }
|column#outs| { gap: 30; } [
  |out#svg| "SVG"
  |out#html| "HTML"
]

src -> cli "compile"
cli -> outs.svg & outs.html
cli ~> src "fmt · desugar" .round

Text in, a figure out — and fmt and desugar are the arrow back, rewriting the text you started from.

The loop

lini figure.lini -o figure.svg      # compile
lini serve figure.lini              # live preview at localhost:7700
lini serve samples/                 # a folder: pick, edit and render in the browser
lini --check --strict figure.lini   # the full compile, nothing written; warnings fail
echo "a -> b -> c" | lini -         # stdin to stdout

serve does two different things, depending on what you point it at:

  • A file — live-reloads it on every save.
  • A directory — opens the playground, the same editor as lini.rs/play: source left, figure right.

--check runs everything a compile runs and writes nothing. With --strict, which turns every warning into a failure, that is the gate to put in CI.

Errors that say what to do

figure.lini:12:9: error: link endpoint 'kitchen.bowl' not found at scene root;
  did you mean 'kitchen.counter.bowl'?

Every diagnostic is file:line:col, carries a stable code, and suggests a fix — an unknown property names the nearest one, an unknown endpoint the full path that does exist.

--json emits the same diagnostics as a document with spans and machine-applicable edits, which is what an editor integration reads.

Nothing is dropped silently. A property that cannot apply where it is written is an error, not a no-op — so a figure never quietly ignores you.

Export

lini --static figure.lini -o figure.svg       # self-contained: no var(), text as paths
lini --embed-font figure.lini -o figure.svg   # @font-face inlined, browser-only
lini --theme dark figure.lini -o dark.svg     # one palette pinned
lini --format html figure.lini -o figure.html

By default an SVG carries font names and live CSS variables — the embeddable form, which a host page can re-theme. Two flags trade that away for portability:

  • --static inlines every variable and outlines the text to paths, so the file renders identically in resvg, librsvg, an email client or a PDF pipeline. Every figure in this book is built this way.
  • --embed-font keeps text as text and inlines the used weights. Browsers only — resvg and librsvg ignore @font-face.

Format and desugar

lini fmt figure.lini            # canonical style, in place
lini fmt --check figure.lini    # exit 1 if it would change anything
lini desugar figure.lini        # the file with every template and shorthand expanded

fmt is the one style, and it is not configurable: two-space indent, declarations grouped on a line, children one per line, table cells padded into columns.

desugar prints the engine’s true input — a |table| as its grid of |cell|s, a tree’s generated branch links, a mindmap’s palette rules. When the sugar surprises you, the expansion is where the answer is.

Highlighting and books

lini highlight figure.lini          # the source as <span class="lini-tok-…"> HTML
lini highlight --css                # the palette those spans wear

One scanner colours every listing — this book, the site, the playground, the VS Code and Zed extensions — from the same word lists the compiler reads. mdbook-lini is the preprocessor behind these pages: ```lini fences compile to inline SVG at build time, with the source a click away.

```lini
|chart| "Signups" { categories: "Jan", "Feb", "Mar"; } [
  |line| { data: 14, 19, 26; curve: smooth; marker: dot; }
]
```

The same fence, and the same words after it, work in an Astro site: astro-lini is the integration, and both ride on lini-wasm — the compiler as an npm package, which is what to reach for to build a third.

For agents

Anything that writes text can write a figure. The repository ships SKILL.md, a self-contained guide an agent can be handed, and schema/lini.schema.json — every type and property with its value shape, generated from the ledger the compiler reads.

Go deeper

The CLI lists every flag, SVG output the structure and class hooks a host can style, and errors the diagnostic catalogue.