Merge pull request #2802 from ehuss/with-replace
Change with_renderer/with_preprocessor to overwrite
This commit is contained in:
commit
21f2435182
8 changed files with 81 additions and 43 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1335,6 +1335,7 @@ name = "mdbook-driver"
|
||||||
version = "0.5.0-alpha.1"
|
version = "0.5.0-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"mdbook-core",
|
"mdbook-core",
|
||||||
"mdbook-html",
|
"mdbook-html",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ font-awesome-as-a-crate = "0.3.0"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
handlebars = "6.3.2"
|
handlebars = "6.3.2"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
indexmap = "2.10.0"
|
||||||
ignore = "0.4.23"
|
ignore = "0.4.23"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
mdbook-core = { path = "crates/mdbook-core" }
|
mdbook-core = { path = "crates/mdbook-core" }
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
indexmap.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
mdbook-core.workspace = true
|
mdbook-core.workspace = true
|
||||||
mdbook-html.workspace = true
|
mdbook-html.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use crate::builtin_renderers::{CmdRenderer, MarkdownRenderer};
|
||||||
use crate::init::BookBuilder;
|
use crate::init::BookBuilder;
|
||||||
use crate::load::{load_book, load_book_from_disk};
|
use crate::load::{load_book, load_book_from_disk};
|
||||||
use anyhow::{Context, Error, Result, bail};
|
use anyhow::{Context, Error, Result, bail};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use log::{debug, error, info, log_enabled, trace, warn};
|
use log::{debug, error, info, log_enabled, trace, warn};
|
||||||
use mdbook_core::book::{Book, BookItem, BookItems};
|
use mdbook_core::book::{Book, BookItem, BookItems};
|
||||||
use mdbook_core::config::{Config, RustEdition};
|
use mdbook_core::config::{Config, RustEdition};
|
||||||
|
|
@ -28,14 +29,18 @@ mod tests;
|
||||||
pub struct MDBook {
|
pub struct MDBook {
|
||||||
/// The book's root directory.
|
/// The book's root directory.
|
||||||
pub root: PathBuf,
|
pub root: PathBuf,
|
||||||
|
|
||||||
/// The configuration used to tweak now a book is built.
|
/// The configuration used to tweak now a book is built.
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
|
||||||
/// A representation of the book's contents in memory.
|
/// A representation of the book's contents in memory.
|
||||||
pub book: Book,
|
pub book: Book,
|
||||||
renderers: Vec<Box<dyn Renderer>>,
|
|
||||||
|
|
||||||
/// List of pre-processors to be run on the book.
|
/// Renderers to execute.
|
||||||
preprocessors: Vec<Box<dyn Preprocessor>>,
|
renderers: IndexMap<String, Box<dyn Renderer>>,
|
||||||
|
|
||||||
|
/// Pre-processors to be run on the book.
|
||||||
|
preprocessors: IndexMap<String, Box<dyn Preprocessor>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MDBook {
|
impl MDBook {
|
||||||
|
|
@ -156,7 +161,7 @@ impl MDBook {
|
||||||
pub fn build(&self) -> Result<()> {
|
pub fn build(&self) -> Result<()> {
|
||||||
info!("Book building has started");
|
info!("Book building has started");
|
||||||
|
|
||||||
for renderer in &self.renderers {
|
for renderer in self.renderers.values() {
|
||||||
self.execute_build_process(&**renderer)?;
|
self.execute_build_process(&**renderer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +176,7 @@ impl MDBook {
|
||||||
renderer.name().to_string(),
|
renderer.name().to_string(),
|
||||||
);
|
);
|
||||||
let mut preprocessed_book = self.book.clone();
|
let mut preprocessed_book = self.book.clone();
|
||||||
for preprocessor in &self.preprocessors {
|
for preprocessor in self.preprocessors.values() {
|
||||||
if preprocessor_should_run(&**preprocessor, renderer, &self.config)? {
|
if preprocessor_should_run(&**preprocessor, renderer, &self.config)? {
|
||||||
debug!("Running the {} preprocessor.", preprocessor.name());
|
debug!("Running the {} preprocessor.", preprocessor.name());
|
||||||
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
|
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
|
||||||
|
|
@ -207,13 +212,15 @@ impl MDBook {
|
||||||
/// The only requirement is that your renderer implement the [`Renderer`]
|
/// The only requirement is that your renderer implement the [`Renderer`]
|
||||||
/// trait.
|
/// trait.
|
||||||
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
|
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
|
||||||
self.renderers.push(Box::new(renderer));
|
self.renderers
|
||||||
|
.insert(renderer.name().to_string(), Box::new(renderer));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a [`Preprocessor`] to be used when rendering the book.
|
/// Register a [`Preprocessor`] to be used when rendering the book.
|
||||||
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
|
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
|
||||||
self.preprocessors.push(Box::new(preprocessor));
|
self.preprocessors
|
||||||
|
.insert(preprocessor.name().to_string(), Box::new(preprocessor));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,10 +265,9 @@ impl MDBook {
|
||||||
|
|
||||||
// Index Preprocessor is disabled so that chapter paths
|
// Index Preprocessor is disabled so that chapter paths
|
||||||
// continue to point to the actual markdown files.
|
// continue to point to the actual markdown files.
|
||||||
self.preprocessors = determine_preprocessors(&self.config, &self.root)?
|
self.preprocessors = determine_preprocessors(&self.config, &self.root)?;
|
||||||
.into_iter()
|
self.preprocessors
|
||||||
.filter(|pre| pre.name() != IndexPreprocessor::NAME)
|
.shift_remove_entry(IndexPreprocessor::NAME);
|
||||||
.collect();
|
|
||||||
let (book, _) = self.preprocess_book(&TestRenderer)?;
|
let (book, _) = self.preprocess_book(&TestRenderer)?;
|
||||||
|
|
||||||
let color_output = std::io::stderr().is_terminal();
|
let color_output = std::io::stderr().is_terminal();
|
||||||
|
|
@ -399,24 +405,25 @@ struct OutputConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look at the `Config` and try to figure out what renderers to use.
|
/// Look at the `Config` and try to figure out what renderers to use.
|
||||||
fn determine_renderers(config: &Config) -> Result<Vec<Box<dyn Renderer>>> {
|
fn determine_renderers(config: &Config) -> Result<IndexMap<String, Box<dyn Renderer>>> {
|
||||||
let mut renderers = Vec::new();
|
let mut renderers = IndexMap::new();
|
||||||
|
|
||||||
let outputs = config.outputs::<OutputConfig>()?;
|
let outputs = config.outputs::<OutputConfig>()?;
|
||||||
renderers.extend(outputs.into_iter().map(|(key, table)| {
|
renderers.extend(outputs.into_iter().map(|(key, table)| {
|
||||||
if key == "html" {
|
let renderer = if key == "html" {
|
||||||
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
|
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
|
||||||
} else if key == "markdown" {
|
} else if key == "markdown" {
|
||||||
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
|
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
|
||||||
} else {
|
} else {
|
||||||
let command = table.command.unwrap_or_else(|| format!("mdbook-{key}"));
|
let command = table.command.unwrap_or_else(|| format!("mdbook-{key}"));
|
||||||
Box::new(CmdRenderer::new(key, command))
|
Box::new(CmdRenderer::new(key.clone(), command))
|
||||||
}
|
};
|
||||||
|
(key, renderer)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// if we couldn't find anything, add the HTML renderer as a default
|
// if we couldn't find anything, add the HTML renderer as a default
|
||||||
if renderers.is_empty() {
|
if renderers.is_empty() {
|
||||||
renderers.push(Box::new(HtmlHandlebars::new()));
|
renderers.insert("html".to_string(), Box::new(HtmlHandlebars::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(renderers)
|
Ok(renderers)
|
||||||
|
|
@ -442,7 +449,10 @@ struct PreprocessorConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
||||||
fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn Preprocessor>>> {
|
fn determine_preprocessors(
|
||||||
|
config: &Config,
|
||||||
|
root: &Path,
|
||||||
|
) -> Result<IndexMap<String, Box<dyn Preprocessor>>> {
|
||||||
// Collect the names of all preprocessors intended to be run, and the order
|
// Collect the names of all preprocessors intended to be run, and the order
|
||||||
// in which they should be run.
|
// in which they should be run.
|
||||||
let mut preprocessor_names = TopologicalSort::<String>::new();
|
let mut preprocessor_names = TopologicalSort::<String>::new();
|
||||||
|
|
@ -490,7 +500,7 @@ fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn P
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that all links have been established, queue preprocessors in a suitable order
|
// Now that all links have been established, queue preprocessors in a suitable order
|
||||||
let mut preprocessors = Vec::with_capacity(preprocessor_names.len());
|
let mut preprocessors = IndexMap::with_capacity(preprocessor_names.len());
|
||||||
// `pop_all()` returns an empty vector when no more items are not being depended upon
|
// `pop_all()` returns an empty vector when no more items are not being depended upon
|
||||||
for mut names in std::iter::repeat_with(|| preprocessor_names.pop_all())
|
for mut names in std::iter::repeat_with(|| preprocessor_names.pop_all())
|
||||||
.take_while(|names| !names.is_empty())
|
.take_while(|names| !names.is_empty())
|
||||||
|
|
@ -516,14 +526,14 @@ fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn P
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.unwrap_or_else(|| format!("mdbook-{name}"));
|
.unwrap_or_else(|| format!("mdbook-{name}"));
|
||||||
Box::new(CmdPreprocessor::new(
|
Box::new(CmdPreprocessor::new(
|
||||||
name,
|
name.clone(),
|
||||||
command,
|
command,
|
||||||
root.to_owned(),
|
root.to_owned(),
|
||||||
table.optional,
|
table.optional,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
preprocessors.push(preprocessor);
|
preprocessors.insert(name, preprocessor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ fn can_determine_third_party_preprocessors() {
|
||||||
|
|
||||||
let got = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
let got = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
||||||
|
|
||||||
assert!(got.into_iter().any(|p| p.name() == "random"));
|
assert!(got.contains_key("random"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -143,19 +143,12 @@ fn preprocessor_order_is_honored() {
|
||||||
let cfg = Config::from_str(cfg_str).unwrap();
|
let cfg = Config::from_str(cfg_str).unwrap();
|
||||||
|
|
||||||
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
||||||
let index = |name| {
|
let index = |name| preprocessors.get_index_of(name).unwrap();
|
||||||
preprocessors
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, preprocessor)| preprocessor.name() == name)
|
|
||||||
.unwrap()
|
|
||||||
.0
|
|
||||||
};
|
|
||||||
let assert_before = |before, after| {
|
let assert_before = |before, after| {
|
||||||
if index(before) >= index(after) {
|
if index(before) >= index(after) {
|
||||||
eprintln!("Preprocessor order:");
|
eprintln!("Preprocessor order:");
|
||||||
for preprocessor in &preprocessors {
|
for preprocessor in preprocessors.keys() {
|
||||||
eprintln!(" {}", preprocessor.name());
|
eprintln!(" {}", preprocessor);
|
||||||
}
|
}
|
||||||
panic!("{before} should come before {after}");
|
panic!("{before} should come before {after}");
|
||||||
}
|
}
|
||||||
|
|
@ -193,11 +186,8 @@ fn dependencies_dont_register_undefined_preprocessors() {
|
||||||
|
|
||||||
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
||||||
|
|
||||||
assert!(
|
// Does not contain "random"
|
||||||
!preprocessors
|
assert_eq!(preprocessors.keys().collect::<Vec<_>>(), ["index", "links"]);
|
||||||
.iter()
|
|
||||||
.any(|preprocessor| preprocessor.name() == "random")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -214,11 +204,8 @@ fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
|
||||||
|
|
||||||
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
|
||||||
|
|
||||||
assert!(
|
// Does not contain "links"
|
||||||
!preprocessors
|
assert_eq!(preprocessors.keys().collect::<Vec<_>>(), ["random"]);
|
||||||
.iter()
|
|
||||||
.any(|preprocessor| preprocessor.name() == "links")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ enum StatusCode {
|
||||||
pub struct BookTest {
|
pub struct BookTest {
|
||||||
/// The temp directory where the test should perform its work.
|
/// The temp directory where the test should perform its work.
|
||||||
pub dir: PathBuf,
|
pub dir: PathBuf,
|
||||||
assert: snapbox::Assert,
|
/// Snapshot assertion support.
|
||||||
|
pub assert: snapbox::Assert,
|
||||||
/// This indicates whether or not the book has been built.
|
/// This indicates whether or not the book has been built.
|
||||||
built: bool,
|
built: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,3 +180,22 @@ fn missing_optional_not_fatal() {
|
||||||
"#]]);
|
"#]]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// with_preprocessor of an existing name.
|
||||||
|
#[test]
|
||||||
|
fn with_preprocessor_same_name() {
|
||||||
|
let mut test = BookTest::init(|_| {});
|
||||||
|
test.change_file(
|
||||||
|
"book.toml",
|
||||||
|
"[preprocessor.dummy]\n\
|
||||||
|
command = 'mdbook-preprocessor-does-not-exist'\n",
|
||||||
|
);
|
||||||
|
let spy: Arc<Mutex<Inner>> = Default::default();
|
||||||
|
let mut book = test.load_book();
|
||||||
|
book.with_preprocessor(Spy(Arc::clone(&spy)));
|
||||||
|
// Unfortunately this is unable to capture the output when using the API.
|
||||||
|
book.build().unwrap();
|
||||||
|
let inner = spy.lock().unwrap();
|
||||||
|
assert_eq!(inner.run_count, 1);
|
||||||
|
assert_eq!(inner.rendered_with, ["html"]);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,3 +241,21 @@ fn relative_command_path() {
|
||||||
})
|
})
|
||||||
.check_file("book/output", "test");
|
.check_file("book/output", "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// with_renderer of an existing name.
|
||||||
|
#[test]
|
||||||
|
fn with_renderer_same_name() {
|
||||||
|
let mut test = BookTest::init(|_| {});
|
||||||
|
test.change_file(
|
||||||
|
"book.toml",
|
||||||
|
"[output.dummy]\n\
|
||||||
|
command = 'mdbook-renderer-does-not-exist'\n",
|
||||||
|
);
|
||||||
|
let spy: Arc<Mutex<Inner>> = Default::default();
|
||||||
|
let mut book = test.load_book();
|
||||||
|
book.with_renderer(Spy(Arc::clone(&spy)));
|
||||||
|
// Unfortunately this is unable to capture the output when using the API.
|
||||||
|
book.build().unwrap();
|
||||||
|
let inner = spy.lock().unwrap();
|
||||||
|
assert_eq!(inner.run_count, 1);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue