merman-rustdoc 0.7.0

Render Mermaid fences in rustdoc with headless Merman SVG output.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
# merman-rustdoc

[![Crates.io](https://img.shields.io/crates/v/merman-rustdoc.svg)](https://crates.io/crates/merman-rustdoc)
[![Documentation](https://docs.rs/merman-rustdoc/badge.svg)](https://docs.rs/merman-rustdoc)

Render Mermaid diagrams in rustdoc as inline SVG.

`merman-rustdoc` is a small proc-macro integration for crates that want diagrams in API docs
without loading Mermaid JavaScript in the browser. It reads Mermaid code fences and `include_mmd!`
lines from doc comments, renders them with Merman during `cargo doc`, and writes the resulting SVG
back into the generated rustdoc page.

Choose `merman-rustdoc` when you want Mermaid diagrams to be part of the generated rustdoc HTML
itself: no browser Mermaid runtime, no CDN dependency, offline-friendly docs, and CI-visible diagram
failures. If you only need the smallest possible macro and are comfortable with browser-side
Mermaid rendering, a JavaScript-based rustdoc integration may be a lighter fit.

## Install

Use a normal dependency for the simplest setup:

```toml
[dependencies]
merman-rustdoc = "=0.7.0"
```

This works for local `cargo doc` and for docs.rs because the examples below use `cfg_attr(doc, ...)`.
The macro only expands during rustdoc builds, but Cargo will still compile the dependency during
ordinary builds.

If you want ordinary builds to avoid compiling `merman-rustdoc`, make it optional behind a
documentation feature:

```toml
[dependencies]
merman-rustdoc = { version = "=0.7.0", optional = true }

[features]
doc-diagrams = ["dep:merman-rustdoc"]

[package.metadata.docs.rs]
features = ["doc-diagrams"]
```

With this optional setup, build docs locally with:

```sh
cargo doc --features doc-diagrams
```

docs.rs will also enable `doc-diagrams` because of the `package.metadata.docs.rs` section.

## Quickstart

Put `#[cfg_attr(doc, merman_rustdoc::merman)]` on any item whose docs contain a Mermaid fence.

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Documentation prose stays before the diagram.
///
/// ```mermaid
/// flowchart TD
///   Source[Mermaid source] --> Macro[merman-rustdoc]
///   Macro --> Svg[Inline SVG]
///   Svg --> Docs[Rustdoc page]
/// ```
///
/// Documentation prose stays after the diagram too.
pub fn example() {}
````

When you run `cargo doc`, the Mermaid fence is replaced with an inline `<svg>` in the generated
HTML, preserving the prose around it. The source view still shows your original Rust source. If you
use the optional dependency setup above, run `cargo doc --features doc-diagrams` instead.

Rendered output:

![Rendered Mermaid diagram in rustdoc light theme](resources/rustdoc-light.png)

A full rustdoc page keeps the item heading, signature, prose, and generated SVG together:

![Rendered Mermaid diagram in a rustdoc function page](resources/rustdoc-realworld.png)

The default `theme = "rustdoc"` mode embeds light and dark SVG variants in the generated rustdoc
page and lets rustdoc's current theme choose which one is visible.

## Common Patterns

### Functions

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Parse, layout, and render a diagram.
///
/// ```mermaid
/// flowchart LR
///   Parse --> Layout --> Svg[SVG]
/// ```
pub fn render_svg(input: &str) -> String {
    todo!()
}
````

### Modules

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Rendering pipeline.
///
/// ```mermaid
/// flowchart TD
///   Core[merman-core] --> Render[merman-render]
///   Render --> Rustdoc[merman-rustdoc]
/// ```
pub mod render {}
````

### Structs

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// A renderer configured for rustdoc output.
///
/// ```mermaid
/// flowchart TD
///   Config --> Renderer
///   Renderer --> InlineSvg[Inline SVG]
/// ```
pub struct RustdocRenderer;
````

### Traits

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Something that can render a diagram.
///
/// ```mermaid
/// sequenceDiagram
///   participant Caller
///   participant Renderer
///   Caller->>Renderer: render(source)
///   Renderer-->>Caller: svg
/// ```
pub trait RenderDiagram {
    fn render(&self, source: &str) -> String;
}
````

### Impl Blocks

````rust
pub struct Client;

#[cfg_attr(doc, merman_rustdoc::merman)]
/// High-level client workflow.
///
/// ```mermaid
/// flowchart TD
///   New[new()] --> Render[render()]
///   Render --> Done[SVG]
/// ```
impl Client {
    pub fn new() -> Self {
        Self
    }

    pub fn render(&self, _source: &str) -> String {
        todo!()
    }
}
````

## Include Mermaid Files

Large diagrams are easier to maintain in separate `.mmd` files.

```text
my-crate/
├── Cargo.toml
├── src/lib.rs
└── docs/architecture.mmd
```

`docs/architecture.mmd`:

```text
flowchart TD
  Api[Public API] --> Core[Core Model]
  Core --> Render[Renderer]
  Render --> Docs[Rustdoc SVG]
```

`src/lib.rs`:

```rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Crate architecture.
///
/// include_mmd!("docs/architecture.mmd")
pub fn architecture() {}
```

Include paths are resolved relative to the consuming crate's `CARGO_MANIFEST_DIR`, not relative to
the source file.

## Multiple Diagrams

You can put more than one diagram on the same item. SVG ids are scoped per diagram so inline SVG
definitions do not collide.

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Input flow:
///
/// ```mermaid
/// flowchart LR
///   Source --> Parse --> Model
/// ```
///
/// Output flow:
///
/// ```mermaid
/// flowchart LR
///   Model --> Layout --> Svg[SVG]
/// ```
pub fn pipeline() {}
````

Backtick and tilde fences are both supported:

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// ~~~ mermaid
/// flowchart TD
///   A --> B
/// ~~~
pub fn tilde_fence() {}
````

## Options

The attribute accepts string options:

```rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(
        scope = "item",
        pipeline = "readable",
        fail = "error",
        source = "hide",
        sanitize = "strict",
        theme = "rustdoc"
    )
)]
/// ```mermaid
/// flowchart TD
///   A --> B
/// ```
pub fn configured() {}
```

| Option | Values | Default | Meaning |
| --- | --- | --- | --- |
| `scope` | `item`, `tree` | `item` | Controls whether only the annotated item or the inline item tree is rewritten. |
| `pipeline` | `readable`, `parity`, `resvg-safe` | `readable` | Selects the SVG output pipeline. |
| `fail` | `error`, `keep-source` | `error` | Controls what happens when rendering or file includes fail. |
| `source` | `hide`, `details` | `hide` | Adds a collapsed Mermaid source block under the SVG when set to `details`. |
| `sanitize` | `strict`, `off` | `strict` | Checks rendered SVG for script elements, event attributes, and unsafe resource references. |
| `theme` | `rustdoc`, `mermaid`, or a supported Mermaid theme name | `rustdoc` | Controls whether diagrams follow rustdoc light/dark themes, use Mermaid source config, or use a fixed Mermaid theme. |

### `scope = "tree"`

Use `scope = "tree"` when one attribute should process docs inside an inline item tree. This is most
useful for modules, but it also handles docs on impl methods, trait methods, fields, and enum
variants that are visible in the annotated item.

````rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(scope = "tree")
)]
pub mod api {
    /// Nested function diagram.
    ///
    /// ```mermaid
    /// flowchart TD
    ///   Request --> Handler --> Response
    /// ```
    pub fn handler() {}

    pub struct Client;

    impl Client {
        /// Nested method diagram.
        ///
        /// ```mermaid
        /// sequenceDiagram
        ///   User->>Client: call()
        ///   Client-->>User: result
        /// ```
        pub fn call(&self) {}
    }
}
````

`scope = "tree"` requires inline Rust syntax. It does not inspect external module files:

```rust
#[cfg_attr(doc, merman_rustdoc::merman(scope = "tree"))]
pub mod external;
```

That form fails with a clear error because a proc macro cannot safely recurse into `external.rs`.

### `source = "details"`

Use this when readers should be able to inspect the Mermaid source from the generated docs.

````rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(source = "details")
)]
/// ```mermaid
/// flowchart TD
///   User --> Api --> Database
/// ```
pub fn visible_source() {}
````

The generated page will show the SVG first, then a collapsed "Mermaid source" block.

### `fail = "keep-source"`

Use this for documentation builds where a broken diagram should not fail the whole crate. The
original Mermaid fence or `include_mmd!` line is left in place when rendering fails.

````rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(fail = "keep-source")
)]
/// ```mermaid
/// flowchart TD
///   A --> B
/// ```
pub fn tolerant_docs() {}
````

The default is `fail = "error"`, which is better for CI and release builds because diagram problems
are caught early.

### `pipeline`

Choose the SVG pipeline that fits the target:

- `readable`: default. Produces stable, readable SVG for rustdoc.
- `parity`: closer to Merman's Mermaid-parity SVG path.
- `resvg-safe`: post-processes SVG for raster-oriented consumers.

```rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(pipeline = "resvg-safe")
)]
/// ```mermaid
/// flowchart TD
///   A --> B
/// ```
pub fn resvg_safe_docs() {}
```

### `sanitize = "strict"`

`sanitize = "strict"` is the default. It validates rendered SVG before inserting it into rustdoc and
fails the documentation build if it finds script elements, event attributes, `javascript:` URLs, or
remote resource references such as `<image href="https://...">`.

```rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(sanitize = "strict")
)]
/// ```mermaid
/// flowchart TD
///   A --> B
/// ```
pub fn checked_svg() {}
```

Use `sanitize = "off"` only when you are deliberately inspecting raw renderer output:

```rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(sanitize = "off")
)]
/// ```mermaid
/// flowchart TD
///   A --> B
/// ```
pub fn raw_svg() {}
```

### `theme`

`theme = "rustdoc"` is the default. It renders light and dark SVG variants during `cargo doc`, then
uses rustdoc's page theme state to show the matching variant. Readers can switch rustdoc light,
dark, or ayu themes without loading Mermaid JavaScript in the browser.

The switch is CSS-only: both SVG variants are embedded in the rustdoc HTML, and a small inline style
uses rustdoc's `:root[data-theme]` attribute to show the light or dark variant. The browser never
loads a Mermaid runtime to render or recolor diagrams.

Use `theme = "mermaid"` when you want a single SVG and want Mermaid source-level config, such as
front matter or an `%%init%%` directive, to decide the theme:

````rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(theme = "mermaid")
)]
/// ```mermaid
/// %%{init: {"theme": "base"}}%%
/// flowchart TD
///   A --> B
/// ```
pub fn mermaid_configured_diagram() {}
````

Use a fixed Mermaid theme when you want one static SVG regardless of the reader's rustdoc theme:

````rust
#[cfg_attr(
    doc,
    merman_rustdoc::merman(theme = "dark")
)]
/// ```mermaid
/// flowchart TD
///   A --> B
/// ```
pub fn dark_diagram() {}
````

Supported fixed theme names follow Merman's Mermaid theme surface: `default`, `base`, `dark`,
`forest`, `neutral`, `neo`, `neo-dark`, `redux`, `redux-dark`, `redux-color`, and
`redux-dark-color`.

Rustdoc theme mode and fixed themes are passed as Mermaid site config. Source-level config still
wins, so a diagram can override them with front matter or an `%%init%%` directive:

```text
%%{init: {"theme": "base"}}%%
flowchart TD
  A --> B
```

When source-level config sets an explicit Mermaid theme, both rustdoc variants may render with that
source-selected theme. Use source-level theme directives only when a diagram should intentionally
opt out of rustdoc theme adaptation.

## Re-exports

Inline SVG is stored in the expanded rustdoc attributes. That makes re-exported pages work when the
upstream item was documented with `merman-rustdoc`.

```rust
// upstream crate
#[cfg_attr(doc, merman_rustdoc::merman)]
/// ```mermaid
/// flowchart TD
///   Upstream --> Reexport
/// ```
pub struct DiagrammedType;
```

```rust
// downstream crate
#[doc(inline)]
pub use upstream::DiagrammedType;
```

If the upstream crate uses the optional documentation feature setup, that feature still has to be
enabled when its docs are built. A downstream re-export cannot render diagrams that were never
expanded upstream.

## What Gets Rendered

Supported today:

- Mermaid fences using backticks or tildes.
- `include_mmd!("path/to/file.mmd")` lines outside other Markdown code fences.
- Item docs on functions, modules, structs, traits, and impl blocks.
- Recursive inline item docs with `scope = "tree"`.
- Multiple diagrams on the same item.
- Footnotes and normal Markdown around diagrams.
- Re-exported item docs when the upstream item was rendered first.

Not supported today:

- Crate-level inner docs using `//!`.
- Recursive processing for external `mod name;` files.
- Running Mermaid JavaScript in the browser.
- Fetching Mermaid source or assets from remote URLs.
- Copying external SVG files into the rustdoc output directory.

## Troubleshooting

### The generated docs still show a Mermaid code block

Make sure the item has the attribute:

```rust
#[cfg_attr(doc, merman_rustdoc::merman)]
```

If you use the optional dependency setup, also make sure the documentation feature is enabled:

```sh
cargo doc --features doc-diagrams
```

Also gate the attribute with the same feature:

```rust
#[cfg_attr(all(doc, feature = "doc-diagrams"), merman_rustdoc::merman)]
```

### `include_mmd!` cannot find a file

Paths are relative to `CARGO_MANIFEST_DIR`.

```rust
/// include_mmd!("docs/architecture.mmd")
```

For a crate at `my-crate/Cargo.toml`, that resolves to:

```text
my-crate/docs/architecture.mmd
```

### docs.rs does not render diagrams

The normal dependency setup does not need docs.rs metadata. If `merman-rustdoc` is optional behind a
documentation feature, add the docs.rs feature configuration:

```toml
[package.metadata.docs.rs]
features = ["doc-diagrams"]
```

### A diagram failure blocks `cargo doc`

That is the default behavior. Use `fail = "keep-source"` if you prefer documentation builds to keep
going while preserving the original Mermaid source.

### `scope = "tree"` fails on `mod name;`

Use an inline module when you want recursive processing:

```rust
#[cfg_attr(doc, merman_rustdoc::merman(scope = "tree"))]
pub mod api {
    // child docs are visible to the proc macro here
}
```

External module files are not traversed by the proc macro.

### Can I use this on crate-level `//!` docs?

No. `merman-rustdoc` rewrites item-level outer docs. It does not rewrite crate-level inner docs
written with `//!`.

Put crate-level diagrams on a public module or item instead:

````rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// Crate architecture.
///
/// ```mermaid
/// flowchart TD
///   Crate --> Module
/// ```
pub mod architecture {}
````

### Does this rewrite `#[doc = include_str!(...)]`?

No. `merman-rustdoc` rewrites literal rustdoc lines that come from item doc comments and
`include_mmd!("path.mmd")` lines. It does not evaluate or rewrite Markdown loaded through
`#[doc = include_str!("...")]`.

Use `include_mmd!` for Mermaid files:

```rust
#[cfg_attr(doc, merman_rustdoc::merman)]
/// include_mmd!("docs/architecture.mmd")
pub fn architecture() {}
```

### Do rustdoc symbol links work inside Mermaid diagrams?

No. Mermaid source is rendered to SVG before rustdoc resolves intra-doc links. Text inside the SVG
does not participate in rustdoc link resolution, so labels such as `[Type](crate::Type)` are treated
as Mermaid text or Mermaid links, not rustdoc symbol links.

Normal Mermaid links follow whatever Merman renders, subject to `sanitize = "strict"`.

### What about themes?

By default, `merman-rustdoc` follows rustdoc's light/dark theme setting. It renders light and dark
SVG variants during `cargo doc` and uses rustdoc's `data-theme` state to show the matching variant.
The switch is CSS-only: both variants are embedded in the generated HTML, and the browser does not
load Mermaid JavaScript to render or recolor diagrams.

Use `theme = "mermaid"` for a single SVG controlled by Mermaid source config. Use `theme = "dark"`
or another supported Mermaid theme to choose one fixed build-time theme. If your Mermaid source uses
Mermaid's own source-level config, such as an `%%init%%` directive, it is passed to Merman with the
rest of the diagram and overrides the rustdoc-level theme default:

```text
%%{init: {"theme": "base"}}%%
flowchart TD
  A --> B
```

Whether a specific Mermaid theme directive works depends on Merman's renderer support for that
diagram and config. `merman-rustdoc` does not add a separate SVG recoloring layer on top.

## Why Build-Time SVG

Many rustdoc Mermaid integrations inject Mermaid JavaScript into the generated page. That works, but
it makes rendering depend on browser execution, script loading, and sometimes remote assets.

`merman-rustdoc` renders before the page is opened:

- no Mermaid JavaScript is injected;
- no CDN is required;
- docs work offline after they are generated;
- SVG is present in the HTML that rustdoc writes;
- broken diagrams can fail CI before release.

## Acknowledgements

Thanks to [`aquamarine`](https://github.com/mersinvald/aquamarine) for proving that Mermaid diagrams
inside rustdoc comments are useful and ergonomic. `merman-rustdoc` follows the same user-facing idea,
but renders SVG with Merman during documentation builds instead of loading Mermaid in the browser.