diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index a384dfdafb..ecf2c46f03 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -57,7 +57,7 @@ fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! { let renderer = sub_args .get_one::("renderer") .expect("Required argument"); - let supported = pre.supports_renderer(renderer); + let supported = pre.supports_renderer(renderer, false); // Signal whether the renderer is supported by exiting with 1 or 0. if supported { @@ -99,7 +99,7 @@ mod nop_lib { Ok(book) } - fn supports_renderer(&self, renderer: &str) -> bool { + fn supports_renderer(&self, renderer: &str, _error_on_missing_preprocessor: bool) -> bool { renderer != "not-supported" } } diff --git a/src/book/mod.rs b/src/book/mod.rs index da88767a8d..d6155a283c 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -605,7 +605,8 @@ fn preprocessor_should_run( ) -> bool { // default preprocessors should be run by default (if supported) if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) { - return preprocessor.supports_renderer(renderer.name()); + return preprocessor + .supports_renderer(renderer.name(), cfg.build.error_on_missing_preprocessor); } let key = format!("preprocessor.{}.renderers", preprocessor.name()); @@ -618,7 +619,7 @@ fn preprocessor_should_run( .any(|name| name == renderer_name); } - preprocessor.supports_renderer(renderer_name) + preprocessor.supports_renderer(renderer_name, cfg.build.error_on_missing_preprocessor) } #[cfg(test)] @@ -876,7 +877,7 @@ mod tests { unimplemented!() } - fn supports_renderer(&self, _renderer: &str) -> bool { + fn supports_renderer(&self, _renderer: &str, _error_on_missing_preprocessor: bool) -> bool { self.0 } } diff --git a/src/cmd/init.rs b/src/cmd/init.rs index f15fb96865..27fad2e3e0 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,8 +1,7 @@ use crate::get_book_dir; use clap::{arg, ArgMatches, Command as ClapCommand}; -use mdbook::config; use mdbook::errors::Result; -use mdbook::MDBook; +use mdbook::{Config, MDBook}; use std::io; use std::io::Write; use std::process::Command; @@ -31,7 +30,12 @@ pub fn make_subcommand() -> ClapCommand { pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let mut builder = MDBook::init(&book_dir); - let mut config = config::Config::default(); + + let mut config = Config::default(); + // We want new books to raise an error if a preprocessor is missing, but we want old books to simply emit a warning. + // This is why BuildConfig::default() sets it to false and why we force it to true here + config.build.error_on_missing_preprocessor = true; + // If flag `--theme` is present, copy theme to src if args.get_flag("theme") { let theme_dir = book_dir.join("theme"); diff --git a/src/config.rs b/src/config.rs index 7ef8bcef12..4484ab7b9c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -496,6 +496,12 @@ pub struct BuildConfig { pub use_default_preprocessors: bool, /// Extra directories to trigger rebuild when watching/serving pub extra_watch_dirs: Vec, + /// Should missing a preprocessor be considered an error? + /// By default, the application raises a warning instead and continue generation, + /// even if the book may be generated incorrectly. + /// Set this flag to ̀false` to consider this an error, and exits the application + /// if a preprocessor is missing. + pub error_on_missing_preprocessor: bool, } impl Default for BuildConfig { @@ -505,6 +511,7 @@ impl Default for BuildConfig { create_missing: true, use_default_preprocessors: true, extra_watch_dirs: Vec::new(), + error_on_missing_preprocessor: false, } } } @@ -831,6 +838,7 @@ mod tests { build-dir = "outputs" create-missing = false use-default-preprocessors = true + error-on-missing-preprocessor = false [output.html] theme = "./themedir" @@ -872,6 +880,7 @@ mod tests { create_missing: false, use_default_preprocessors: true, extra_watch_dirs: Vec::new(), + error_on_missing_preprocessor: false, }; let rust_should_be = RustConfig { edition: None }; let playground_should_be = Playground { @@ -1083,6 +1092,8 @@ mod tests { create_missing: true, use_default_preprocessors: true, extra_watch_dirs: Vec::new(), + error_on_missing_preprocessor: false, // This flag is missing from "src" string, + // so it should be false to ensure backward compatibility }; let html_should_be = HtmlConfig { diff --git a/src/preprocess/cmd.rs b/src/preprocess/cmd.rs index 149dabda56..3e8bfe0888 100644 --- a/src/preprocess/cmd.rs +++ b/src/preprocess/cmd.rs @@ -1,7 +1,7 @@ use super::{Preprocessor, PreprocessorContext}; use crate::book::Book; use crate::errors::*; -use log::{debug, trace, warn}; +use log::{debug, error, trace, warn}; use shlex::Shlex; use std::io::{self, Read, Write}; use std::process::{Child, Command, Stdio}; @@ -134,7 +134,7 @@ impl Preprocessor for CmdPreprocessor { }) } - fn supports_renderer(&self, renderer: &str) -> bool { + fn supports_renderer(&self, renderer: &str, error_on_missing_preprocessor: bool) -> bool { debug!( "Checking if the \"{}\" preprocessor supports \"{}\"", self.name(), @@ -164,11 +164,17 @@ impl Preprocessor for CmdPreprocessor { if let Err(ref e) = outcome { if e.kind() == io::ErrorKind::NotFound { - warn!( - "The command wasn't found, is the \"{}\" preprocessor installed?", - self.name + let message = format!( + "The command \"{}\" wasn't found, is the \"{}\" preprocessor installed?", + self.cmd, self.name ); - warn!("\tCommand: {}", self.cmd); + + if error_on_missing_preprocessor { + error!("{message}"); + std::process::exit(1); + } else { + warn!("{message}"); + } } } diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index df01a3dbfb..6bfaff2792 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -63,7 +63,10 @@ pub trait Preprocessor { /// particular renderer. /// /// By default, always returns `true`. - fn supports_renderer(&self, _renderer: &str) -> bool { + /// + /// Set `error_on_missing_preprocessor` to `true` to exit the application if a preprocessor is missing, + /// or to `false` to simply raise a warning and continue the generation. + fn supports_renderer(&self, _renderer: &str, _error_on_missing_preprocessor: bool) -> bool { true } } diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs index e5989eb62c..a1f985906c 100644 --- a/tests/testsuite/init.rs +++ b/tests/testsuite/init.rs @@ -30,6 +30,13 @@ authors = [] language = "en" src = "src" +[build] +build-dir = "book" +create-missing = true +error-on-missing-preprocessor = true +extra-watch-dirs = [] +use-default-preprocessors = true + "#]], ) .check_file( @@ -96,6 +103,13 @@ authors = [] language = "en" src = "src" +[build] +build-dir = "book" +create-missing = true +error-on-missing-preprocessor = true +extra-watch-dirs = [] +use-default-preprocessors = true + "#]], ); assert!(!test.dir.join(".gitignore").exists()); @@ -130,6 +144,13 @@ language = "en" src = "src" title = "Example title" +[build] +build-dir = "book" +create-missing = true +error-on-missing-preprocessor = true +extra-watch-dirs = [] +use-default-preprocessors = true + "#]], ); assert!(!test.dir.join(".gitignore").exists()); @@ -184,6 +205,7 @@ src = "in" [build] build-dir = "out" create-missing = true +error-on-missing-preprocessor = false extra-watch-dirs = [] use-default-preprocessors = true diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index d2c1608bd7..42aa0d266a 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -80,7 +80,7 @@ fn example() -> CmdPreprocessor { fn example_supports_whatever() { let cmd = example(); - let got = cmd.supports_renderer("whatever"); + let got = cmd.supports_renderer("whatever", false); assert_eq!(got, true); } @@ -89,7 +89,7 @@ fn example_supports_whatever() { fn example_doesnt_support_not_supported() { let cmd = example(); - let got = cmd.supports_renderer("not-supported"); + let got = cmd.supports_renderer("not-supported", false); assert_eq!(got, false); }