beautiful_mermaid

MoonBit port of beautiful-mermaid: render Mermaid diagrams to SVG and ASCII/Unicode text.

mermaid
diagram
svg
ascii
renderer
moonbit
moon add bobzhang/beautiful_mermaid@0.1.5
Download zip
Author
Version
0.1.5
License
MIT
Last updated
6 months ago
Downloads
55
README

#bobzhang/beautiful_mermaid

Render Mermaid diagrams as SVG or ASCII/Unicode text in MoonBit.

#Features

  • Flowchart, state, sequence, class, and ER parsing/rendering.
  • SVG output with CSS-variable theming.
  • ASCII/Unicode terminal rendering.
  • Configurable layout and rendering options.
  • Smoke coverage against the upstream beautiful-mermaid/samples-data.ts corpus.

#Architecture

See ARCHITECTURE.md for the parser/layout/renderer pipeline, module boundaries, and test architecture.

#Credits

This project is a MoonBit port of beautiful-mermaid by Luki Labs, with additional inspiration from mermaid-ascii by Alexander Grooff.

  • Original project: beautiful-mermaid (TypeScript)
  • Original terminal renderer inspiration: mermaid-ascii (Go)
  • Port in this repository: bobzhang/beautiful_mermaid (MoonBit)
  • License: MIT (aligned with the original project)

#Maintainer Workflow

Regenerate the upstream sample smoke test after upstream samples-data.ts changes:

  • bun run scripts/generate_upstream_samples_smoke.ts
  • moon test upstream_samples_smoke_test.mbt --target native
  • moon test ascii_flowchart_corpus_test.mbt --target native
  • moon test ascii_state_corpus_test.mbt --target native
  • CATEGORY=State bun run scripts/check_sample_text_parity.ts (optional parity audit against upstream text output)
  • bun run scripts/check_upstream_parity_title_coverage.ts (ensures parity titles match upstream sample titles exactly)

#Quick Start

#SVG rendering

///|
test "simple_td" (it : @test.Test) {
let svg = try! render_mermaid("graph TD\nA --> B")
assert_true(svg.has_prefix("<svg "))
assert_true(svg.has_suffix("</svg>"))
assert_true(svg.contains(">A</text>"))
assert_true(svg.contains(">B</text>"))
it.write(svg)
it.snapshot(filename="simple_td.svg")
}

#ASCII rendering

///|
test {
let ascii = try! render_mermaid_ascii("graph LR\nA --> B")
assert_true(ascii.length() > 0)
assert_true(ascii.contains("A"))
assert_true(ascii.contains("B"))
}

#Public API

  • parse_mermaid(text) -> MermaidGraph raise MermaidError
  • render_mermaid(text, options?) -> String raise MermaidError
  • render_mermaid_ascii(text, options?) -> String raise MermaidError
  • render_mermaid_with_colors(text, colors, options?) -> String raise MermaidError
  • render_mermaid_with_theme(text, theme, options?) -> String raise MermaidError
  • render_mermaid_with_theme_name(text, theme_name, options?) -> String raise MermaidError
  • build_colors(options) -> DiagramColors
  • default_colors() -> DiagramColors
  • merge_options_with_colors(options, colors) -> RenderOptions
  • Theme helpers: theme_by_name, parse_theme_name, theme_exists, built_in_theme_slugs, built_in_theme_colors
  • Shiki mapping helpers: shiki_theme, shiki_dark_theme, shiki_light_theme, from_shiki_theme

#Styling Options

render_mermaid accepts RenderOptions for colors, font, spacing, and transparency. Use default_colors() if you want to start from package defaults and override selectively.

///|
test {
let options = RenderOptions::{
bg: Some("#18181B"),
fg: Some("#FAFAFA"),
line: Some("#7aa2f7"),
accent: None,
muted: None,
surface: None,
border: None,
font: Some("Inter"),
padding: None,
node_spacing: None,
layer_spacing: None,
transparent: Some(true),
}
let svg = try! render_mermaid("graph TD\nA --> B", options~)
assert_true(svg.contains("--bg:#18181B"))
assert_true(svg.contains("--line:#7aa2f7"))
}

#Built-in Themes

Use built-in theme presets by name and pass the resulting colors through RenderOptions. Use built_in_theme_colors() if you want the full slug-to-colors map. Use theme_exists(name) to validate a user-provided theme name before rendering. Use canonical_theme_slug(name) to resolve an arbitrary user input to a canonical slug. default is accepted as an alias for zinc-light. Use built_in_theme_slugs_csv() when you need a CLI-friendly list string. Available slugs: zinc-light, zinc-dark, tokyo-night, tokyo-night-storm, tokyo-night-light, catppuccin-mocha, catppuccin-latte, nord, nord-light, dracula, github-light, github-dark, solarized-light, solarized-dark, one-dark. Theme names are case-insensitive and accept whitespace/underscores/hyphens. Leading/trailing separators are ignored, so forms like __github_dark__ also normalize.

///|
test {
let colors = match theme_by_name("tokyo-night") {
Some(found) => found
None => fail("missing theme")
}
let options = RenderOptions::{
bg: Some(colors.bg),
fg: Some(colors.fg),
line: colors.line,
accent: colors.accent,
muted: colors.muted,
surface: colors.surface,
border: colors.border,
font: None,
padding: None,
node_spacing: None,
layer_spacing: None,
transparent: None,
}
let svg = try! render_mermaid("graph TD\nA --> B", options~)
assert_true(svg.contains("--bg:#1a1b26"))
assert_true(svg.contains("--accent:#7aa2f7"))
}

Or render in one call:

///|
test {
let svg = try! render_mermaid_with_theme_name(
"graph TD\nA --> B", "github-dark",
)
assert_true(svg.contains("--bg:#0d1117"))
assert_true(svg.contains("--accent:#4493f8"))
}

Typed variant via parsed enum:

///|
test {
let theme = match parse_theme_name("tokyo-night") {
Some(found) => found
None => fail("missing theme")
}
let svg = try! render_mermaid_with_theme("graph TD\nA --> B", theme)
assert_true(svg.contains("--bg:#1a1b26"))
}

#Theme Extraction from Editor-Like Theme Data

Use from_shiki_theme to map editor/theme token data into DiagramColors. Use shiki_token_color / shiki_token_color_many to build token entries. Use shiki_dark_theme / shiki_light_theme when constructing ShikiTheme. When theme_type is omitted, fallback colors use light defaults.

///|
test {
let shiki_theme = shiki_theme(
Some("dark"),
Some({
"editor.background": "#1a1b26",
"editor.foreground": "#a9b1d6",
"editorLineNumber.foreground": "#565f89",
"focusBorder": "#7aa2f7",
}),
Some([shiki_token_color("comment", Some("#565f89"))]),
)
let colors = from_shiki_theme(shiki_theme)
let svg = try! render_mermaid_with_colors("graph TD\nA --> B", colors)
assert_true(svg.contains("--bg:#1a1b26"))
assert_true(svg.contains("--accent:#7aa2f7"))
}

#CLI

Run the local CLI entrypoint from the module root:

  • moon run cmd/main -- "graph TD\nA --> B"
  • moon run cmd/main -- --ascii "graph LR\nA --> B"
  • moon run cmd/main -- --ascii --ascii-padding-x 8 "graph LR\nA --> B"
  • moon run cmd/main -- --ascii --ascii-padding-y 2 "graph LR\nA --> B"
  • moon run cmd/main -- --ascii --ascii-box-border-padding 2 "graph LR\nA --> B"
  • moon run cmd/main -- --unicode "graph LR\nA --> B"
  • --ascii-padding-x, --ascii-padding-y, and --ascii-box-border-padding are valid only with --ascii or --unicode
  • SVG flags (--theme, --font, --bg, etc.) are valid only in SVG output mode (without --ascii/--unicode)
  • moon run cmd/main -- --theme=tokyo-night "graph TD\nA --> B"
  • moon run cmd/main -- --theme=default "graph TD\nA --> B"
  • moon run cmd/main -- --theme tokyo-night "graph TD\nA --> B"
  • moon run cmd/main -- --theme "TOKYO NIGHT" "graph TD\nA --> B" (normalized automatically)
  • moon run cmd/main -- --font "Roboto Mono" "graph TD\nA --> B"
  • moon run cmd/main -- --bg "#0f172a" "graph TD\nA --> B"
  • moon run cmd/main -- --fg "#e2e8f0" "graph TD\nA --> B"
  • moon run cmd/main -- --line "#64748b" "graph TD\nA --> B"
  • moon run cmd/main -- --accent "#38bdf8" "graph TD\nA --> B"
  • moon run cmd/main -- --muted "#94a3b8" "graph TD\nA --> B"
  • moon run cmd/main -- --surface "#0b1220" "graph TD\nA --> B"
  • moon run cmd/main -- --border "#334155" "graph TD\nA --> B"
  • moon run cmd/main -- --padding 20 "graph TD\nA --> B"
  • moon run cmd/main -- --node-spacing 80 "graph TD\nA --> B --> C"
  • moon run cmd/main -- --layer-spacing 90 "graph TD\nA --> B --> C"
  • moon run cmd/main -- --transparent "graph TD\nA --> B"
  • moon run cmd/main -- --list-themes
  • --list-themes includes built-in slugs plus default (alias of zinc-light)

#
AsciiRenderOptions

ASCII/Unicode render configuration for terminal output.

#
DiagramColors

SVG theme colors. bg and fg are required; the rest are optional enrichments.

#
Direction

Flow/state layout direction.

#
MermaidEdge

Parsed logical edge before layout.

#
MermaidError

Error type returned by parsing and themed render helpers.

#
MermaidGraph

Normalized parsed Mermaid model consumed by layout/render stages.

#
MermaidNode

Parsed logical node before layout.

#
MermaidSubgraph

Parsed subgraph/composite block with optional direction override.

#
NodeShape

Normalized node shapes used by parser and renderers.

#
Point

2D point used by positioned edge routes.

#
PositionedEdge

Edge after layout with routed polyline points and optional label anchor.

#
PositionedGraph

Full positioned scene graph consumed by SVG and ASCII renderers.

#
PositionedGroup

Positioned subgraph/composite group bounds and nested children.

#
PositionedNode

Node after layout with absolute coordinates and computed size.

#
PositionedSequenceActivation

Sequence activation bar geometry.

#
PositionedSequenceBlock

Positioned sequence block container and divider rows.

#
PositionedSequenceBlockDivider

Positioned divider label row inside a sequence block.

#
PositionedSequenceLifeline

Sequence lifeline geometry for SVG/ASCII sequence renderers.

#
PositionedSequenceNote

Positioned sequence note box.

#
RenderOptions

SVG render configuration (theme colors, font, spacing, transparency).

#
SequenceActivationCommand

Sequence activation/deactivation command positioned relative to messages.

#
SequenceBlock

Parsed sequence control block (alt, opt, loop, ...).

#
SequenceBlockDivider

Divider row metadata (else / and) inside a sequence block.

#
SequenceBlockType

Sequence block keyword kind (alt, loop, par, etc.).

#
SequenceNote

Sequence note attached after a message index.

#
SequenceNotePosition

Placement mode for sequence notes.

#
SequenceParticipantKind

Sequence participant visual kind.

#
ShikiTheme

Simplified Shiki-like theme model used to derive DiagramColors.

#
ShikiTokenColor

Minimal Shiki token color model used by from_shiki_theme.

#
ThemeName

Built-in theme identifiers for render_mermaid_with_theme.

#
build_colors

Resolve a full DiagramColors value from RenderOptions. Missing bg/fg values fall back to default_colors().

#
built_in_theme_colors

#
built_in_theme_slugs

fn built_in_theme_slugs() -> Array[String]

#
built_in_theme_slugs_csv

fn built_in_theme_slugs_csv() -> String

#
canonical_theme_slug

fn canonical_theme_slug(name : String) -> String?

#
default_colors

Return the package default colors (#FFFFFF background, #27272A foreground). Use this when building custom theme presets.

Example

test {
let colors = default_colors()
assert_eq(colors.bg, "#FFFFFF")
assert_eq(colors.fg, "#27272A")
}

#
merge_options_with_colors

Merge user options with an explicit color palette. Explicit values in options win; missing color fields are filled from colors.

#
normalize_theme_name

fn normalize_theme_name(name : String) -> String

#
parse_mermaid

Parse Mermaid text into the normalized in-memory graph model used by both SVG and ASCII renderers.

Supported headers:
  • graph / flowchart
  • stateDiagram / stateDiagram-v2
  • sequenceDiagram
  • classDiagram
  • erDiagram

#
parse_theme_name

fn parse_theme_name(name : String) ->
ThemeName
?

#
render_mermaid

Parse Mermaid text and render an SVG string. Supports flowchart, state, sequence, class, and ER headers.

Example

test {
let svg = try! render_mermaid("graph TD\nA --> B")
assert_true(svg.has_prefix("<svg "))
assert_true(svg.contains(">A</text>"))
}

#
render_mermaid_ascii

Parse Mermaid text and render ASCII/Unicode terminal output. Use AsciiRenderOptions.use_ascii = true for pure ASCII glyphs.

Example

test {
let ascii = try! render_mermaid_ascii("graph LR\nA --> B", options={
use_ascii: true,
padding_x: 5,
padding_y: 5,
box_border_padding: 1,
})
assert_true(ascii.contains("A"))
assert_true(ascii.contains("B"))
}

#
render_mermaid_with_colors

Render SVG using an explicit color palette, plus optional non-color options.

#
render_mermaid_with_theme

Render SVG with a built-in ThemeName palette.

#
render_mermaid_with_theme_name

fn render_mermaid_with_theme_name(text : String, theme_name : String, options? :
RenderOptions
) -> String raise
MermaidError

Render SVG with a theme name string. Theme names are normalized (TOKYO NIGHT, tokyo_night, etc. are accepted). Raises UnknownTheme if the name cannot be resolved.

Example

test {
let svg = try! render_mermaid_with_theme_name(
"graph TD\nA --> B", "TOKYO NIGHT",
)
assert_true(svg.contains("--bg:#1a1b26"))
}

#
shiki_theme

#
shiki_token_color

fn shiki_token_color(scope : String, foreground : String?) ->
ShikiTokenColor

#
shiki_token_color_many

fn shiki_token_color_many(scopes : Array[String], foreground : String?) ->
ShikiTokenColor

#
theme_by_name

#
theme_exists

fn theme_exists(name : String) -> Bool

#
theme_slug