Render d2 and mermaid labeled diagram blocks in markup documents into
inline SVG, so diagrams show up rendered in Marked 2
(or any tool that accepts a custom markup preprocessor). Both Markdown and
AsciiDoc are supported.
Each preprocessor scans a markup document for diagram blocks, shells out to the
corresponding renderer (d2 or mmdc), and replaces each block with a centered
<div> containing the generated SVG. The rest of the document is left
untouched. Each script prepends the common Homebrew/system locations to PATH
in its shebang wrapper so it works when launched from markup preview apps such
as Marked 2, not just an interactive shell.
| Format | Flag | D2 block | Mermaid block |
|---|---|---|---|
| Markdown | -f md (default) |
```d2 … ``` |
```mermaid … ``` |
| AsciiDoc | -f adoc |
[d2] or [source,d2] + ---- … ---- |
[mermaid] or [source,mermaid] + ---- … ---- |
For AsciiDoc, both the diagram-style ([d2]) and source-style
([source,d2]) block headers are recognized.
| File | Purpose |
|---|---|
process-markup.sh |
Orchestrator. Pipes a document through the D2 preprocessor, then the Mermaid preprocessor, in sequence. Format-aware via -f. |
d2-preprocessor/process-d2.sc |
Replaces D2 blocks (Markdown or AsciiDoc) with SVG rendered by d2. |
mermaid-preprocessor/process-mermaid.sc |
Replaces Mermaid blocks (Markdown or AsciiDoc) with SVG rendered by mmdc, with <script> tags and XML declarations stripped from the output. |
tests/run.sh |
Fixture-based test suite (see Testing). |
Pre-processors can also run standalone; the orchestrator simply chains them so a single document can contain both diagram types.
Each preprocessor selects which block syntax to scan for from a small
FenceSpec table keyed by --format, so the detection/replacement pipeline is
identical across formats — only the fence patterns differ.
scala-cli— runs the.scscriptsd2— for D2 diagrams (d2onPATH)@mermaid-js/mermaid-cli— for Mermaid diagrams (mmdconPATH)ripgrep—rg, used to locate diagram blocks
brew install scala-cli d2 ripgrep
npm install -g @mermaid-js/mermaid-cliNo build step is required to run either script — scala-cli --power shebang
compiles and runs the .sc source directly on first invocation (cached after
that). See Native binaries below for an optional
one-time build that removes that dispatch overhead.
Run the full pipeline (D2 then Mermaid):
# Markdown (default format), file in / file out
./process-markup.sh -i input.md -o output.md
# AsciiDoc
./process-markup.sh -i input.adoc -o output.adoc -f adoc
# stdin to stdout
cat input.md | ./process-markup.sh
# Verbose (forwarded to both child preprocessors)
./process-markup.sh -i input.adoc -o output.html -f adoc -vRun a single preprocessor on its own:
./d2-preprocessor/process-d2.sc -i input.md -o output.md
./mermaid-preprocessor/process-mermaid.sc -i input.adoc -f adoc[d2]
----
x -> y
----
[source,mermaid]
----
graph TD
A --> B
----Both blocks above are rendered to inline SVG; either AsciiDoc block-header form works for either diagram type.
| Flag | Meaning |
|---|---|
-i <file> |
Input markup file (default: stdin) |
-o <file> |
Output file (default: stdout) |
-f <md|adoc> |
Markup format (default: md) |
-v |
Verbose logging to stderr |
./tests/run.shThe suite is fixture-based (tests/fixtures/) and assumes d2, mmdc, and
rg are installed — it asserts on real <svg> output rather than degrading
gracefully. It covers both formats, both AsciiDoc fence forms, cross-engine
isolation, and the passthrough invariant (a document with no diagram blocks
emerges byte-for-byte unchanged). It exits non-zero if any assertion fails or a
renderer is missing.
Both .sc scripts target platform "scala-native" and can be compiled ahead
of time into standalone binaries. This drops per-invocation latency from
roughly the JVM's ~500ms scala-cli --power shebang dispatch to ~50ms —
worthwhile for interactive use in Marked 2, since it re-invokes the pipeline
on every keystroke-driven preview refresh.
scala-cli --power package d2-preprocessor/process-d2.sc \
--native -f -o d2-preprocessor/process-d2-bin
scala-cli --power package mermaid-preprocessor/process-mermaid.sc \
--native -f -o mermaid-preprocessor/process-mermaid-binEach script's shebang wrapper prefers its sibling *-bin binary if present
next to the script, falling back to scala-cli --power shebang otherwise —
so building is optional and process-markup.sh picks up whichever mode each
script is in transparently. Binaries are machine/architecture-specific and are
.gitignored (*-preprocessor/*-bin); rebuild after pulling changes or
switching machines.
The wrapper does not detect staleness — if you edit a script's Scala
source without rebuilding its binary, the wrapper keeps running the old
binary silently. Rebuild after every source change, or delete the *-bin
file to fall back to scala-cli --power shebang (always current, just
slower).
In Marked 2 → Preferences → Advanced → Custom Processor, point the
preprocessor at process-markup.sh (use the absolute path). Marked 2 feeds the
document on stdin and reads the rendered HTML on stdout. To preview AsciiDoc,
configure the processor command with -f adoc.
If a diagram fails to render, the offending block is replaced with a visible red error box containing the renderer's stderr, so the rest of the document still previews.
EUPL-1.2 © TJ Kolleh