mdbook_mermaid_animate/
lib.rs

1extern crate serde_json;
2
3use anyhow::Result;
4use mdbook_preprocessor::book::{Book, BookItem, Chapter};
5use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
6// use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag, TagEnd};
7use regex::Regex;
8
9pub mod mermaid_of_frame;
10pub mod model;
11pub mod process_diagram;
12use process_diagram::process_diagram;
13pub mod handlebar_helpers;
14pub mod tests;
15
16pub struct MermaidAnimatePreprocessor;
17
18fn run_section(ctx: &PreprocessorContext, section: &BookItem) -> Result<BookItem> {
19    // dbg!("section: {}", &section);
20    let section = match section {
21        BookItem::Chapter(chapter) => run_chapter(ctx, chapter)?,
22        _ => {
23            dbg!("non-chapter section: {:?}", &section);
24            unimplemented!()
25        }
26    };
27    Ok(BookItem::Chapter(section))
28}
29
30fn run_chapter(ctx: &PreprocessorContext, chapter: &Chapter) -> Result<Chapter> {
31    let re = Regex::new(r#"(?ms)(?<mermaid><pre .*?class=\"mermaid\".*?</pre>)"#).unwrap();
32    let mut replacements: Vec<(String, String)> = vec![];
33    let mut data = chapter.content.clone();
34    loop {
35        let caps = re.captures(&data);
36        if caps.is_none() {
37            break;
38        }
39        let caps = caps.unwrap();
40        // dbg!("caps: {:?}", &caps);
41        let diagram = &caps["mermaid"];
42        let processed_diagram = process_diagram(ctx, diagram)?;
43        // dbg!("before diagram: {}", &diagram);
44        // dbg!("processed diagram: {}", &processed_diagram);
45        let id = uuid::Uuid::new_v4();
46        // dbg!("generated id: {}", &id);
47        replacements.push((id.to_string(), processed_diagram.clone()));
48        let mut dst = String::new();
49        dst.push_str(&data[..caps.get(0).unwrap().start()]);
50        dst.push_str(id.to_string().as_str());
51        dst.push_str(&data[caps.get(0).unwrap().end()..]);
52        data = dst;
53    }
54
55    let mut chapter = chapter.clone();
56    for (id, processed_diagram) in replacements {
57        data = data.replace(&id, &processed_diagram);
58    }
59
60    chapter.content = data;
61
62    let sub_items: Vec<_> = chapter
63        .sub_items
64        .iter()
65        .map(|c| run_section(ctx, c))
66        .collect::<Result<Vec<_>>>()?;
67
68    chapter.sub_items = sub_items;
69
70    Ok(chapter.clone())
71}
72
73fn run_all(ctx: &PreprocessorContext, book: &Book) -> Result<Book> {
74    let items: &Vec<BookItem> = &book
75        .items
76        .iter()
77        .map(|section| run_section(ctx, section))
78        .collect::<Result<Vec<_>>>()?;
79
80    let book: &mut Book = &mut book.clone();
81    book.items = items.clone();
82    Ok(book.clone())
83}
84
85impl Preprocessor for MermaidAnimatePreprocessor {
86    fn name(&self) -> &str {
87        "mermaid-animate"
88    }
89
90    fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
91        log::info!("Running mermaid-animate preprocessor");
92
93        let processed_book = run_all(ctx, &book);
94        match processed_book {
95            Err(e) => {
96                eprintln!("Error during processing: {e}");
97                Ok(book)
98            }
99            Ok(b) => Ok(b),
100        }
101    }
102
103    fn supports_renderer(&self, renderer: &str) -> Result<bool> {
104        Ok(renderer == "html")
105    }
106}