A Lisp for diagrams. C4-aligned, browser-native, CLI-friendly.
Try it in the browser: drawl.jordangarrison.dev.
drawl is a small Lisp surface for declaring diagrams. Forms nest, edges reference elements by name, attributes are keyword/value pairs. The compiler reads source as Clojure forms, walks them into a single IR, and emits a backend string (graphviz dot today; mermaid C4 + Excalidraw planned).
The same .cljc core ships three targets:
- a browser SPA with live preview (shadow-cljs + viz.js),
- a Babashka CLI (
drawl compile|render|lint|watch, distributed viabbin), - a JVM library under
drawl.compiler.
Pre-v0.1, in flight. Vertical slices delivered: parser, walker, IR, dot + mermaid C4 + excalidraw emitters, level inference (:context / :container / :component), at-level filter, nesting validation, ref/duplicate-id checks, and a live shadow-cljs preview with CodeMirror 6 + clojure-mode rendering through viz.js (dot) and mermaid.js (C4). See GRAMMAR.org for the source language, SPEC.org for the full design, and AGENTS.md for the dev guide.
No install: open drawl.jordangarrison.dev and start typing.
Local dev:
nix develop # JVM, Clojure, Babashka, Node, graphviz, mermaid-cli
npm install # shadow-cljs + @viz-js/viz
npx shadow-cljs watch app # live SPA at http://localhost:8090/Run tests against a JVM REPL:
clojure -M:test:nrepl # nREPL on a free port; eval (clojure.test/run-all-tests)# Install via bbin (planned; until published you can run from a clone with `bb drawl ...`)
bbin install io.github.jordangarrison/drawl
# Compile a .drawl file to dot (default backend) and print to stdout
drawl compile -i examples/03-bank-containers.drawl
# Compile to a file with the mermaid C4 backend
drawl compile -i examples/03-bank-containers.drawl -b mermaid -o bank.mmd
# Validate without emitting
drawl lint -i examples/03-bank-containers.drawl
# Recompile on change (500 ms poll; Ctrl-C to stop)
drawl watch -i examples/03-bank-containers.drawl -o bank.dotThe flake exposes the CLI as packages.cli and a programs.drawl NixOS module.
# One-shot
nix run github:jordangarrison/drawl#cli -- compile -i examples/01-hello.drawl
# Install into user profile
nix profile install github:jordangarrison/drawl#cliNixOS, system-wide:
{
inputs.drawl.url = "github:jordangarrison/drawl";
outputs = { self, nixpkgs, drawl, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
drawl.nixosModules.cli
{ programs.drawl.enable = true; }
];
};
};
}See SPEC.org §5.2 for the full CLI contract.
(diagram "Internet Banking — Containers"
(person customer "Banking Customer")
(system bank "Internet Banking System"
(container webapp "Web App")
(container spa "SPA")
(container api "API")
(container db "Database")
(-> spa api "API calls")
(-> api db "Reads/writes"))
(system mainframe "Mainframe Banking")
(-> customer webapp "Visits bigbank.com")
(-> webapp spa "Delivers SPA")
(-> api mainframe "Calls"))Compile to dot:
(require '[drawl.compiler :as drawl])
(drawl/compile (slurp "examples/03-bank-containers.drawl") :dot)More examples in =examples/=.
source string
→ parser (clojure.edn/read-string on JVM/bb, cljs.reader/read-string in browser)
→ walker (multimethod on form head; produces IR fragments)
→ IR (one nested map: {:title :level :elements :relationships})
→ emitter (drawl.emit.dot / .mermaid / .excalidraw)
Public API in drawl.compiler: parse, emit, compile, validate, plus drawl.ir/at-level for filtering one source to context/container/component views.
Define shorthands at the top of any source. Plain symbol substitution, no eval, no quoting gymnastics:
(defmacro service [id label tech]
(container id label :tech tech))
(diagram "REPL-driven shop"
(service api "Shop API" "Pedestal + Reitit")
(service workers "Job runners" "Component + core.async")
(service ingest "Event ingest" "Kafka + transit-clj"))User macros override built-ins of the same name (with a warning) — last-write-wins, the way Lisp expects.
(diagram "Postgres-backed app"
(webapp ui "Reagent SPA") ; → :tech "Web"
(rest-api api "Pedestal API") ; → :tech "REST API"
(postgres-db store "App data + next.jdbc") ; → :role :database, :tech "PostgreSQL"
(redis-cache cache "ring-session store")) ; → :role :database, :tech "Redis"(-> editor nrepl "evaluates") ; editor → nrepl
(-> nrepl runtime "bencode") ; nrepl → runtime
(-> runtime shadow "hot-reload" :tech "websocket")
(<-> cider nrepl "middleware ops") ; bidirectional(diagram "Clojure dev loop"
(system tooling "Local tooling"
(container repl "JVM REPL")
(container shadow "shadow-cljs"))
(system app "Running app"
(container backend "Pedestal")
(container frontend "Reagent SPA"))
(-> tooling app "hot-reload + eval"))Levels are inferred — deepest :kind wins (component > container > context). Filter the IR to a lower level for a context/container/component view of the same source:
(require '[drawl.compiler :as drawl]
'[drawl.ir :as ir])
(def ir (drawl/parse src))
(drawl/emit ir :dot) ; full detail
(drawl/emit (ir/at-level :context ir) :dot) ; landscape viewThe browser SPA ships a CodeMirror 6 editor wired to nextjournal/clojure-mode (paren matching, slurp/barf, auto-close, syntax highlight). Press Ctrl+/ for a built-in cheatsheet covering both keyboard shortcuts and drawl syntax.
The flake exposes the SPA as a Nix package and a NixOS module so you can host it anywhere you run NixOS:
packages.default— fixed-output build of the optimised SPA ($out/share/drawl/{index.html,styles.css,js/main.js}).nixosModules.default—services.drawlwith optional nginx + ACME wrapper.
Add it to your system flake:
{
inputs.drawl = {
url = "github:jordangarrison/drawl";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, drawl, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
drawl.nixosModules.default
({ ... }: {
services.drawl = {
enable = true;
host = "drawl.example.com";
nginx.enable = true;
nginx.enableACME = true;
};
})
];
};
};
}If you already manage nginx + ACME elsewhere (e.g. Cloudflare DNS-01, a tunnel, or a shared reverse proxy), leave services.drawl.nginx.enable off and point your own vhost at ${config.services.drawl.package}/share/drawl.
Live deployment: drawl.jordangarrison.dev runs from this exact module behind a Cloudflare Tunnel — see nix/module.nix for the option surface.
See SPEC §8. Headlines:
- v0.1 core compiler + browser MVP (in flight)
- v0.2 C4 mapping + mermaid emitter (done)
- v0.3 Babashka CLI
- v0.4 JVM library polish
- v0.5 Excalidraw backend
- v0.6 editor support (
drawl-modederived fromlisp-mode, optionaldrawl lspsubcommand)
TBD.
