diff --git a/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs index c4925893..498b055d 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result, ensure}; use log::{debug, trace, warn}; use mdbook_core::book::Book; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; -use std::io::{self, Write}; +use std::io::Write; use std::path::PathBuf; use std::process::{Child, Stdio}; @@ -34,12 +34,18 @@ pub struct CmdPreprocessor { name: String, cmd: String, root: PathBuf, + optional: bool, } impl CmdPreprocessor { /// Create a new `CmdPreprocessor`. - pub fn new(name: String, cmd: String, root: PathBuf) -> CmdPreprocessor { - CmdPreprocessor { name, cmd, root } + pub fn new(name: String, cmd: String, root: PathBuf, optional: bool) -> CmdPreprocessor { + CmdPreprocessor { + name, + cmd, + root, + optional, + } } fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) { @@ -75,18 +81,29 @@ impl Preprocessor for CmdPreprocessor { fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result { let mut cmd = crate::compose_command(&self.cmd, &ctx.root)?; - let mut child = cmd + let mut child = match cmd .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .current_dir(&self.root) .spawn() - .with_context(|| { - format!( - "Unable to start the \"{}\" preprocessor. Is it installed?", - self.name() - ) - })?; + { + Ok(c) => c, + Err(e) => { + crate::handle_command_error( + e, + self.optional, + "preprocessor", + "preprocessor", + &self.name, + &self.cmd, + )?; + // This should normally not be reached, since the validation + // for NotFound should have already happened when running the + // "supports" command. + return Ok(book); + } + }; self.write_input_to_child(&mut child, &book, ctx); @@ -123,7 +140,7 @@ impl Preprocessor for CmdPreprocessor { let mut cmd = crate::compose_command(&self.cmd, &self.root)?; - let outcome = cmd + match cmd .arg("supports") .arg(renderer) .stdin(Stdio::null()) @@ -131,19 +148,20 @@ impl Preprocessor for CmdPreprocessor { .stderr(Stdio::inherit()) .current_dir(&self.root) .status() - .map(|status| status.code() == Some(0)); - - if let Err(ref e) = outcome { - if e.kind() == io::ErrorKind::NotFound { - warn!( - "The command wasn't found, is the \"{}\" preprocessor installed?", - self.name - ); - warn!("\tCommand: {}", self.cmd); + { + Ok(status) => Ok(status.code() == Some(0)), + Err(e) => { + crate::handle_command_error( + e, + self.optional, + "preprocessor", + "preprocessor", + &self.name, + &self.cmd, + )?; + Ok(false) } } - - Ok(outcome.unwrap_or(false)) } } @@ -161,7 +179,12 @@ mod tests { #[test] fn round_trip_write_and_parse_input() { let md = guide(); - let cmd = CmdPreprocessor::new("test".to_string(), "test".to_string(), md.root.clone()); + let cmd = CmdPreprocessor::new( + "test".to_string(), + "test".to_string(), + md.root.clone(), + false, + ); let ctx = PreprocessorContext::new( md.root.clone(), md.config.clone(), diff --git a/crates/mdbook-driver/src/mdbook.rs b/crates/mdbook-driver/src/mdbook.rs index 19abdd87..4edc27c6 100644 --- a/crates/mdbook-driver/src/mdbook.rs +++ b/crates/mdbook-driver/src/mdbook.rs @@ -437,6 +437,8 @@ struct PreprocessorConfig { before: Vec, #[serde(default)] after: Vec, + #[serde(default)] + optional: bool, } /// Look at the `MDBook` and try to figure out what preprocessors to run. @@ -513,7 +515,12 @@ fn determine_preprocessors(config: &Config, root: &Path) -> Result CmdPreprocessor { "nop-preprocessor".to_string(), "cargo run --quiet --example nop-preprocessor --".to_string(), std::env::current_dir().unwrap(), + false, ) } @@ -154,11 +155,25 @@ fn relative_command_path() { #[test] fn missing_preprocessor() { BookTest::from_dir("preprocessor/missing_preprocessor").run("build", |cmd| { - cmd.expect_stdout(str![[""]]) + cmd.expect_failure() + .expect_stdout(str![[""]]) .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started -[TIMESTAMP] [WARN] (mdbook_driver::builtin_preprocessors::cmd): The command wasn't found, is the "missing" preprocessor installed? -[TIMESTAMP] [WARN] (mdbook_driver::builtin_preprocessors::cmd): [TAB]Command: trduyvbhijnorgevfuhn +[TIMESTAMP] [ERROR] (mdbook_driver): The command `trduyvbhijnorgevfuhn` wasn't found, is the `missing` preprocessor installed? If you want to ignore this error when the `missing` preprocessor is not installed, set `optional = true` in the `[preprocessor.missing]` section of the book.toml configuration file. +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Unable to run the preprocessor `missing` +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: [NOT_FOUND] + +"#]]); + }); +} + +// Optional missing is not an error. +#[test] +fn missing_optional_not_fatal() { + BookTest::from_dir("preprocessor/missing_optional_not_fatal").run("build", |cmd| { + cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#" +[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started +[TIMESTAMP] [WARN] (mdbook_driver): The command `trduyvbhijnorgevfuhn` for preprocessor `missing` was not found, but is marked as optional. [TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend [TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` diff --git a/tests/testsuite/preprocessor/missing_optional_not_fatal/book.toml b/tests/testsuite/preprocessor/missing_optional_not_fatal/book.toml new file mode 100644 index 00000000..59bc75cd --- /dev/null +++ b/tests/testsuite/preprocessor/missing_optional_not_fatal/book.toml @@ -0,0 +1,3 @@ +[preprocessor.missing] +command = "trduyvbhijnorgevfuhn" +optional = true diff --git a/tests/testsuite/preprocessor/missing_optional_not_fatal/src/SUMMARY.md b/tests/testsuite/preprocessor/missing_optional_not_fatal/src/SUMMARY.md new file mode 100644 index 00000000..e69de29b