Skip to content

Commit 338a9b4

Browse files
committed
Change with_renderer/with_preprocessor to overwrite
This changes `with_renderer` and `with_preprocessor` to replace any extensions of the same name instead of just appending to the list. This is necessary for rust-lang's build process, because we replace the preprocessors with local ones. Previously, mdbook would just print an error, but continue working. With the change that preprocessors are no longer optional by default, it is now required that we have a way to replace the existing entries.
1 parent 25c47ed commit 338a9b4

File tree

7 files changed

+51
-63
lines changed

7 files changed

+51
-63
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ font-awesome-as-a-crate = "0.3.0"
3737
futures-util = "0.3.31"
3838
handlebars = "6.3.2"
3939
hex = "0.4.3"
40+
indexmap = "2.10.0"
4041
ignore = "0.4.23"
4142
log = "0.4.27"
4243
mdbook-core = { path = "crates/mdbook-core" }

crates/mdbook-driver/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ rust-version.workspace = true
99

1010
[dependencies]
1111
anyhow.workspace = true
12+
indexmap.workspace = true
1213
log.workspace = true
1314
mdbook-core.workspace = true
1415
mdbook-html.workspace = true

crates/mdbook-driver/src/mdbook.rs

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::builtin_renderers::{CmdRenderer, MarkdownRenderer};
55
use crate::init::BookBuilder;
66
use crate::load::{load_book, load_book_from_disk};
77
use anyhow::{Context, Error, Result, bail};
8+
use indexmap::IndexMap;
89
use log::{debug, error, info, log_enabled, trace, warn};
910
use mdbook_core::book::{Book, BookItem, BookItems};
1011
use mdbook_core::config::{Config, RustEdition};
@@ -28,14 +29,18 @@ mod tests;
2829
pub struct MDBook {
2930
/// The book's root directory.
3031
pub root: PathBuf,
32+
3133
/// The configuration used to tweak now a book is built.
3234
pub config: Config,
35+
3336
/// A representation of the book's contents in memory.
3437
pub book: Book,
35-
renderers: Vec<Box<dyn Renderer>>,
3638

37-
/// List of pre-processors to be run on the book.
38-
preprocessors: Vec<Box<dyn Preprocessor>>,
39+
/// Renderers to execute.
40+
renderers: IndexMap<String, Box<dyn Renderer>>,
41+
42+
/// Pre-processors to be run on the book.
43+
preprocessors: IndexMap<String, Box<dyn Preprocessor>>,
3944
}
4045

4146
impl MDBook {
@@ -156,7 +161,7 @@ impl MDBook {
156161
pub fn build(&self) -> Result<()> {
157162
info!("Book building has started");
158163

159-
for renderer in &self.renderers {
164+
for renderer in self.renderers.values() {
160165
self.execute_build_process(&**renderer)?;
161166
}
162167

@@ -171,7 +176,7 @@ impl MDBook {
171176
renderer.name().to_string(),
172177
);
173178
let mut preprocessed_book = self.book.clone();
174-
for preprocessor in &self.preprocessors {
179+
for preprocessor in self.preprocessors.values() {
175180
if preprocessor_should_run(&**preprocessor, renderer, &self.config)? {
176181
debug!("Running the {} preprocessor.", preprocessor.name());
177182
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
@@ -207,13 +212,15 @@ impl MDBook {
207212
/// The only requirement is that your renderer implement the [`Renderer`]
208213
/// trait.
209214
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
210-
self.renderers.push(Box::new(renderer));
215+
self.renderers
216+
.insert(renderer.name().to_string(), Box::new(renderer));
211217
self
212218
}
213219

214220
/// Register a [`Preprocessor`] to be used when rendering the book.
215221
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
216-
self.preprocessors.push(Box::new(preprocessor));
222+
self.preprocessors
223+
.insert(preprocessor.name().to_string(), Box::new(preprocessor));
217224
self
218225
}
219226

@@ -258,10 +265,9 @@ impl MDBook {
258265

259266
// Index Preprocessor is disabled so that chapter paths
260267
// continue to point to the actual markdown files.
261-
self.preprocessors = determine_preprocessors(&self.config, &self.root)?
262-
.into_iter()
263-
.filter(|pre| pre.name() != IndexPreprocessor::NAME)
264-
.collect();
268+
self.preprocessors = determine_preprocessors(&self.config, &self.root)?;
269+
self.preprocessors
270+
.shift_remove_entry(IndexPreprocessor::NAME);
265271
let (book, _) = self.preprocess_book(&TestRenderer)?;
266272

267273
let color_output = std::io::stderr().is_terminal();
@@ -399,24 +405,25 @@ struct OutputConfig {
399405
}
400406

401407
/// Look at the `Config` and try to figure out what renderers to use.
402-
fn determine_renderers(config: &Config) -> Result<Vec<Box<dyn Renderer>>> {
403-
let mut renderers = Vec::new();
408+
fn determine_renderers(config: &Config) -> Result<IndexMap<String, Box<dyn Renderer>>> {
409+
let mut renderers = IndexMap::new();
404410

405411
let outputs = config.outputs::<OutputConfig>()?;
406412
renderers.extend(outputs.into_iter().map(|(key, table)| {
407-
if key == "html" {
413+
let renderer = if key == "html" {
408414
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
409415
} else if key == "markdown" {
410416
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
411417
} else {
412418
let command = table.command.unwrap_or_else(|| format!("mdbook-{key}"));
413-
Box::new(CmdRenderer::new(key, command))
414-
}
419+
Box::new(CmdRenderer::new(key.clone(), command))
420+
};
421+
(key, renderer)
415422
}));
416423

417424
// if we couldn't find anything, add the HTML renderer as a default
418425
if renderers.is_empty() {
419-
renderers.push(Box::new(HtmlHandlebars::new()));
426+
renderers.insert("html".to_string(), Box::new(HtmlHandlebars::new()));
420427
}
421428

422429
Ok(renderers)
@@ -442,7 +449,10 @@ struct PreprocessorConfig {
442449
}
443450

444451
/// Look at the `MDBook` and try to figure out what preprocessors to run.
445-
fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn Preprocessor>>> {
452+
fn determine_preprocessors(
453+
config: &Config,
454+
root: &Path,
455+
) -> Result<IndexMap<String, Box<dyn Preprocessor>>> {
446456
// Collect the names of all preprocessors intended to be run, and the order
447457
// in which they should be run.
448458
let mut preprocessor_names = TopologicalSort::<String>::new();
@@ -490,7 +500,7 @@ fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn P
490500
}
491501

492502
// Now that all links have been established, queue preprocessors in a suitable order
493-
let mut preprocessors = Vec::with_capacity(preprocessor_names.len());
503+
let mut preprocessors = IndexMap::with_capacity(preprocessor_names.len());
494504
// `pop_all()` returns an empty vector when no more items are not being depended upon
495505
for mut names in std::iter::repeat_with(|| preprocessor_names.pop_all())
496506
.take_while(|names| !names.is_empty())
@@ -516,14 +526,14 @@ fn determine_preprocessors(config: &Config, root: &Path) -> Result<Vec<Box<dyn P
516526
.to_owned()
517527
.unwrap_or_else(|| format!("mdbook-{name}"));
518528
Box::new(CmdPreprocessor::new(
519-
name,
529+
name.clone(),
520530
command,
521531
root.to_owned(),
522532
table.optional,
523533
))
524534
}
525535
};
526-
preprocessors.push(preprocessor);
536+
preprocessors.insert(name, preprocessor);
527537
}
528538
}
529539

crates/mdbook-driver/src/mdbook/tests.rs

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ fn can_determine_third_party_preprocessors() {
8585

8686
let got = determine_preprocessors(&cfg, Path::new("")).unwrap();
8787

88-
assert!(got.into_iter().any(|p| p.name() == "random"));
88+
assert!(got.contains_key("random"));
8989
}
9090

9191
#[test]
@@ -143,19 +143,12 @@ fn preprocessor_order_is_honored() {
143143
let cfg = Config::from_str(cfg_str).unwrap();
144144

145145
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
146-
let index = |name| {
147-
preprocessors
148-
.iter()
149-
.enumerate()
150-
.find(|(_, preprocessor)| preprocessor.name() == name)
151-
.unwrap()
152-
.0
153-
};
146+
let index = |name| preprocessors.get_index_of(name).unwrap();
154147
let assert_before = |before, after| {
155148
if index(before) >= index(after) {
156149
eprintln!("Preprocessor order:");
157-
for preprocessor in &preprocessors {
158-
eprintln!(" {}", preprocessor.name());
150+
for preprocessor in preprocessors.keys() {
151+
eprintln!(" {}", preprocessor);
159152
}
160153
panic!("{before} should come before {after}");
161154
}
@@ -193,11 +186,8 @@ fn dependencies_dont_register_undefined_preprocessors() {
193186

194187
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
195188

196-
assert!(
197-
!preprocessors
198-
.iter()
199-
.any(|preprocessor| preprocessor.name() == "random")
200-
);
189+
// Does not contain "random"
190+
assert_eq!(preprocessors.keys().collect::<Vec<_>>(), ["index", "links"]);
201191
}
202192

203193
#[test]
@@ -214,11 +204,8 @@ fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
214204

215205
let preprocessors = determine_preprocessors(&cfg, Path::new("")).unwrap();
216206

217-
assert!(
218-
!preprocessors
219-
.iter()
220-
.any(|preprocessor| preprocessor.name() == "links")
221-
);
207+
// Does not contain "links"
208+
assert_eq!(preprocessors.keys().collect::<Vec<_>>(), ["random"]);
222209
}
223210

224211
#[test]

tests/testsuite/preprocessor.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,9 @@ fn with_preprocessor_same_name() {
193193
let spy: Arc<Mutex<Inner>> = Default::default();
194194
let mut book = test.load_book();
195195
book.with_preprocessor(Spy(Arc::clone(&spy)));
196-
let err = book.build().unwrap_err();
197-
test.assert.eq(
198-
format!("{err:?}"),
199-
str![[r#"
200-
Unable to run the preprocessor `dummy`
201-
202-
Caused by:
203-
[NOT_FOUND]
204-
"#]],
205-
);
196+
// Unfortunately this is unable to capture the output when using the API.
197+
book.build().unwrap();
198+
let inner = spy.lock().unwrap();
199+
assert_eq!(inner.run_count, 1);
200+
assert_eq!(inner.rendered_with, ["html"]);
206201
}

tests/testsuite/renderer.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -254,15 +254,8 @@ fn with_renderer_same_name() {
254254
let spy: Arc<Mutex<Inner>> = Default::default();
255255
let mut book = test.load_book();
256256
book.with_renderer(Spy(Arc::clone(&spy)));
257-
let err = book.build().unwrap_err();
258-
test.assert.eq(
259-
format!("{err:?}"),
260-
str![[r#"
261-
Rendering failed
262-
263-
Caused by:
264-
0: Unable to run the backend `dummy`
265-
1: [NOT_FOUND]
266-
"#]],
267-
);
257+
// Unfortunately this is unable to capture the output when using the API.
258+
book.build().unwrap();
259+
let inner = spy.lock().unwrap();
260+
assert_eq!(inner.run_count, 1);
268261
}

0 commit comments

Comments
 (0)