Skip to main content

mdbook_plotly/preprocessor/
handlers.rs

1#[cfg(feature = "plotly-html-handler")]
2pub mod plotly_html_handler;
3#[cfg(feature = "plotly-svg-handler")]
4pub mod plotly_svg_handler;
5
6use crate::code_handler;
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    if !chapter.content.contains("plot") {
18        return;
19    }
20
21    let events = Parser::new_ext(&chapter.content, PULLDOWN_CMARK_OPTIONS);
22
23    let mut code = String::with_capacity(10);
24    let mut in_target_code = false;
25    let mut new_events = Vec::with_capacity(10);
26    let mut code_sequence = 0;
27    let mut saw_target_code = false;
28    let mut generated_plot = false;
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                    saw_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                        generated_plot = true;
59                        new_events.push(Event::HardBreak);
60                        new_events.push(event);
61                        new_events.push(Event::HardBreak);
62                    }
63                    Err(message) => warn!(
64                        "An error occurred during processing in {} at sequence {}:\n{}",
65                        chapter.name, code_sequence, message
66                    ),
67                }
68            }
69            _ => new_events.push(event),
70        }
71    }
72    if !saw_target_code {
73        return;
74    }
75
76    if config.output_type == PlotlyOutputType::PlotlyHtml && generated_plot {
77        new_events.insert(0, Event::HardBreak);
78        new_events.insert(0, plotly_html_handler::inject_header());
79    }
80
81    let mut new_content = String::with_capacity(chapter.content.len());
82    if let Err(e) = pulldown_cmark_to_cmark::cmark_with_options(
83        new_events.into_iter(),
84        &mut new_content,
85        pulldown_cmark_to_cmark::Options::default(),
86    ) {
87        error!(
88            "Processing failed during cmark. {} Chapter will use the original content. \nInterError: {:#?}",
89            chapter.name, e
90        );
91    } else {
92        chapter.content = new_content;
93    }
94}
95
96#[allow(unused)]
97pub fn handle_plotly<'a>(
98    code: String,
99    config: &'a PreprocessorConfig,
100    book_path: &'a Path,
101) -> Result<Event<'static>> {
102    let plot = code_handler::handle(code, &config.input_type, &config.map_eval)?;
103    let result = match config.output_type {
104        #[cfg(feature = "plotly-html-handler")]
105        PlotlyOutputType::PlotlyHtml => plotly_html_handler::handle(plot),
106        #[cfg(feature = "plotly-svg-handler")]
107        PlotlyOutputType::PlotlySvg => plotly_svg_handler::handle(plot, book_path),
108    };
109    Ok(result)
110}