Skip to content

jordangarrison/drawl

Repository files navigation

drawl

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 via bbin),
  • a JVM library under drawl.compiler.

Status

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.

Quick start

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)

CLI

# 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.dot

Install via Nix

The 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#cli

NixOS, 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.

Example

(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"))

images/internet-banking.png

Compile to dot:

(require '[drawl.compiler :as drawl])
(drawl/compile (slurp "examples/03-bank-containers.drawl") :dot)

More examples in =examples/=.

Architecture

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.

Highlights

User macros — abstract over your own conventions

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.

Built-in shorthand for the boring 80%

(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"

Bidirectional edges, named or unnamed

(-> editor   nrepl     "evaluates")          ; editor → nrepl
(-> nrepl    runtime   "bencode")            ; nrepl → runtime
(-> runtime  shadow    "hot-reload" :tech "websocket")
(<-> cider   nrepl     "middleware ops")     ; bidirectional

Nested systems for system-of-systems landscapes

(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"))

One source, multiple zoom levels

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 view

Live editor with paredit + cheatsheet

The 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.

Deploy

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.defaultservices.drawl with 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.

Roadmap

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-mode derived from lisp-mode, optional drawl lsp subcommand)

License

TBD.

About

Diagrams Rendered As Walked Lists. A Lisp for diagrams — C4-aligned, browser-native, CLI-friendly.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors