From f78120db7780b678b0ce1f29fbf663db277e378e Mon Sep 17 00:00:00 2001 From: Mike Lubinets Date: Wed, 13 Dec 2023 14:08:36 +0400 Subject: [PATCH] feat: multiple fs imports, free placement of imported diagrams --- README.md | 20 ++---- demo/{diagram.mermaid => diagram_0.mmd} | 2 +- demo/diagram_1.mmd | 5 ++ demo/src/lib.rs | 12 +++- src/attrs.rs | 91 ++++++++++++------------- src/lib.rs | 21 +++++- 6 files changed, 84 insertions(+), 67 deletions(-) rename demo/{diagram.mermaid => diagram_0.mmd} (76%) create mode 100644 demo/diagram_1.mmd diff --git a/README.md b/README.md index cf40d3a..aa2f580 100644 --- a/README.md +++ b/README.md @@ -68,26 +68,18 @@ To learn more, see the [Theming Section](https://mermaid-js.github.io/mermaid/#/ ### Separating diagrams from code -A diagram can be loaded from file to reduce clutter in the documentation comments. +A diagram, or multiple, can be loaded from file to reduce clutter in the documentation comments. -Such diagram will always be placed under the rest of the document section. -Reading diagrams from file can be combined with placing them into the doc-comment, to get multiple diagrams describing a single entity, however only one can be placed inside the file. (FIXME). ```rust -#[cfg_attr(doc, aquamarine::aquamarine, path = "./diagram.mermaid")] +#[cfg_attr(doc, aquamarine::aquamarine)] +/// My diagram #1 +/// include_mmd!("diagram1.mmd") +/// My diagram #2 +/// include_mmd!("diagram2.mmd") pub fn example_foad_from_file() {} ``` -```bash -# diagram.mermaid -graph LR - s([Source]) --> a[[aquamarine]] - r[[rustdoc]] --> f([Docs w/ Mermaid!]) - subgraph rustc[Rust Compiler] - a -. load diagram.mermaid .-> r - end -``` - ### In the wild Crates that use `aquamarine` in their documentation diff --git a/demo/diagram.mermaid b/demo/diagram_0.mmd similarity index 76% rename from demo/diagram.mermaid rename to demo/diagram_0.mmd index 4162c17..0d88a18 100644 --- a/demo/diagram.mermaid +++ b/demo/diagram_0.mmd @@ -2,5 +2,5 @@ graph LR s([Source]) --> a[[aquamarine]] r[[rustdoc]] --> f([Docs w/ Mermaid!]) subgraph rustc[Rust Compiler] - a -. "load diagram.mermaid" .-> r + a -. "load diagram_0.mermaid" .-> r end \ No newline at end of file diff --git a/demo/diagram_1.mmd b/demo/diagram_1.mmd new file mode 100644 index 0000000..498bd7d --- /dev/null +++ b/demo/diagram_1.mmd @@ -0,0 +1,5 @@ +graph LR + A[Square Rect] -- Link text --> B((Circle)) + A --> C(Round Rect) + B --> D{Rhombus} + C --> D \ No newline at end of file diff --git a/demo/src/lib.rs b/demo/src/lib.rs index f39afe0..15947e2 100644 --- a/demo/src/lib.rs +++ b/demo/src/lib.rs @@ -40,10 +40,16 @@ pub fn example() {} /// To learn more, see the [Theming Section](https://mermaid-js.github.io/mermaid/#/theming) of the mermaid.js book pub fn example_with_styling() {} -#[cfg_attr(doc, aquamarine::aquamarine, path = "./diagram.mermaid")] +#[cfg_attr(doc, aquamarine::aquamarine)] /// A diagram can be loaded from a file as well! /// -/// Reduce clutter in your doc comments, when a diagram is big enough +/// include_mmd!("diagram_0.mmd") +/// +/// Reduce clutter in your doc comments, when a diagram is big enough. +/// +/// You can include multiple diagrams in a single doc comment, using the macro-like syntax `include_mmd!("/path/to/diagram.mmd")` +/// +/// include_mmd!(diagram_1.mmd) /// -/// **Note:** diagrams loaded form file are always placed in the bottom of the doc section +/// **Note:** `indlude_mmd!` syntax is only supported inside doc comments pub fn example_load_from_file() {} diff --git a/src/attrs.rs b/src/attrs.rs index c768d2e..e803a1c 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -1,12 +1,12 @@ +use include_dir::{include_dir, Dir}; use itertools::Itertools; use proc_macro2::TokenStream; use proc_macro_error::{abort, emit_call_site_warning}; use quote::quote; -use std::iter; -use syn::{Attribute, Ident, MetaNameValue}; use std::fs; use std::path::Path; -use include_dir::{include_dir, Dir}; +use std::{iter, path::PathBuf}; +use syn::{Attribute, Ident, MetaNameValue}; // embedded JS code being inserted as html script elmenets static MERMAID_JS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/doc/js/"); @@ -35,6 +35,8 @@ pub enum Attr { DiagramEntry(Ident, String), /// Diagram end token DiagramEnd(Ident), + /// Include Anchor + DiagramIncludeAnchor(Ident, PathBuf), } impl Attr { @@ -45,20 +47,21 @@ impl Attr { Attr::DiagramStart(ident) => Some(ident), Attr::DiagramEntry(ident, _) => Some(ident), Attr::DiagramEnd(ident) => Some(ident), + Attr::DiagramIncludeAnchor(ident, _) => Some(ident), } } pub fn is_diagram_end(&self) -> bool { match self { Attr::DiagramEnd(_) => true, - _ => false + _ => false, } } pub fn is_diagram_start(&self) -> bool { match self { Attr::DiagramStart(_) => true, - _ => false + _ => false, } } @@ -80,27 +83,9 @@ impl From> for Attrs { impl quote::ToTokens for Attrs { fn to_tokens(&self, tokens: &mut TokenStream) { let mut attrs = self.0.iter(); - let mut loaded_from_file = None; while let Some(attr) = attrs.next() { match attr { - Attr::Forward(attr) => { - // check if filepath is supplied - if attr.path().is_ident("path") { - if let syn::Meta::List(ref ml) = attr.meta { - for token in ml.tokens.to_token_stream().into_iter() { - if let proc_macro2::TokenTree::Literal(value) = token { - let data = - std::fs::read_to_string(value.to_string().replace("\"", "")) - .expect("Unable to read mermaid file"); - loaded_from_file = Some(generate_diagram_rustdoc( - vec![data.as_str()].into_iter(), - )); - } - } - } - } - attr.to_tokens(tokens) - } + Attr::Forward(attr) => attr.to_tokens(tokens), Attr::DocComment(_, comment) => tokens.extend(quote! { #[doc = #comment] }), @@ -118,18 +103,17 @@ impl quote::ToTokens for Attrs { emit_call_site_warning!("encountered an unexpected attribute that's going to be ignored, this is a bug! ({})", body); } Attr::DiagramEnd(_) => (), + Attr::DiagramIncludeAnchor(_, path) => { + let data = std::fs::read_to_string(path).expect("Unable to read mermaid file"); + tokens.extend(generate_diagram_rustdoc(Some(data.as_str()).into_iter())) + } } } - - if let Some(diagram) = loaded_from_file { - tokens.extend(diagram) - } } } fn place_mermaid_js() -> std::io::Result<()> { - let target_dir = std::env::var("CARGO_TARGET_DIR") - .unwrap_or("./target".to_string()); + let target_dir = std::env::var("CARGO_TARGET_DIR").unwrap_or("./target".to_string()); let docs_dir = Path::new(&target_dir).join("doc"); // extract mermaid module iff rustdoc folder exists already if docs_dir.exists() { @@ -210,16 +194,16 @@ const MERMAID_INIT_SCRIPT: &str = r#" } "#; -fn generate_diagram_rustdoc<'a>(parts: impl Iterator) -> TokenStream { +fn generate_diagram_rustdoc<'a>(parts: impl Iterator) -> TokenStream { let preamble = iter::once(r#"
"#); let postamble = iter::once("
"); - - let mermaid_js_init = format!(r#""#, - MERMAID_INIT_SCRIPT - .replace("{mermaidModuleFile}", MERMAID_JS_LOCAL) - .replace("{fallbackRemoteUrl}", MERMAID_JS_CDN)); - + let mermaid_js_init = format!( + r#""#, + MERMAID_INIT_SCRIPT + .replace("{mermaidModuleFile}", MERMAID_JS_LOCAL) + .replace("{fallbackRemoteUrl}", MERMAID_JS_CDN) + ); let body = preamble.chain(parts).chain(postamble).join("\n"); @@ -235,9 +219,9 @@ fn generate_diagram_rustdoc<'a>(parts: impl Iterator) -> TokenStre impl Attrs { pub fn push_attrs(&mut self, attrs: Vec) { - use syn::Lit::*; use syn::Expr; use syn::ExprLit; + use syn::Lit::*; let mut current_location = Location::OutsideDiagram; let mut diagram_start_ident = None; @@ -245,8 +229,10 @@ impl Attrs { for attr in attrs { match attr.meta.require_name_value() { Ok(MetaNameValue { - value: Expr::Lit(ExprLit {lit: Str(s), .. }), path, .. - }) if path.is_ident("doc") => { + value: Expr::Lit(ExprLit { lit: Str(s), .. }), + path, + .. + }) if path.is_ident("doc") => { let ident = path.get_ident().unwrap(); for attr in split_attr_body(ident, &s.value(), &mut current_location) { if attr.is_diagram_start() { @@ -281,7 +267,7 @@ impl Location { fn is_inside(self) -> bool { match self { Location::InsideDiagram => true, - _ => false + _ => false, } } } @@ -306,7 +292,7 @@ fn split_attr_body(ident: &Ident, input: &str, loc: &mut Location) -> Vec buffer: Vec<&'a str>, } - let mut ctx = Default::default(); + let mut ctx: Ctx<'_> = Default::default(); let flush_buffer_as_doc_comment = |ctx: &mut Ctx| { if !ctx.buffer.is_empty() { @@ -326,6 +312,16 @@ fn split_attr_body(ident: &Ident, input: &str, loc: &mut Location) -> Vec while let Some(token) = tokens.next() { match (*loc, token, tokens.peek()) { + // Detect include anchor + (OutsideDiagram, token, _) if token.starts_with("include_mmd!") => { + // cleanup + let path = token.trim_start_matches("include_mmd!").trim(); + let path = path.trim_start_matches('(').trim_end_matches(')'); + let path = path.trim_matches('"'); + let path = PathBuf::from(path); + ctx.attrs + .push(Attr::DiagramIncludeAnchor(ident.clone(), path)); + } // Flush the buffer, then open the diagram code block (OutsideDiagram, TICKS, Some(&MERMAID)) => { tokens.next(); @@ -354,7 +350,7 @@ fn split_attr_body(ident: &Ident, input: &str, loc: &mut Location) -> Vec ctx.attrs } -fn tokenize_doc_str(input: &str) -> impl Iterator { +fn tokenize_doc_str(input: &str) -> impl Iterator { const TICKS: &str = "```"; split_inclusive(input, TICKS).flat_map(|token| { // not str::split_whitespace because we don't wanna filter-out the whitespace tokens @@ -363,7 +359,7 @@ fn tokenize_doc_str(input: &str) -> impl Iterator { } // TODO: remove once str::split_inclusive is stable -fn split_inclusive<'a, 'b: 'a>(input: &'a str, delim: &'b str) -> impl Iterator { +fn split_inclusive<'a, 'b: 'a>(input: &'a str, delim: &'b str) -> impl Iterator { let mut tokens = vec![]; let mut prev = 0; @@ -404,6 +400,9 @@ mod tests { Attr::DiagramStart(..) => f.write_str("Attr::DiagramStart"), Attr::DiagramEntry(_, body) => write!(f, "Attr::DiagramEntry({:?})", body), Attr::DiagramEnd(..) => f.write_str("Attr::DiagramEnd"), + Attr::DiagramIncludeAnchor(_, path) => { + write!(f, "Attr::DiagramIncludeAnchor({:?})", path) + } } } } @@ -428,7 +427,7 @@ mod tests { fn temp_split_inclusive() { let src = "```"; let out: Vec<_> = split_inclusive(src, "```").collect(); - assert_eq!(&out, &["```", ]); + assert_eq!(&out, &["```",]); let src = "```abcd```"; let out: Vec<_> = split_inclusive(src, "```").collect(); @@ -436,7 +435,7 @@ mod tests { let src = "left```abcd```right"; let out: Vec<_> = split_inclusive(src, "```").collect(); - assert_eq!(&out, &["left", "```", "abcd", "```", "right", ]); + assert_eq!(&out, &["left", "```", "abcd", "```", "right",]); } mod split_attr_body_tests { diff --git a/src/lib.rs b/src/lib.rs index 6c0c8b4..b59d539 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ //! //! You can even add multiple diagrams! //! -//! To see it in action, go to the [demo crate](https://docs.rs/aquamarine-demo-crate/0.3.3/aquamarine_demo_crate/fn.example.html) docs.rs page. +//! To see it in action, go to the [demo crate](https://docs.rs/aquamarine-demo-crate/0.4.0/aquamarine_demo_crate/fn.example.html) docs.rs page. //! //! ### Dark-mode //! @@ -51,10 +51,25 @@ //! /// ``` //! # fn example() {} //! ``` -//! -//! [Demo on docs.rs](https://docs.rs/aquamarine-demo-crate/0.3.3/aquamarine_demo_crate/fn.example_with_styling.html) +//! [Demo on docs.rs](https://docs.rs/aquamarine-demo-crate/0.4.0/aquamarine_demo_crate/fn.example_with_styling.html) //! //! To learn more, see the [Theming Section](https://mermaid-js.github.io/mermaid/#/theming) of the mermaid.js book +//! +//! ### Loading from a file +//! +//! When describing complex logic, a diagram can get quite big. +//! +//! To reduce clutter in your doc comments, you can load a diagram from file using the `include_mmd!` macro-like syntax. +//! +//! ```no_run +//! /// Diagram #1 +//! /// include_mmd!("diagram_1.mmd") +//! /// +//! /// Diagram #2 +//! /// include_mmd!("diagram_2.mmd") +//! # fn example() {} +//! ``` +//! [Demo on docs.rs](https://docs.rs/aquamarine-demo-crate/0.4.0/aquamarine_demo_crate/fn.example_load_from_file.html) extern crate proc_macro;