scopegraphs_render_docs/lib.rs
1//! Render_scopegraph is a procedural macro extension for [rustdoc](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html),
2//! that aims to improve the visual component of Rust documentation through use of the [mermaid.js](https://mermaid-js.github.io/mermaid/#/) diagrams.
3//!
4//! > NOTE: This is a custom, heavily modified version of the [Aquamarine](https://github.com/mersinvald/aquamarine) library, distributed under the MIT license.
5//!
6//! `#[render_scopegraphs]` macro works through embedding the [mermaid.js](https://github.com/mermaid-js/mermaid) into the generated rustdoc HTML page, modifying the doc comment attributes.
7//!
8//! To inline a diagram into the documentation, use the `mermaid` snippet in a doc-string:
9//!
10//! ```rust
11//! # use scopegraphs_render_docs::render_scopegraphs;
12//! #[cfg_attr(doc, render_scopegraph)]
13//! /// ```rust
14//! /// todo!()
15//! ///
16//! /// ```
17//! pub fn example() {}
18//! ```
19//! The diagram will appear in place of the `mermaid` code block, preserving all the comments around it.
20//!
21//! You can even add multiple diagrams!
22//!
23//! To see it in action, go to the [demo crate](https://docs.rs/aquamarine-demo-crate/0.5.0/aquamarine_demo_crate/fn.example.html) docs.rs page.
24//!
25//! ### Dark-mode
26//!
27//! Aquamarine will automatically select the `dark` theme as a default, if the current `rustdoc` theme is either `ayu` or `dark`.
28//!
29//! You might need to reload the page to redraw the diagrams after changing the theme.
30//!
31//! ### Custom themes
32//!
33//! Theming is supported on per-diagram basis, through the mermaid's `%%init%%` attribute.
34//!
35//! *Note*: custom theme will override the default theme
36//!
37//! ```no_run
38//! /// ```mermaid
39//! /// %%{init: {
40//! /// 'theme': 'base',
41//! /// 'themeVariables': {
42//! /// 'primaryColor': '#ffcccc',
43//! /// 'edgeLabelBackground':'#ccccff',
44//! /// 'tertiaryColor': '#fff0f0' }}}%%
45//! /// graph TD
46//! /// A(Diagram needs to be drawn) --> B{Does it have 'init' annotation?}
47//! /// B -->|No| C(Apply default theme)
48//! /// B -->|Yes| D(Apply customized theme)
49//! /// ```
50//! # fn example() {}
51//! ```
52//! [Demo on docs.rs](https://docs.rs/aquamarine-demo-crate/0.5.0/aquamarine_demo_crate/fn.example_with_styling.html)
53//!
54//! To learn more, see the [Theming Section](https://mermaid-js.github.io/mermaid/#/theming) of the mermaid.js book
55//!
56//! ### Loading from a file
57//!
58//! When describing complex logic, a diagram can get quite big.
59//!
60//! To reduce clutter in your doc comments, you can load a diagram from file using the `include_mmd!` macro-like syntax.
61//!
62//! ```no_run
63//! /// Diagram #1
64//! /// include_mmd!("diagram_1.mmd")
65//! ///
66//! /// Diagram #2
67//! /// include_mmd!("diagram_2.mmd")
68//! # fn example() {}
69//! ```
70//! [Demo on docs.rs](https://docs.rs/aquamarine-demo-crate/0.5.0/aquamarine_demo_crate/fn.example_load_from_file.html)
71
72extern crate proc_macro;
73
74use proc_macro::TokenStream;
75use proc_macro_error::{abort, proc_macro_error};
76
77use quote::quote;
78use syn::{parse_macro_input, Attribute};
79
80mod attrs;
81mod parse;
82
83/// Aquamarine is a proc-macro that adds [Mermaid](https://mermaid-js.github.io/mermaid/#/) diagrams to rustdoc
84///
85/// To inline a diagram into the documentation, use the `mermaid` snippet:
86///
87/// ```rust
88/// # use scopegraphs_render_docs::render_scopegraphs;
89/// #[render_scopegraphs]
90/// /// ```rust
91/// /// # use scopegraphs::*;
92/// /// # use completeness::{UncheckedCompleteness};
93/// /// # use resolve::{DataWellformedness, Resolve, ResolvedPath};
94/// /// # use render::{RenderSettings, RenderScopeData, RenderScopeLabel};
95/// /// #
96/// /// # #[derive(Label, Hash, PartialEq, Eq, Debug, Clone, Copy)]
97/// /// # enum Lbl {
98/// /// # Lex,
99/// /// # Imp,
100/// /// # Def,
101/// /// # }
102/// /// # use Lbl::*;
103/// /// #
104/// /// # #[derive(Hash, PartialEq, Eq, Debug, Default, Clone)]
105/// /// # enum TData<'a> {
106/// /// # #[default]
107/// /// # NoData,
108/// /// # Data {
109/// /// # name: &'a str,
110/// /// # data: usize,
111/// /// # },
112/// /// # }
113/// /// #
114/// /// # use TData::*;
115/// /// #
116/// /// # impl RenderScopeData for TData<'_> {
117/// /// # fn render_node(&self) -> Option<String> {
118/// /// # match self {
119/// /// # NoData => None,
120/// /// # Data {name, data} => Some(format!("{name}: {data}")),
121/// /// # }
122/// /// # }
123/// /// # }
124/// /// #
125/// /// # impl RenderScopeLabel for Lbl {
126/// /// # fn render(&self) -> String {
127/// /// # match self {
128/// /// # Lex => "lex",
129/// /// # Imp => "imp",
130/// /// # Def => "def",
131/// /// # }.to_string()
132/// /// # }
133/// /// # }
134/// /// #
135/// /// # impl<'a> TData<'a> {
136/// /// # fn matches(&self, n: &str) -> bool {
137/// /// # match self {
138/// /// # NoData => false,
139/// /// # Data { name, .. } => *name == n,
140/// /// # }
141/// /// # }
142/// /// #
143/// /// # fn matcher(n: &'a str) -> impl DataWellformedness<Self> {
144/// /// # |data: &Self| data.matches(n)
145/// /// # }
146/// /// #
147/// /// # fn from_default(name: &'a str) -> Self {
148/// /// # Data { name, data: 0 }
149/// /// # }
150/// /// # }
151/// /// # let storage = Storage::new();
152/// /// # let sg: ScopeGraph<Lbl, TData, UncheckedCompleteness> =
153/// /// # unsafe { ScopeGraph::raw(&storage) };
154/// ///
155/// /// let s0 = sg.add_scope_default();
156/// /// let s1 = sg.add_scope_default();
157/// /// sg.add_edge(s0, Lex, s1);
158/// /// sg.add_decl(s0, Def, Data { name: "x", data: 0 });
159/// /// sg.add_decl(s1, Def, Data { name: "x", data: 1 });
160/// ///
161/// /// sg.render_to("output.dot", RenderSettings::default()).unwrap();
162/// /// ```
163/// struct Foo;
164/// ```
165#[proc_macro_attribute]
166#[proc_macro_error]
167pub fn render_scopegraphs(_args: TokenStream, input: TokenStream) -> TokenStream {
168 let input = parse_macro_input!(input as parse::Input);
169
170 check_input_attrs(&input.attrs);
171
172 let attrs = attrs::Attrs::from(input.attrs);
173 let forward = input.rest;
174
175 let tokens = quote! {
176 #attrs
177 #forward
178 };
179
180 tokens.into()
181}
182
183fn check_input_attrs(input: &[Attribute]) {
184 for attr in input {
185 if attr.path().is_ident("render_scopegraph") {
186 abort!(
187 attr,
188 "multiple `render_scopegraph` attributes on one entity are illegal"
189 );
190 }
191 }
192}