merman_rustdoc/lib.rs
1#![forbid(unsafe_code)]
2
3//! Render Mermaid diagrams in rustdoc as inline SVG.
4//!
5//! `merman-rustdoc` is a proc-macro integration for crates that want Mermaid diagrams in API docs
6//! without loading Mermaid JavaScript in the browser. The [`macro@merman`] attribute reads Mermaid
7//! code fences and `include_mmd!` lines from item documentation, renders them with Merman during
8//! `cargo doc`, and writes the resulting SVG back into the generated rustdoc page.
9//!
10//! # Install
11//!
12//! Use a normal dependency for the simplest setup:
13//!
14//! ```toml
15//! [dependencies]
16//! merman-rustdoc = "0.7"
17//! ```
18//!
19//! This works for local `cargo doc` and for docs.rs because the examples below use
20//! `cfg_attr(doc, ...)`. The macro only expands during rustdoc builds, but Cargo will still compile
21//! the dependency during ordinary builds.
22//!
23//! If you want ordinary builds to avoid compiling `merman-rustdoc`, make it optional behind a
24//! documentation feature:
25//!
26//! ```toml
27//! [dependencies]
28//! merman-rustdoc = { version = "0.7", optional = true }
29//!
30//! [features]
31//! doc-diagrams = ["dep:merman-rustdoc"]
32//!
33//! [package.metadata.docs.rs]
34//! features = ["doc-diagrams"]
35//! ```
36//!
37//! With this optional setup, build docs locally with:
38//!
39//! ```sh
40//! cargo doc --features doc-diagrams
41//! ```
42//!
43//! # Quickstart
44//!
45//! Put the attribute on any item whose docs contain a Mermaid fence:
46//!
47//! ````rust
48//! #[cfg_attr(doc, merman_rustdoc::merman)]
49//! /// Rendered by rustdoc as inline SVG:
50//! ///
51//! /// ```mermaid
52//! /// flowchart TD
53//! /// A[Start] --> B[Done]
54//! /// ```
55//! pub fn example() {}
56//! ````
57//!
58//! # Include Mermaid files
59//!
60//! Large diagrams can live in separate `.mmd` files. Paths are resolved relative to the consuming
61//! crate's `CARGO_MANIFEST_DIR`.
62//!
63//! ```rust
64//! #[cfg_attr(doc, merman_rustdoc::merman)]
65//! /// Crate architecture.
66//! ///
67//! /// include_mmd!("docs/architecture.mmd")
68//! pub fn architecture() {}
69//! ```
70//!
71//! # Options
72//!
73//! The attribute accepts string options:
74//!
75//! ```rust
76//! #[cfg_attr(
77//! doc,
78//! merman_rustdoc::merman(
79//! scope = "item",
80//! pipeline = "readable",
81//! fail = "error",
82//! source = "hide",
83//! sanitize = "strict",
84//! theme = "rustdoc"
85//! )
86//! )]
87//! /// ```mermaid
88//! /// flowchart TD
89//! /// A --> B
90//! /// ```
91//! pub fn configured() {}
92//! ```
93//!
94//! | Option | Values | Default | Meaning |
95//! | --- | --- | --- | --- |
96//! | `scope` | `item`, `tree` | `item` | Controls whether only the annotated item or the inline item tree is rewritten. |
97//! | `pipeline` | `readable`, `parity`, `resvg-safe` | `readable` | Selects the SVG output pipeline. |
98//! | `fail` | `error`, `keep-source` | `error` | Controls what happens when rendering or file includes fail. |
99//! | `source` | `hide`, `details` | `hide` | Adds a collapsed Mermaid source block under the SVG when set to `details`. |
100//! | `sanitize` | `strict`, `off` | `strict` | Checks rendered SVG for script elements, event attributes, and unsafe resource references. |
101//! | `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. |
102//!
103//! Use `scope = "tree"` to process docs on children inside an inline module, trait, impl block,
104//! struct fields, and enum variants:
105//!
106//! ````rust
107//! #[cfg_attr(
108//! doc,
109//! merman_rustdoc::merman(scope = "tree")
110//! )]
111//! pub mod api {
112//! /// ```mermaid
113//! /// flowchart TD
114//! /// Child --> Docs
115//! /// ```
116//! pub fn child() {}
117//! }
118//! ````
119//!
120//! # Scope
121//!
122//! Supported today:
123//!
124//! - Mermaid fences using backticks or tildes.
125//! - `include_mmd!("path/to/file.mmd")` lines outside other Markdown code fences.
126//! - Item docs on functions, modules, structs, traits, and impl blocks.
127//! - Recursive inline item docs with `scope = "tree"`.
128//! - Multiple diagrams on the same item.
129//! - Footnotes and normal Markdown around diagrams.
130//! - Re-exported item docs when the upstream item was rendered first.
131//!
132//! Not supported today:
133//!
134//! - Crate-level inner docs using `//!`.
135//! - Rewriting Markdown loaded through `#[doc = include_str!("...")]`.
136//! - Rustdoc intra-doc symbol links inside rendered Mermaid SVG text.
137//! - Recursive processing for external `mod name;` files.
138//! - Running Mermaid JavaScript in the browser.
139//! - Fetching Mermaid source or assets from remote URLs.
140//!
141//! # Crate-level docs
142//!
143//! `merman-rustdoc` rewrites item-level outer docs. It does not rewrite crate-level inner docs
144//! written with `//!`.
145//!
146//! Put crate-level diagrams on a public module or item instead:
147//!
148//! ````rust
149//! #[cfg_attr(doc, merman_rustdoc::merman)]
150//! /// Crate architecture.
151//! ///
152//! /// ```mermaid
153//! /// flowchart TD
154//! /// Crate --> Module
155//! /// ```
156//! pub mod architecture {}
157//! ````
158//!
159//! # External docs, links, and themes
160//!
161//! `merman-rustdoc` does not evaluate or rewrite Markdown loaded through
162//! `#[doc = include_str!("...")]`. Use `include_mmd!("path.mmd")` for Mermaid files instead.
163//!
164//! Mermaid source is rendered to SVG before rustdoc resolves intra-doc links. Text inside the SVG
165//! does not participate in rustdoc link resolution, so labels such as `[Type](crate::Type)` are
166//! treated as Mermaid text or Mermaid links, not rustdoc symbol links.
167//!
168//! By default, `merman-rustdoc` follows rustdoc's light/dark theme setting. It renders light and
169//! dark SVG variants during `cargo doc` and uses rustdoc's page theme state to show the matching
170//! variant.
171//! The switch is CSS-only: both variants are embedded in the generated HTML, and the browser does
172//! not load Mermaid JavaScript to render or recolor diagrams.
173//!
174//! Use `theme = "mermaid"` for a single SVG controlled by Mermaid source config. Use
175//! `theme = "dark"` or another supported Mermaid theme to choose one fixed build-time theme.
176//! Source-level Mermaid config, such as an `%%init%%` directive, is still passed to Merman with the
177//! rest of the diagram and overrides the rustdoc-level theme default. Whether a specific theme
178//! directive works depends on Merman's renderer support for that diagram and config.
179
180extern crate proc_macro;
181
182mod doc;
183mod error;
184mod expand;
185mod html;
186mod options;
187mod render;
188mod svg;
189
190use proc_macro::TokenStream;
191use proc_macro2::TokenStream as TokenStream2;
192use quote::quote;
193use syn::LitStr;
194
195/// Render Mermaid code fences in rustdoc comments as inline SVG.
196///
197/// Use this with `cfg_attr` so normal builds do not need to expand diagrams:
198///
199/// ````rust
200/// #[cfg_attr(doc, merman_rustdoc::merman)]
201/// /// ```mermaid
202/// /// flowchart TD
203/// /// A --> B
204/// /// ```
205/// pub fn example() {}
206/// ````
207#[proc_macro_attribute]
208pub fn merman(args: TokenStream, input: TokenStream) -> TokenStream {
209 let input: TokenStream2 = input.into();
210 let args: TokenStream2 = args.into();
211
212 let options = match options::Options::parse(args) {
213 Ok(options) => options,
214 Err(err) => return compile_error_with_input(input, &err.to_string()),
215 };
216
217 match expand::expand(input.clone(), options) {
218 Ok(output) => output.into(),
219 Err(err) => compile_error_with_input(input, &err.to_string()),
220 }
221}
222
223fn compile_error_with_input(input: TokenStream2, message: &str) -> TokenStream {
224 let message = LitStr::new(message, proc_macro2::Span::call_site());
225 quote! {
226 compile_error!(#message);
227 #input
228 }
229 .into()
230}