cifg_diag_attr/
lib.rs

1use std::mem;
2
3use base64::{prelude::BASE64_STANDARD as B64_ENGINE, Engine};
4use macro_railroad::{diagram, lowering, parser};
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use quote::{quote, ToTokens};
8use syn::{parse_macro_input, parse_quote, ItemMacro, LitStr};
9
10/// Internal attribute intented for use by the [cifg] crate only.
11///
12/// Adds a railroad expansion diagram to the cifg macro's doc comments.
13#[proc_macro_attribute]
14pub fn gen_rr_diag(args: TokenStream, macro_item: TokenStream) -> TokenStream {
15    let diag_ref_key = parse_macro_input!(args as LitStr).value();
16    let mut cifg_macro = parse_macro_input!(macro_item as ItemMacro);
17
18    // Strip attributes off of the macro_rules def; the parser doesn't handle them.
19    let mut attrs = mem::take(&mut cifg_macro.attrs);
20
21    // Parse the macro_rules def into an intermediate form.
22    let mut parsed_macro = parser::parse(&cifg_macro.to_token_stream().to_string())
23        .map(lowering::MacroRules::from)
24        .expect("Failed to parse macro_rules.");
25    parsed_macro.foldcommontails();
26    parsed_macro.normalize();
27
28    const WITH_LEGEND: bool = true;
29
30    // // Create a diagram from the intermediate form.
31    let mut macro_diag = diagram::into_diagram(parsed_macro, WITH_LEGEND);
32    // Adds CSS from 'railroad' crate.
33    macro_diag.add_default_css();
34    // Adds CSS from the 'macro_railroad' crate; colors and stuff.
35    diagram::add_default_css(&mut macro_diag);
36
37    // // Base64 encode so the diagram can be embeded into the md docs.
38    let base64_diag = B64_ENGINE.encode(macro_diag.to_string());
39    let base64_md_ref = format!("[{diag_ref_key}]: data:image/svg+xml;base64,{base64_diag}");
40    let doc_str = LitStr::new(&base64_md_ref, Span::mixed_site());
41
42    // // Push a new doc string with the diagram.
43    attrs.push(parse_quote! {
44        #[doc = #doc_str]
45    });
46    cifg_macro.attrs = attrs;
47
48    quote!(#cifg_macro).into()
49}