From 235c1f87f0333b6ad545d25b20624d093dba478c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 16 Aug 2025 12:48:53 -0700 Subject: [PATCH] Factor out handle_render_command_error This moves `handle_render_command_error` out to the crate root so that it can later be shared with `CmdPreprocessor`. --- .../src/builtin_renderers/mod.rs | 49 +++++-------------- crates/mdbook-driver/src/lib.rs | 30 +++++++++++- tests/testsuite/renderer.rs | 6 +-- 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/crates/mdbook-driver/src/builtin_renderers/mod.rs b/crates/mdbook-driver/src/builtin_renderers/mod.rs index 615dedde..b319ff1f 100644 --- a/crates/mdbook-driver/src/builtin_renderers/mod.rs +++ b/crates/mdbook-driver/src/builtin_renderers/mod.rs @@ -6,7 +6,6 @@ use anyhow::{Context, Result, bail}; use log::{error, info, trace, warn}; use mdbook_renderer::{RenderContext, Renderer}; use std::fs; -use std::io::{self, ErrorKind}; use std::process::Stdio; pub use self::markdown_renderer::MarkdownRenderer; @@ -49,41 +48,6 @@ impl CmdRenderer { } } -impl CmdRenderer { - fn handle_render_command_error(&self, ctx: &RenderContext, error: io::Error) -> Result<()> { - if let ErrorKind::NotFound = error.kind() { - // Look for "output.{self.name}.optional". - // If it exists and is true, treat this as a warning. - // Otherwise, fail the build. - - let optional_key = format!("output.{}.optional", self.name); - - let is_optional = match ctx.config.get(&optional_key) { - Ok(Some(value)) => value, - Err(e) => bail!("expected bool for `{optional_key}`: {e}"), - Ok(None) => false, - }; - - if is_optional { - warn!( - "The command `{}` for backend `{}` was not found, \ - but was marked as optional.", - self.cmd, self.name - ); - return Ok(()); - } else { - error!( - "The command `{0}` wasn't found, is the \"{1}\" backend installed? \ - If you want to ignore this error when the \"{1}\" backend is not installed, \ - set `optional = true` in the `[output.{1}]` section of the book.toml configuration file.", - self.cmd, self.name - ); - } - } - Err(error).with_context(|| "Unable to start the backend")? - } -} - impl Renderer for CmdRenderer { fn name(&self) -> &str { &self.name @@ -92,6 +56,13 @@ impl Renderer for CmdRenderer { fn render(&self, ctx: &RenderContext) -> Result<()> { info!("Invoking the \"{}\" renderer", self.name); + let optional_key = format!("output.{}.optional", self.name); + let optional = match ctx.config.get(&optional_key) { + Ok(Some(value)) => value, + Err(e) => bail!("expected bool for `{optional_key}`: {e}"), + Ok(None) => false, + }; + let _ = fs::create_dir_all(&ctx.destination); let mut cmd = crate::compose_command(&self.cmd, &ctx.root)?; @@ -103,7 +74,11 @@ impl Renderer for CmdRenderer { .spawn() { Ok(c) => c, - Err(e) => return self.handle_render_command_error(ctx, e), + Err(e) => { + return crate::handle_command_error( + e, optional, "output", "backend", &self.name, &self.cmd, + ); + } }; let mut stdin = child.stdin.take().expect("Child has stdin"); diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs index 8eb85c8b..c2b3123f 100644 --- a/crates/mdbook-driver/src/lib.rs +++ b/crates/mdbook-driver/src/lib.rs @@ -64,7 +64,8 @@ pub mod init; mod load; mod mdbook; -use anyhow::{Result, bail}; +use anyhow::{Context, Result, bail}; +use log::{error, warn}; pub use mdbook::MDBook; pub use mdbook_core::{book, config, errors}; use shlex::Shlex; @@ -95,3 +96,30 @@ fn compose_command(cmd: &str, root: &Path) -> Result { Ok(cmd) } + +/// Handles a failure for a preprocessor or renderer. +fn handle_command_error( + error: std::io::Error, + optional: bool, + key: &str, + what: &str, + name: &str, + cmd: &str, +) -> Result<()> { + if let std::io::ErrorKind::NotFound = error.kind() { + if optional { + warn!( + "The command `{cmd}` for {what} `{name}` was not found, \ + but is marked as optional.", + ); + return Ok(()); + } else { + error!( + "The command `{cmd}` wasn't found, is the `{name}` {what} installed? \ + If you want to ignore this error when the `{name}` {what} is not installed, \ + set `optional = true` in the `[{key}.{name}]` section of the book.toml configuration file.", + ); + } + } + Err(error).with_context(|| format!("Unable to run the {what} `{name}`"))? +} diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index 28ff25be..9f2ee010 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -85,9 +85,9 @@ fn missing_renderer() { [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the missing backend [TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "missing" renderer -[TIMESTAMP] [ERROR] (mdbook_driver::builtin_renderers): The command `trduyvbhijnorgevfuhn` wasn't found, is the "missing" backend installed? If you want to ignore this error when the "missing" backend is not installed, set `optional = true` in the `[output.missing]` section of the book.toml configuration file. +[TIMESTAMP] [ERROR] (mdbook_driver): The command `trduyvbhijnorgevfuhn` wasn't found, is the `missing` backend installed? If you want to ignore this error when the `missing` backend is not installed, set `optional = true` in the `[output.missing]` section of the book.toml configuration file. [TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: Unable to start the backend +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: Unable to run the backend `missing` [TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: [NOT_FOUND] "#]]); @@ -102,7 +102,7 @@ fn missing_optional_not_fatal() { [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the missing backend [TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "missing" renderer -[TIMESTAMP] [WARN] (mdbook_driver::builtin_renderers): The command `trduyvbhijnorgevfuhn` for backend `missing` was not found, but was marked as optional. +[TIMESTAMP] [WARN] (mdbook_driver): The command `trduyvbhijnorgevfuhn` for backend `missing` was not found, but is marked as optional. "#]]); });