mdbook_plotly/preprocessor/
handlers.rs1#[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}