Skip to content

Exit if a preprocessor command is not found #2597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/nop-preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
let renderer = sub_args
.get_one::<String>("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 {
Expand Down Expand Up @@ -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"
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/book/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,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());
Expand All @@ -619,7 +620,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)]
Expand Down Expand Up @@ -877,7 +878,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
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/cmd/init.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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");
Expand Down
11 changes: 11 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,12 @@ pub struct BuildConfig {
pub use_default_preprocessors: bool,
/// Extra directories to trigger rebuild when watching/serving
pub extra_watch_dirs: Vec<PathBuf>,
/// 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 {
Expand All @@ -489,6 +495,7 @@ impl Default for BuildConfig {
create_missing: true,
use_default_preprocessors: true,
extra_watch_dirs: Vec::new(),
error_on_missing_preprocessor: false,
}
}
}
Expand Down Expand Up @@ -815,6 +822,7 @@ mod tests {
build-dir = "outputs"
create-missing = false
use-default-preprocessors = true
error-on-missing-preprocessor = false

[output.html]
theme = "./themedir"
Expand Down Expand Up @@ -856,6 +864,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 {
Expand Down Expand Up @@ -1067,6 +1076,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 {
Expand Down
18 changes: 12 additions & 6 deletions src/preprocess/cmd.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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}");
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/preprocess/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ 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 is a preprocessor is missing,
/// or to `false` to simply raise a warning and continue generation.
fn supports_renderer(&self, _renderer: &str, _error_on_missing_preprocessor: bool) -> bool {
true
}
}
4 changes: 2 additions & 2 deletions tests/custom_preprocessors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,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);
}
Expand All @@ -24,7 +24,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);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
assert_eq!(
contents,
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nextra-watch-dirs = []\nuse-default-preprocessors = true\n"
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nerror-on-missing-preprocessor = false\nextra-watch-dirs = []\nuse-default-preprocessors = true\n"
);
}

Expand Down