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