Skip to main content

mdbook_plotly/preprocessor/
handlers.rs

1pub mod code_handler;
2#[cfg(feature = "plotly-html-handler")]
3pub mod plotly_html_handler;
4#[cfg(feature = "plotly-svg-handler")]
5pub mod plotly_svg_handler;
6
7use crate::preprocessor::config::{PlotlyOutputType, PreprocessorConfig};
8use anyhow::Result;
9use log::{error, warn};
10use mdbook_preprocessor::book::Chapter;
11use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd};
12use std::path::Path;
13
14const PULLDOWN_CMARK_OPTIONS: pulldown_cmark::Options = pulldown_cmark::Options::all();
15
16pub fn handle(chapter: &mut Chapter, config: &PreprocessorConfig, book_path: &Path) {
17    let events = Parser::new_ext(&chapter.content, PULLDOWN_CMARK_OPTIONS);
18
19    let mut code = String::with_capacity(10);
20    let mut in_target_code = false;
21    let mut new_events = Vec::with_capacity(10);
22
23    if config.output_type == PlotlyOutputType::PlotlyHtml {
24        new_events.push(plotly_html_handler::inject_header(
25            config.offline_js_sources,
26        ));
27        new_events.push(Event::HardBreak);
28    }
29
30    for event in events {
31        match event {
32            Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref lang))) => {
33                if matches!(lang.as_ref(), "plotly" | "plot") {
34                    in_target_code = true;
35                    code.clear();
36                } else {
37                    new_events.push(event);
38                }
39            }
40            Event::Text(ref text) => {
41                if in_target_code {
42                    code.push_str(text);
43                } else {
44                    new_events.push(event);
45                }
46            }
47            Event::End(TagEnd::CodeBlock) => {
48                if !in_target_code {
49                    new_events.push(event);
50                    continue;
51                }
52                in_target_code = false;
53                let ready_code = std::mem::take(&mut code);
54                match handle_plotly(ready_code, config, book_path) {
55                    Ok(event) => {
56                        new_events.push(Event::HardBreak);
57                        new_events.push(event);
58                        new_events.push(Event::HardBreak);
59                    }
60                    Err(message) => warn!(
61                        "An error occurred during processing in Chapter {}:\n{}",
62                        chapter.name, message
63                    ),
64                }
65            }
66            _ => new_events.push(event),
67        }
68    }
69    let mut new_content = String::with_capacity(chapter.content.len());
70    if let Err(e) = pulldown_cmark_to_cmark::cmark_with_options(
71        new_events.into_iter(),
72        &mut new_content,
73        pulldown_cmark_to_cmark::Options::default(),
74    ) {
75        error!(
76            "Processing failed during cmark. {} Chapter will use the original content. \nInterError: {:#?}",
77            chapter.name, e
78        );
79    } else {
80        chapter.content = new_content;
81    }
82}
83
84#[allow(unused)]
85pub fn handle_plotly<'a>(
86    code: String,
87    config: &'a PreprocessorConfig,
88    book_path: &'a Path,
89) -> Result<Event<'static>> {
90    let plot = code_handler::handle(code, &config.input_type)?;
91    let result = match config.output_type {
92        #[cfg(feature = "plotly-html-handler")]
93        PlotlyOutputType::PlotlyHtml => plotly_html_handler::handle(plot),
94        #[cfg(feature = "plotly-svg-handler")]
95        PlotlyOutputType::PlotlySvg => plotly_svg_handler::handle(plot, book_path),
96    };
97    Ok(result)
98}