mdbook_driver/builtin_renderers/
mod.rs

1//! Built-in renderers.
2//!
3//! The HTML renderer can be found in the [`mdbook_html`] crate.
4
5use anyhow::{Context, Result, bail};
6use mdbook_core::utils::fs;
7use mdbook_renderer::{RenderContext, Renderer};
8use std::process::Stdio;
9use tracing::{error, info, trace, warn};
10
11pub use self::markdown_renderer::MarkdownRenderer;
12
13mod markdown_renderer;
14
15/// A generic renderer which will shell out to an arbitrary executable.
16///
17/// See <https://rust-lang.github.io/mdBook/for_developers/backends.html>
18/// for a description of the renderer protocol.
19#[derive(Debug, Clone, PartialEq)]
20pub struct CmdRenderer {
21    name: String,
22    cmd: String,
23}
24
25impl CmdRenderer {
26    /// Create a new `CmdRenderer` which will invoke the provided `cmd` string.
27    pub fn new(name: String, cmd: String) -> CmdRenderer {
28        CmdRenderer { name, cmd }
29    }
30}
31
32impl Renderer for CmdRenderer {
33    fn name(&self) -> &str {
34        &self.name
35    }
36
37    fn render(&self, ctx: &RenderContext) -> Result<()> {
38        info!("Invoking the \"{}\" renderer", self.name);
39
40        let optional_key = format!("output.{}.optional", self.name);
41        let optional = match ctx.config.get(&optional_key) {
42            Ok(Some(value)) => value,
43            Err(e) => bail!("expected bool for `{optional_key}`: {e}"),
44            Ok(None) => false,
45        };
46
47        let _ = fs::create_dir_all(&ctx.destination);
48
49        let mut cmd = crate::compose_command(&self.cmd, &ctx.root)?;
50        let mut child = match cmd
51            .stdin(Stdio::piped())
52            .stdout(Stdio::inherit())
53            .stderr(Stdio::inherit())
54            .current_dir(&ctx.destination)
55            .spawn()
56        {
57            Ok(c) => c,
58            Err(e) => {
59                return crate::handle_command_error(
60                    e, optional, "output", "backend", &self.name, &self.cmd,
61                );
62            }
63        };
64
65        let mut stdin = child.stdin.take().expect("Child has stdin");
66        if let Err(e) = serde_json::to_writer(&mut stdin, &ctx) {
67            // Looks like the backend hung up before we could finish
68            // sending it the render context. Log the error and keep going
69            warn!("Error writing the RenderContext to the backend, {}", e);
70        }
71
72        // explicitly close the `stdin` file handle
73        drop(stdin);
74
75        let status = child
76            .wait()
77            .with_context(|| "Error waiting for the backend to complete")?;
78
79        trace!("{} exited with output: {:?}", self.cmd, status);
80
81        if !status.success() {
82            error!("Renderer exited with non-zero return code.");
83            bail!("The \"{}\" renderer failed", self.name);
84        } else {
85            Ok(())
86        }
87    }
88}