|
| 1 | +//! Renders the example snippets from the official mermaid flowchart |
| 2 | +//! docs (https://mermaid.ai/open-source/syntax/flowchart.html) through |
| 3 | +//! our renderer, writing one .svg (success) or .err.txt (error) per |
| 4 | +//! example into target/doc-gallery/ for side-by-side visual comparison |
| 5 | +//! with the doc's reference images. |
| 6 | +//! |
| 7 | +//! Run: cargo run -p ogrenotes-mermaid --example doc_gallery |
| 8 | +//! |
| 9 | +//! Expectation notes reflect the post-polish-slice behavior (issue #32; |
| 10 | +//! docs/superpowers/specs/2026-07-11-mermaid-polish-design.md). |
| 11 | +
|
| 12 | +use std::fs; |
| 13 | +use std::path::Path; |
| 14 | + |
| 15 | +fn main() { |
| 16 | + // (name, source, expectation-note) |
| 17 | + let cases: &[(&str, &str, &str)] = &[ |
| 18 | + // ── Basic ──────────────────────────────────────────────── |
| 19 | + ("basic_bare_node", "flowchart TD\n A", "match"), |
| 20 | + ("basic_text_node", "flowchart TD\n A[This is the text in the box]", "match"), |
| 21 | + ("basic_unicode_quoted", "flowchart TD\n A[\"This is the text in the box\"]", "match"), |
| 22 | + ("basic_markdown_backticks", "flowchart TD\n A[\"`This is the text in the box`\"]", "diverge: backticks render literally (no markdown)"), |
| 23 | + ("front_matter", "---\ntitle: Node\n---\nflowchart LR\n id", "match: front matter skipped (title not rendered in v1)"), |
| 24 | + ("direction_td", "flowchart TD\n A --> B", "match"), |
| 25 | + ("direction_lr", "flowchart LR\n A --> B", "match"), |
| 26 | + // ── Legacy shapes ──────────────────────────────────────── |
| 27 | + ("shape_round", "flowchart TD\n A(This is the text in the box)", "match"), |
| 28 | + ("shape_stadium", "flowchart TD\n A([This is the text in the box])", "match"), |
| 29 | + ("shape_subroutine", "flowchart TD\n A[[This is the text in the box]]", "match (added in polish slice)"), |
| 30 | + ("shape_cylinder", "flowchart TD\n A[(Database)]", "match"), |
| 31 | + ("shape_circle", "flowchart TD\n A((This is the text in the box))", "match"), |
| 32 | + ("shape_asymmetric", "flowchart TD\n A>This is the text in the box]", "match"), |
| 33 | + ("shape_rhombus", "flowchart TD\n A{This is the text in the box}", "match"), |
| 34 | + ("shape_hexagon", "flowchart TD\n A{{This is the text in the box}}", "match"), |
| 35 | + ("shape_parallelogram", "flowchart TD\n A[/This is the text in the box/]", "match"), |
| 36 | + ("shape_parallelogram_alt", "flowchart TD\n A[\\This is the text in the box\\]", "match"), |
| 37 | + ("shape_trapezoid", "flowchart TD\n A[/This is the text in the box\\]", "match"), |
| 38 | + ("shape_trapezoid_alt", "flowchart TD\n A[\\This is the text in the box/]", "match"), |
| 39 | + ("shape_double_circle", "flowchart TD\n A(((This is the text in the box)))", "match"), |
| 40 | + // ── v11.3+ @-syntax (expected to error loudly) ─────────── |
| 41 | + ("at_syntax_multi", "flowchart TD\n A@{ shape: rect, label: \"Rectangle\" }\n B@{ shape: circle, label: \"Circle\" }\n A --> B", "error (out of scope: @-syntax)"), |
| 42 | + ("at_syntax_icon", "flowchart TD\n A@{ shape: icon, icon: \"fa:fa-heart\", form: \"circle\", label: \"Heart\" }", "error (out of scope)"), |
| 43 | + // ── Links ──────────────────────────────────────────────── |
| 44 | + ("link_arrow", "flowchart TD\n A-->B", "match"), |
| 45 | + ("link_open", "flowchart TD\n A---B", "match"), |
| 46 | + ("link_pipe_text", "flowchart TD\n A-->|text|B", "match"), |
| 47 | + ("link_chain_text_node", "flowchart TD\n A-->text-->B", "match (creates a node named 'text' — same as mermaid)"), |
| 48 | + ("link_inline_text_spaced", "flowchart TD\n A-- text -->B", "match"), |
| 49 | + ("link_dotted", "flowchart TD\n A-.->B", "match"), |
| 50 | + ("link_dotted_open", "flowchart TD\n A-.-B", "match: dotted open, no arrowhead (fixed in polish slice)"), |
| 51 | + ("link_dotted_text_nospace", "flowchart TD\n A-.text.-B", "match (no-space spelling added in polish slice)"), |
| 52 | + ("link_dotted_text_spaced", "flowchart TD\n A-. text .-> B", "match"), |
| 53 | + ("link_thick", "flowchart TD\n A==>B", "match"), |
| 54 | + ("link_thick_text_nospace", "flowchart TD\n A==text==>B", "match (no-space spelling added in polish slice)"), |
| 55 | + ("link_thick_text_spaced", "flowchart TD\n A== text ==>B", "match"), |
| 56 | + ("link_invisible", "flowchart TD\n A ~~~ B", "match: renders both nodes, no visible edge (layout-only)"), |
| 57 | + // ── Chaining ───────────────────────────────────────────── |
| 58 | + ("chain_simple", "flowchart TD\n A-->B-->C", "match"), |
| 59 | + ("chain_fanout_mid", "flowchart TD\n A-->B & C-->D", "match"), |
| 60 | + ("chain_fanout_both", "flowchart TD\n A & B-->C & D", "match"), |
| 61 | + ("chain_multiline", "flowchart TD\n A-->B-->C\n A-->D-->C\n B-->E", "match"), |
| 62 | + // ── Edge ids / animation (v11.10+) ─────────────────────── |
| 63 | + ("edge_id", "flowchart TD\n e1@-->A & B", "error (out of scope: edge ids)"), |
| 64 | + // ── New arrow types (fixed silent divergences) ─────────── |
| 65 | + ("circle_edge", "flowchart LR\n A --o B", "match: circle-ended edge (was a loud error pre-polish)"), |
| 66 | + ("cross_edge", "flowchart LR\n A --x B", "match: cross-ended edge"), |
| 67 | + ("circle_edge_nospace", "flowchart TD\n A---oB", "match: circle edge to B — mermaid's documented terminator binding (was a phantom node 'oB')"), |
| 68 | + ("cross_edge_nospace", "flowchart TD\n A---xB", "match: cross edge to B (was a phantom node 'xB')"), |
| 69 | + ("arrow_then_o_node", "flowchart TD\n A-->oB", "match: arrow to a node named 'oB' — the '>' already terminated the link, in mermaid too"), |
| 70 | + // ── Multi-directional / lengths ────────────────────────── |
| 71 | + ("multidir", "flowchart LR\n A o--o B\n B <--> C\n C x--x D", "match: per-end heads via marker-start/marker-end"), |
| 72 | + ("min_length", "flowchart TD\n A[Start] --> B{Rhombus}\n B --> C[rect_1]\n B --> D[rect_2]\n B --> E[rect_3]\n C --> F[A]\n D --> E\n E --> F[B]\n F --> G[End]\n A -----> E", "match: parses; extra length's rank-span hint not honored (quiet cosmetic divergence)"), |
| 73 | + ("length_with_text", "flowchart TD\n A --text---- E", "match: long closer run with inline label"), |
| 74 | + // ── Special characters ─────────────────────────────────── |
| 75 | + ("special_html", "flowchart TD\n A[\"This is a <strong>test</strong>\"]", "diverge-by-design: we escape (literal text); mermaid renders the HTML"), |
| 76 | + ("entity_codes", "flowchart TD\n A[\"This is a #35; test\"]", "diverge: entity code renders literally, not decoded to #"), |
| 77 | + // ── Subgraphs ──────────────────────────────────────────── |
| 78 | + ("subgraph_titled", "flowchart TD\n subgraph sgID[A Subgraph]\n A-->B\n end", "match"), |
| 79 | + ("subgraph_edge_to_id", "flowchart TD\n subgraph sgID[A Subgraph]\n A-->B\n end\n sgID-->C\n C-->D", "LOUD ERROR: edge-to-subgraph routing deferred (was a silent phantom node 'sgID')"), |
| 80 | + ("subgraph_direction", "flowchart TD\n subgraph sgID[A Subgraph]\n direction LR\n A-->B\n end", "error (direction-in-subgraph out of scope)"), |
| 81 | + // ── Interaction / styling ──────────────────────────────── |
| 82 | + ("click_callback", "flowchart TD\n A-->B\n click A callback \"Tooltip text\"", "error (out of scope: click)"), |
| 83 | + ("comments", "flowchart TD\n A[Auslan]\n %%this is a comment\n A-->B[\"Christmas\"]", "match"), |
| 84 | + ("link_style", "flowchart LR\n A-->B-->C-->D\n linkStyle 3 stroke:#ff3,stroke-width:4px,color:red;", "error (out of scope: linkStyle)"), |
| 85 | + ("node_style", "flowchart TD\n A-->B\n style A fill:#f9f,stroke:#333,stroke-width:4px;", "error (out of scope: style)"), |
| 86 | + ("classes_inline", "flowchart TD\n A:::someclass --> B\n classDef someclass fill:#f9f,stroke:#333,stroke-width:4px;", "match (allowlisted props apply)"), |
| 87 | + ("classes_two", "flowchart TD\n A:::first --> B:::second\n classDef first fill:#f9f,stroke:#333,stroke-width:4px;\n classDef second fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff;", "match"), |
| 88 | + ("class_default", "flowchart TD\n A --> B\n classDef default fill:#f9f,stroke:#333,stroke-width:4px;", "match: default auto-applied to unclassed nodes (added in polish slice)"), |
| 89 | + ("fontawesome", "flowchart TD\n B[fa:fa-twitter]", "diverge-by-design: renders literal text, no icon"), |
| 90 | + ("spaced_no_semicolons", "flowchart LR\n A --> B --> C\n A --> D --> C\n B --> E", "match"), |
| 91 | + ]; |
| 92 | + |
| 93 | + let out_dir = Path::new("target/doc-gallery"); |
| 94 | + fs::create_dir_all(out_dir).expect("create out dir"); |
| 95 | + |
| 96 | + let (mut ok, mut err) = (0usize, 0usize); |
| 97 | + for (name, source, note) in cases { |
| 98 | + let result = ogrenotes_mermaid::render(source); |
| 99 | + match (result.svg, result.error) { |
| 100 | + (Some(svg), None) => { |
| 101 | + fs::write(out_dir.join(format!("{name}.svg")), svg).expect("write svg"); |
| 102 | + ok += 1; |
| 103 | + println!("RENDERED {name:<28} — {note}"); |
| 104 | + } |
| 105 | + (None, Some(e)) => { |
| 106 | + let msg = format!( |
| 107 | + "line {:?}: {}\n\nsource:\n{}\n\nexpectation: {}\n", |
| 108 | + e.line, e.message, source, note |
| 109 | + ); |
| 110 | + fs::write(out_dir.join(format!("{name}.err.txt")), msg).expect("write err"); |
| 111 | + err += 1; |
| 112 | + println!("ERRORED {name:<28} — {note}"); |
| 113 | + } |
| 114 | + other => { |
| 115 | + // XOR invariant means this is unreachable; keep loud anyway. |
| 116 | + println!("INVARIANT VIOLATION {name}: {other:?}"); |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + println!("\n{ok} rendered, {err} errored → target/doc-gallery/"); |
| 121 | + println!("Compare SVGs against the doc images; .err.txt files list the loud-error cases."); |
| 122 | +} |
0 commit comments