2025-07-21 21:42:58 -07:00
|
|
|
//! The high-level interface for loading and rendering books.
|
|
|
|
|
|
|
|
|
|
use crate::builtin_preprocessors::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor};
|
|
|
|
|
use crate::builtin_renderers::{CmdRenderer, MarkdownRenderer};
|
|
|
|
|
use crate::init::BookBuilder;
|
|
|
|
|
use crate::load::{load_book, load_book_from_disk};
|
2025-07-21 11:37:46 -07:00
|
|
|
use anyhow::{Context, Error, Result, bail};
|
2025-08-18 11:14:29 -07:00
|
|
|
use indexmap::IndexMap;
|
2025-07-21 21:42:58 -07:00
|
|
|
use mdbook_core::book::{Book, BookItem, BookItems};
|
2025-07-21 13:26:57 -07:00
|
|
|
use mdbook_core::config::{Config, RustEdition};
|
2025-09-20 17:05:33 -07:00
|
|
|
use mdbook_core::utils::fs;
|
2025-07-21 20:45:14 -07:00
|
|
|
use mdbook_html::HtmlHandlebars;
|
2025-07-21 15:19:18 -07:00
|
|
|
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
|
2025-07-21 15:30:51 -07:00
|
|
|
use mdbook_renderer::{RenderContext, Renderer};
|
2025-07-21 21:42:58 -07:00
|
|
|
use mdbook_summary::Summary;
|
2025-07-25 11:28:52 -07:00
|
|
|
use serde::Deserialize;
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
use std::ffi::OsString;
|
2025-09-20 17:05:33 -07:00
|
|
|
use std::io::IsTerminal;
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
use std::path::{Path, PathBuf};
|
2016-04-26 23:04:27 +02:00
|
|
|
use std::process::Command;
|
2018-03-27 01:47:37 +02:00
|
|
|
use tempfile::Builder as TempFileBuilder;
|
2021-07-21 17:38:59 +02:00
|
|
|
use topological_sort::TopologicalSort;
|
2025-12-10 13:57:22 -08:00
|
|
|
use tracing::{debug, info, trace, warn};
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2025-07-21 21:42:58 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests;
|
|
|
|
|
|
2017-12-11 11:24:43 +11:00
|
|
|
/// The object used to manage and build a book.
|
2017-05-18 23:52:38 +02:00
|
|
|
pub struct MDBook {
|
2017-12-11 11:24:43 +11:00
|
|
|
/// The book's root directory.
|
2017-09-30 21:13:00 +08:00
|
|
|
pub root: PathBuf,
|
2025-08-18 11:14:29 -07:00
|
|
|
|
2017-12-11 11:24:43 +11:00
|
|
|
/// The configuration used to tweak now a book is built.
|
2017-09-30 21:36:03 +08:00
|
|
|
pub config: Config,
|
2025-08-18 11:14:29 -07:00
|
|
|
|
2018-01-07 22:10:48 +08:00
|
|
|
/// A representation of the book's contents in memory.
|
|
|
|
|
pub book: Book,
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2025-08-18 11:14:29 -07:00
|
|
|
/// Renderers to execute.
|
|
|
|
|
renderers: IndexMap<String, Box<dyn Renderer>>,
|
|
|
|
|
|
|
|
|
|
/// Pre-processors to be run on the book.
|
|
|
|
|
preprocessors: IndexMap<String, Box<dyn Preprocessor>>,
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MDBook {
|
2017-11-18 20:01:50 +08:00
|
|
|
/// Load a book from its root directory on disk.
|
|
|
|
|
pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
|
|
|
|
|
let book_root = book_root.into();
|
|
|
|
|
let config_location = book_root.join("book.toml");
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2018-01-14 02:38:43 +08:00
|
|
|
let mut config = if config_location.exists() {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Loading config from {}", config_location.display());
|
2017-11-18 20:01:50 +08:00
|
|
|
Config::from_disk(&config_location)?
|
|
|
|
|
} else {
|
|
|
|
|
Config::default()
|
|
|
|
|
};
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2025-11-17 14:38:58 -08:00
|
|
|
config.update_from_env()?;
|
2018-01-14 02:38:43 +08:00
|
|
|
|
2025-09-12 06:13:45 -07:00
|
|
|
if tracing::enabled!(tracing::Level::TRACE) {
|
2024-09-21 15:53:59 -07:00
|
|
|
for line in format!("Config: {config:#?}").lines() {
|
2017-12-11 11:42:36 +11:00
|
|
|
trace!("{}", line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 10:32:35 +11:00
|
|
|
MDBook::load_with_config(book_root, config)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-10 14:51:30 -08:00
|
|
|
/// Load a book from its root directory using a custom `Config`.
|
2017-12-11 10:32:35 +11:00
|
|
|
pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
|
2018-01-07 22:10:48 +08:00
|
|
|
let root = book_root.into();
|
2017-12-11 10:32:35 +11:00
|
|
|
|
2018-01-07 22:10:48 +08:00
|
|
|
let src_dir = root.join(&config.book.src);
|
2025-07-21 21:42:58 -07:00
|
|
|
let book = load_book(src_dir, &config.build)?;
|
2018-01-07 22:10:48 +08:00
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
let renderers = determine_renderers(&config)?;
|
2025-08-16 12:25:54 -07:00
|
|
|
let preprocessors = determine_preprocessors(&config, &root)?;
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2018-01-07 20:05:57 +00:00
|
|
|
Ok(MDBook {
|
2018-01-07 22:10:48 +08:00
|
|
|
root,
|
|
|
|
|
config,
|
|
|
|
|
book,
|
|
|
|
|
renderers,
|
2018-01-07 20:05:57 +00:00
|
|
|
preprocessors,
|
|
|
|
|
})
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-10 14:51:30 -08:00
|
|
|
/// Load a book from its root directory using a custom `Config` and a custom summary.
|
2019-03-04 11:44:00 -08:00
|
|
|
pub fn load_with_config_and_summary<P: Into<PathBuf>>(
|
|
|
|
|
book_root: P,
|
|
|
|
|
config: Config,
|
2019-05-05 21:57:43 +07:00
|
|
|
summary: Summary,
|
2019-03-04 11:44:00 -08:00
|
|
|
) -> Result<MDBook> {
|
|
|
|
|
let root = book_root.into();
|
|
|
|
|
|
|
|
|
|
let src_dir = root.join(&config.book.src);
|
2025-07-21 21:42:58 -07:00
|
|
|
let book = load_book_from_disk(&summary, src_dir)?;
|
2019-03-04 11:44:00 -08:00
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
let renderers = determine_renderers(&config)?;
|
2025-08-16 12:25:54 -07:00
|
|
|
let preprocessors = determine_preprocessors(&config, &root)?;
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2018-01-07 20:05:57 +00:00
|
|
|
Ok(MDBook {
|
2018-01-07 22:10:48 +08:00
|
|
|
root,
|
|
|
|
|
config,
|
|
|
|
|
book,
|
|
|
|
|
renderers,
|
2018-01-07 20:05:57 +00:00
|
|
|
preprocessors,
|
|
|
|
|
})
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-22 11:22:31 -07:00
|
|
|
/// Returns a flat depth-first iterator over the [`BookItem`]s of the book.
|
2016-04-26 23:04:27 +02:00
|
|
|
///
|
|
|
|
|
/// ```no_run
|
2025-07-21 21:42:58 -07:00
|
|
|
/// # use mdbook_driver::MDBook;
|
2025-07-22 11:17:07 -07:00
|
|
|
/// # use mdbook_driver::book::BookItem;
|
2017-11-18 22:16:35 +08:00
|
|
|
/// # let book = MDBook::load("mybook").unwrap();
|
2016-04-26 23:04:27 +02:00
|
|
|
/// for item in book.iter() {
|
2017-11-18 20:01:50 +08:00
|
|
|
/// match *item {
|
|
|
|
|
/// BookItem::Chapter(ref chapter) => {},
|
2017-11-18 22:16:35 +08:00
|
|
|
/// BookItem::Separator => {},
|
2020-03-20 21:18:07 -05:00
|
|
|
/// BookItem::PartTitle(ref title) => {}
|
2025-08-09 16:38:22 -07:00
|
|
|
/// _ => {}
|
2016-04-26 23:04:27 +02:00
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // would print something like this:
|
|
|
|
|
/// // 1. Chapter 1
|
|
|
|
|
/// // 1.1 Sub Chapter
|
|
|
|
|
/// // 1.2 Sub Chapter
|
|
|
|
|
/// // 2. Chapter 2
|
|
|
|
|
/// //
|
|
|
|
|
/// // etc.
|
|
|
|
|
/// ```
|
2019-05-07 03:50:34 +07:00
|
|
|
pub fn iter(&self) -> BookItems<'_> {
|
2017-11-18 20:01:50 +08:00
|
|
|
self.book.iter()
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
|
2017-11-18 20:41:04 +08:00
|
|
|
/// `init()` gives you a `BookBuilder` which you can use to setup a new book
|
|
|
|
|
/// and its accompanying directory structure.
|
|
|
|
|
///
|
|
|
|
|
/// The `BookBuilder` creates some boilerplate files and directories to get
|
|
|
|
|
/// you started with your book.
|
2016-04-26 23:04:27 +02:00
|
|
|
///
|
|
|
|
|
/// ```text
|
|
|
|
|
/// book-test/
|
|
|
|
|
/// ├── book
|
|
|
|
|
/// └── src
|
|
|
|
|
/// ├── chapter_1.md
|
|
|
|
|
/// └── SUMMARY.md
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
2017-11-18 20:41:04 +08:00
|
|
|
/// It uses the path provided as the root directory for your book, then adds
|
|
|
|
|
/// in a `src/` directory containing a `SUMMARY.md` and `chapter_1.md` file
|
|
|
|
|
/// to get you started.
|
|
|
|
|
pub fn init<P: Into<PathBuf>>(book_root: P) -> BookBuilder {
|
|
|
|
|
BookBuilder::new(book_root)
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
|
2017-11-18 22:07:08 +08:00
|
|
|
/// Tells the renderer to build our book and put it in the build directory.
|
2018-01-07 22:10:48 +08:00
|
|
|
pub fn build(&self) -> Result<()> {
|
2018-01-23 01:28:37 +08:00
|
|
|
info!("Book building has started");
|
2016-04-26 23:04:27 +02:00
|
|
|
|
2025-08-18 11:14:29 -07:00
|
|
|
for renderer in self.renderers.values() {
|
2018-09-10 18:55:58 +08:00
|
|
|
self.execute_build_process(&**renderer)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-15 11:44:46 +01:00
|
|
|
/// Run preprocessors and return the final book.
|
|
|
|
|
pub fn preprocess_book(&self, renderer: &dyn Renderer) -> Result<(Book, PreprocessorContext)> {
|
2018-12-04 00:10:09 +01:00
|
|
|
let preprocess_ctx = PreprocessorContext::new(
|
|
|
|
|
self.root.clone(),
|
|
|
|
|
self.config.clone(),
|
|
|
|
|
renderer.name().to_string(),
|
|
|
|
|
);
|
2023-01-15 11:44:46 +01:00
|
|
|
let mut preprocessed_book = self.book.clone();
|
2025-08-18 11:14:29 -07:00
|
|
|
for preprocessor in self.preprocessors.values() {
|
2025-07-25 11:28:52 -07:00
|
|
|
if preprocessor_should_run(&**preprocessor, renderer, &self.config)? {
|
2018-09-10 18:55:58 +08:00
|
|
|
debug!("Running the {} preprocessor.", preprocessor.name());
|
2018-12-04 00:10:09 +01:00
|
|
|
preprocessed_book = preprocessor.run(&preprocess_ctx, preprocessed_book)?;
|
2018-09-10 18:55:58 +08:00
|
|
|
}
|
2018-01-07 19:08:31 +00:00
|
|
|
}
|
2023-01-15 11:44:46 +01:00
|
|
|
Ok((preprocessed_book, preprocess_ctx))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run the entire build process for a particular [`Renderer`].
|
|
|
|
|
pub fn execute_build_process(&self, renderer: &dyn Renderer) -> Result<()> {
|
|
|
|
|
let (preprocessed_book, preprocess_ctx) = self.preprocess_book(renderer)?;
|
2018-01-07 19:08:31 +00:00
|
|
|
|
2018-01-07 22:10:48 +08:00
|
|
|
let name = renderer.name();
|
|
|
|
|
let build_dir = self.build_dir_for(name);
|
2017-05-18 23:52:38 +02:00
|
|
|
|
2021-03-23 18:36:45 -07:00
|
|
|
let mut render_context = RenderContext::new(
|
2018-01-07 22:10:48 +08:00
|
|
|
self.root.clone(),
|
2021-03-23 18:36:45 -07:00
|
|
|
preprocessed_book,
|
2018-01-07 22:10:48 +08:00
|
|
|
self.config.clone(),
|
|
|
|
|
build_dir,
|
|
|
|
|
);
|
2021-03-23 18:36:45 -07:00
|
|
|
render_context
|
|
|
|
|
.chapter_titles
|
|
|
|
|
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
|
2018-01-07 22:10:48 +08:00
|
|
|
|
2021-03-23 18:36:45 -07:00
|
|
|
info!("Running the {} backend", renderer.name());
|
2018-01-07 22:10:48 +08:00
|
|
|
renderer
|
|
|
|
|
.render(&render_context)
|
2020-05-20 14:32:00 -07:00
|
|
|
.with_context(|| "Rendering failed")
|
2016-12-31 22:27:38 -08:00
|
|
|
}
|
|
|
|
|
|
2017-11-18 22:16:35 +08:00
|
|
|
/// You can change the default renderer to another one by using this method.
|
2021-01-10 14:51:30 -08:00
|
|
|
/// The only requirement is that your renderer implement the [`Renderer`]
|
|
|
|
|
/// trait.
|
2018-01-07 22:10:48 +08:00
|
|
|
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
|
2025-08-18 11:14:29 -07:00
|
|
|
self.renderers
|
|
|
|
|
.insert(renderer.name().to_string(), Box::new(renderer));
|
2016-04-26 23:04:27 +02:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-10 14:51:30 -08:00
|
|
|
/// Register a [`Preprocessor`] to be used when rendering the book.
|
2019-05-03 19:59:58 +02:00
|
|
|
pub fn with_preprocessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
|
2025-08-18 11:14:29 -07:00
|
|
|
self.preprocessors
|
|
|
|
|
.insert(preprocessor.name().to_string(), Box::new(preprocessor));
|
2018-01-07 15:24:37 +00:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 11:24:43 +11:00
|
|
|
/// Run `rustdoc` tests on the book, linking against the provided libraries.
|
2017-06-19 22:06:15 +08:00
|
|
|
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
|
2022-08-25 19:13:51 -07:00
|
|
|
// test_chapter with chapter:None will run all tests.
|
|
|
|
|
self.test_chapter(library_paths, None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run `rustdoc` tests on a specific chapter of the book, linking against the provided libraries.
|
|
|
|
|
/// If `chapter` is `None`, all tests will be run.
|
|
|
|
|
pub fn test_chapter(&mut self, library_paths: Vec<&str>, chapter: Option<&str>) -> Result<()> {
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
let cwd = std::env::current_dir()?;
|
|
|
|
|
let library_args: Vec<OsString> = library_paths
|
|
|
|
|
.into_iter()
|
|
|
|
|
.flat_map(|path| {
|
|
|
|
|
let path = Path::new(path);
|
|
|
|
|
let path = if path.is_relative() {
|
|
|
|
|
cwd.join(path).into_os_string()
|
|
|
|
|
} else {
|
|
|
|
|
path.to_path_buf().into_os_string()
|
|
|
|
|
};
|
|
|
|
|
[OsString::from("-L"), path]
|
|
|
|
|
})
|
2017-11-18 22:59:45 +08:00
|
|
|
.collect();
|
2017-12-10 23:13:46 +11:00
|
|
|
|
2018-07-25 12:17:17 -05:00
|
|
|
let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?;
|
2017-12-10 23:13:46 +11:00
|
|
|
|
2022-08-25 19:13:51 -07:00
|
|
|
let mut chapter_found = false;
|
|
|
|
|
|
2023-01-15 11:44:46 +01:00
|
|
|
struct TestRenderer;
|
|
|
|
|
impl Renderer for TestRenderer {
|
|
|
|
|
// FIXME: Is "test" the proper renderer name to use here?
|
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
|
"test"
|
|
|
|
|
}
|
2018-01-07 16:21:46 +00:00
|
|
|
|
2023-01-15 11:44:46 +01:00
|
|
|
fn render(&self, _: &RenderContext) -> Result<()> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (book, _) = self.preprocess_book(&TestRenderer)?;
|
2018-01-07 16:21:46 +00:00
|
|
|
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
let color_output = std::io::stderr().is_terminal();
|
2020-09-03 18:36:51 -07:00
|
|
|
let mut failed = false;
|
2018-09-10 18:55:58 +08:00
|
|
|
for item in book.iter() {
|
2017-11-18 20:01:50 +08:00
|
|
|
if let BookItem::Chapter(ref ch) = *item {
|
2020-03-09 22:34:28 +01:00
|
|
|
let chapter_path = match ch.path {
|
|
|
|
|
Some(ref path) if !path.as_os_str().is_empty() => path,
|
|
|
|
|
_ => continue,
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-25 19:13:51 -07:00
|
|
|
if let Some(chapter) = chapter {
|
|
|
|
|
if ch.name != chapter && chapter_path.to_str() != Some(chapter) {
|
|
|
|
|
if chapter == "?" {
|
|
|
|
|
info!("Skipping chapter '{}'...", ch.name);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
chapter_found = true;
|
|
|
|
|
info!("Testing chapter '{}': {:?}", ch.name, chapter_path);
|
2020-03-09 22:34:28 +01:00
|
|
|
|
|
|
|
|
// write preprocessed file to tempdir
|
2023-05-13 09:44:11 -07:00
|
|
|
let path = temp_dir.path().join(chapter_path);
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(&path, &ch.content)?;
|
2020-03-09 22:34:28 +01:00
|
|
|
|
|
|
|
|
let mut cmd = Command::new("rustdoc");
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
cmd.current_dir(temp_dir.path())
|
2024-05-13 12:13:50 -07:00
|
|
|
.arg(chapter_path)
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
.arg("--test")
|
|
|
|
|
.args(&library_args);
|
2020-03-09 22:34:28 +01:00
|
|
|
|
|
|
|
|
if let Some(edition) = self.config.rust.edition {
|
|
|
|
|
match edition {
|
|
|
|
|
RustEdition::E2015 => {
|
2023-05-13 09:44:11 -07:00
|
|
|
cmd.args(["--edition", "2015"]);
|
2019-11-17 21:36:10 +03:00
|
|
|
}
|
2020-03-09 22:34:28 +01:00
|
|
|
RustEdition::E2018 => {
|
2023-05-13 09:44:11 -07:00
|
|
|
cmd.args(["--edition", "2018"]);
|
2020-02-29 17:55:45 +01:00
|
|
|
}
|
2021-07-04 14:44:23 -07:00
|
|
|
RustEdition::E2021 => {
|
2023-05-13 09:44:11 -07:00
|
|
|
cmd.args(["--edition", "2021"]);
|
2021-07-04 14:44:23 -07:00
|
|
|
}
|
2024-06-12 15:53:56 -07:00
|
|
|
RustEdition::E2024 => {
|
2024-11-23 15:25:29 -08:00
|
|
|
cmd.args(["--edition", "2024"]);
|
2024-06-12 15:53:56 -07:00
|
|
|
}
|
2025-08-09 16:38:22 -07:00
|
|
|
_ => panic!("RustEdition {edition:?} not covered"),
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
2017-02-15 22:01:26 -05:00
|
|
|
}
|
2020-03-09 22:34:28 +01:00
|
|
|
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
if color_output {
|
2024-05-13 12:13:50 -07:00
|
|
|
cmd.args(["--color", "always"]);
|
Color test output and shorten chapter paths
Currently, the output from `rustdoc --test` is not colored because
`rustdoc`'s stdout is not a tty. The output of a failed `rustdoc` run is
sent to `mdbook`'s stderr via the `error!()` macro. This commit checks
if stderr is a tty using the standard `.is_terminal()` and if so, passes
`--color always` to `rustdoc`.
The test output from `rustdoc` includes the full path that `rustdoc` was
called with. This obscures the path of the file with the error. E.g.,
```
---- /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> /var/folders/9v/90bm7kb10fx3_bprxltb3t1r0000gn/T/mdbook-tnGJxp/lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
This commit runs `rustdoc` in the temp directory and replaces any
relative library paths with absolute library paths. This leads to
simpler error messages. The one above becomes
```
---- lab0/index.md - Lab_0__Getting_Started (line 3) stdout ----
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `code`
--> lab0/index.md:4:6
|
3 | this code has a bug
| ^^^^ expected one of 8 possible tokens
error: aborting due to previous error
```
(with colors, of course).
2023-12-06 11:31:39 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 23:16:31 +08:00
|
|
|
debug!("running {:?}", cmd);
|
2025-02-03 11:02:53 -08:00
|
|
|
let output = cmd
|
|
|
|
|
.output()
|
|
|
|
|
.with_context(|| "failed to execute `rustdoc`")?;
|
2020-03-09 22:34:28 +01:00
|
|
|
|
|
|
|
|
if !output.status.success() {
|
2020-09-03 18:36:51 -07:00
|
|
|
failed = true;
|
2025-12-10 13:57:22 -08:00
|
|
|
eprintln!(
|
|
|
|
|
"ERROR rustdoc returned an error:\n\
|
2020-05-20 14:32:00 -07:00
|
|
|
\n--- stdout\n{}\n--- stderr\n{}",
|
|
|
|
|
String::from_utf8_lossy(&output.stdout),
|
|
|
|
|
String::from_utf8_lossy(&output.stderr)
|
|
|
|
|
);
|
2020-03-09 22:34:28 +01:00
|
|
|
}
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-09-03 18:36:51 -07:00
|
|
|
if failed {
|
2020-09-06 09:11:53 -07:00
|
|
|
bail!("One or more tests failed");
|
2020-09-03 18:36:51 -07:00
|
|
|
}
|
2022-08-25 19:13:51 -07:00
|
|
|
if let Some(chapter) = chapter {
|
|
|
|
|
if !chapter_found {
|
|
|
|
|
bail!("Chapter not found: {}", chapter);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-26 23:04:27 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-07 22:10:48 +08:00
|
|
|
/// The logic for determining where a backend should put its build
|
|
|
|
|
/// artefacts.
|
|
|
|
|
///
|
|
|
|
|
/// If there is only 1 renderer, put it in the directory pointed to by the
|
2021-01-10 14:51:30 -08:00
|
|
|
/// `build.build_dir` key in [`Config`]. If there is more than one then the
|
2018-01-07 22:10:48 +08:00
|
|
|
/// renderer gets its own directory within the main build dir.
|
|
|
|
|
///
|
|
|
|
|
/// i.e. If there were only one renderer (in this case, the HTML renderer):
|
|
|
|
|
///
|
|
|
|
|
/// - build/
|
|
|
|
|
/// - index.html
|
|
|
|
|
/// - ...
|
|
|
|
|
///
|
|
|
|
|
/// Otherwise if there are multiple:
|
|
|
|
|
///
|
|
|
|
|
/// - build/
|
|
|
|
|
/// - epub/
|
|
|
|
|
/// - my_awesome_book.epub
|
|
|
|
|
/// - html/
|
|
|
|
|
/// - index.html
|
|
|
|
|
/// - ...
|
|
|
|
|
/// - latex/
|
|
|
|
|
/// - my_awesome_book.tex
|
|
|
|
|
///
|
|
|
|
|
pub fn build_dir_for(&self, backend_name: &str) -> PathBuf {
|
|
|
|
|
let build_dir = self.root.join(&self.config.build.build_dir);
|
|
|
|
|
|
|
|
|
|
if self.renderers.len() <= 1 {
|
|
|
|
|
build_dir
|
|
|
|
|
} else {
|
|
|
|
|
build_dir.join(backend_name)
|
|
|
|
|
}
|
2017-05-20 13:56:01 +02:00
|
|
|
}
|
|
|
|
|
|
2017-12-11 11:24:43 +11:00
|
|
|
/// Get the directory containing this book's source files.
|
|
|
|
|
pub fn source_dir(&self) -> PathBuf {
|
2017-09-30 21:13:00 +08:00
|
|
|
self.root.join(&self.config.book.src)
|
2017-05-20 13:56:01 +02:00
|
|
|
}
|
|
|
|
|
|
2018-03-15 02:27:56 +11:00
|
|
|
/// Get the directory containing the theme resources for the book.
|
2017-09-30 21:36:03 +08:00
|
|
|
pub fn theme_dir(&self) -> PathBuf {
|
2018-03-15 02:27:56 +11:00
|
|
|
self.config
|
|
|
|
|
.html_config()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.theme_dir(&self.root)
|
2016-04-26 23:04:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
2018-01-07 22:10:48 +08:00
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
/// An `output` table.
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct OutputConfig {
|
|
|
|
|
command: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-07 22:10:48 +08:00
|
|
|
/// Look at the `Config` and try to figure out what renderers to use.
|
2025-08-18 11:14:29 -07:00
|
|
|
fn determine_renderers(config: &Config) -> Result<IndexMap<String, Box<dyn Renderer>>> {
|
|
|
|
|
let mut renderers = IndexMap::new();
|
2018-01-07 22:10:48 +08:00
|
|
|
|
2025-08-12 15:00:53 -07:00
|
|
|
let outputs = config.outputs::<OutputConfig>()?;
|
|
|
|
|
renderers.extend(outputs.into_iter().map(|(key, table)| {
|
2025-08-18 11:14:29 -07:00
|
|
|
let renderer = if key == "html" {
|
2025-08-12 15:00:53 -07:00
|
|
|
Box::new(HtmlHandlebars::new()) as Box<dyn Renderer>
|
|
|
|
|
} else if key == "markdown" {
|
|
|
|
|
Box::new(MarkdownRenderer::new()) as Box<dyn Renderer>
|
|
|
|
|
} else {
|
|
|
|
|
let command = table.command.unwrap_or_else(|| format!("mdbook-{key}"));
|
2025-08-18 11:14:29 -07:00
|
|
|
Box::new(CmdRenderer::new(key.clone(), command))
|
|
|
|
|
};
|
|
|
|
|
(key, renderer)
|
2025-08-12 15:00:53 -07:00
|
|
|
}));
|
2018-01-07 22:10:48 +08:00
|
|
|
|
|
|
|
|
// if we couldn't find anything, add the HTML renderer as a default
|
|
|
|
|
if renderers.is_empty() {
|
2025-08-18 11:14:29 -07:00
|
|
|
renderers.insert("html".to_string(), Box::new(HtmlHandlebars::new()));
|
2018-01-07 22:10:48 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
Ok(renderers)
|
2018-01-07 22:10:48 +08:00
|
|
|
}
|
|
|
|
|
|
2022-06-26 11:37:52 +02:00
|
|
|
const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"];
|
2018-01-16 22:25:51 +00:00
|
|
|
|
2019-05-07 03:50:34 +07:00
|
|
|
fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool {
|
2018-09-10 18:55:58 +08:00
|
|
|
let name = pre.name();
|
|
|
|
|
name == LinkPreprocessor::NAME || name == IndexPreprocessor::NAME
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
/// A `preprocessor` table.
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct PreprocessorConfig {
|
|
|
|
|
command: Option<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
before: Vec<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
after: Vec<String>,
|
2025-08-16 13:39:54 -07:00
|
|
|
#[serde(default)]
|
|
|
|
|
optional: bool,
|
2025-07-25 11:28:52 -07:00
|
|
|
}
|
|
|
|
|
|
2018-01-07 16:40:17 +00:00
|
|
|
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
2025-08-18 11:14:29 -07:00
|
|
|
fn determine_preprocessors(
|
|
|
|
|
config: &Config,
|
|
|
|
|
root: &Path,
|
|
|
|
|
) -> Result<IndexMap<String, Box<dyn Preprocessor>>> {
|
2021-07-21 17:38:59 +02:00
|
|
|
// Collect the names of all preprocessors intended to be run, and the order
|
|
|
|
|
// in which they should be run.
|
|
|
|
|
let mut preprocessor_names = TopologicalSort::<String>::new();
|
2018-09-19 23:13:25 +08:00
|
|
|
|
|
|
|
|
if config.build.use_default_preprocessors {
|
2021-07-21 17:38:59 +02:00
|
|
|
for name in DEFAULT_PREPROCESSORS {
|
|
|
|
|
preprocessor_names.insert(name.to_string());
|
|
|
|
|
}
|
2018-09-19 23:13:25 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-12 15:00:53 -07:00
|
|
|
let preprocessor_table = config.preprocessors::<PreprocessorConfig>()?;
|
2021-07-21 17:38:59 +02:00
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
for (name, table) in preprocessor_table.iter() {
|
|
|
|
|
preprocessor_names.insert(name.to_string());
|
2021-07-21 17:38:59 +02:00
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
let exists = |name| {
|
|
|
|
|
(config.build.use_default_preprocessors && DEFAULT_PREPROCESSORS.contains(&name))
|
|
|
|
|
|| preprocessor_table.contains_key(name)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for after in &table.before {
|
|
|
|
|
if !exists(&after) {
|
|
|
|
|
// Only warn so that preprocessors can be toggled on and off (e.g. for
|
|
|
|
|
// troubleshooting) without having to worry about order too much.
|
|
|
|
|
warn!(
|
|
|
|
|
"preprocessor.{}.after contains \"{}\", which was not found",
|
|
|
|
|
name, after
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
preprocessor_names.add_dependency(name, after);
|
2021-07-21 17:38:59 +02:00
|
|
|
}
|
2025-07-25 11:28:52 -07:00
|
|
|
}
|
2021-07-21 17:38:59 +02:00
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
for before in &table.after {
|
|
|
|
|
if !exists(&before) {
|
|
|
|
|
// See equivalent warning above for rationale
|
|
|
|
|
warn!(
|
|
|
|
|
"preprocessor.{}.before contains \"{}\", which was not found",
|
|
|
|
|
name, before
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
preprocessor_names.add_dependency(before, name);
|
2018-09-19 23:13:25 +08:00
|
|
|
}
|
2018-01-07 16:40:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-21 17:38:59 +02:00
|
|
|
// Now that all links have been established, queue preprocessors in a suitable order
|
2025-08-18 11:14:29 -07:00
|
|
|
let mut preprocessors = IndexMap::with_capacity(preprocessor_names.len());
|
2021-09-26 21:54:14 +02:00
|
|
|
// `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())
|
|
|
|
|
.take_while(|names| !names.is_empty())
|
|
|
|
|
{
|
|
|
|
|
// The `topological_sort` crate does not guarantee a stable order for ties, even across
|
|
|
|
|
// runs of the same program. Thus, we break ties manually by sorting.
|
|
|
|
|
// Careful: `str`'s default sorting, which we are implicitly invoking here, uses code point
|
|
|
|
|
// values ([1]), which may not be an alphabetical sort.
|
|
|
|
|
// As mentioned in [1], doing so depends on locale, which is not desirable for deciding
|
|
|
|
|
// preprocessor execution order.
|
|
|
|
|
// [1]: https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#impl-Ord-14
|
|
|
|
|
names.sort();
|
|
|
|
|
for name in names {
|
|
|
|
|
let preprocessor: Box<dyn Preprocessor> = match name.as_str() {
|
|
|
|
|
"links" => Box::new(LinkPreprocessor::new()),
|
|
|
|
|
"index" => Box::new(IndexPreprocessor::new()),
|
|
|
|
|
_ => {
|
|
|
|
|
// The only way to request a custom preprocessor is through the `preprocessor`
|
|
|
|
|
// table, so it must exist, be a table, and contain the key.
|
2025-07-25 11:28:52 -07:00
|
|
|
let table = &preprocessor_table[&name];
|
|
|
|
|
let command = table
|
|
|
|
|
.command
|
|
|
|
|
.to_owned()
|
|
|
|
|
.unwrap_or_else(|| format!("mdbook-{name}"));
|
2025-08-16 13:39:54 -07:00
|
|
|
Box::new(CmdPreprocessor::new(
|
2025-08-18 11:14:29 -07:00
|
|
|
name.clone(),
|
2025-08-16 13:39:54 -07:00
|
|
|
command,
|
|
|
|
|
root.to_owned(),
|
|
|
|
|
table.optional,
|
|
|
|
|
))
|
2021-09-26 21:54:14 +02:00
|
|
|
}
|
|
|
|
|
};
|
2025-08-18 11:14:29 -07:00
|
|
|
preprocessors.insert(name, preprocessor);
|
2021-09-26 21:54:14 +02:00
|
|
|
}
|
2021-07-21 17:38:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-26 21:54:14 +02:00
|
|
|
// "If `pop_all` returns an empty vector and `len` is not 0, there are cyclic dependencies."
|
|
|
|
|
// Normally, `len() == 0` is equivalent to `is_empty()`, so we'll use that.
|
2021-07-21 17:38:59 +02:00
|
|
|
if preprocessor_names.is_empty() {
|
|
|
|
|
Ok(preprocessors)
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::msg("Cyclic dependency detected in preprocessors"))
|
|
|
|
|
}
|
2018-01-07 16:21:46 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-10 18:55:58 +08:00
|
|
|
/// Check whether we should run a particular `Preprocessor` in combination
|
|
|
|
|
/// with the renderer, falling back to `Preprocessor::supports_renderer()`
|
|
|
|
|
/// method if the user doesn't say anything.
|
|
|
|
|
///
|
|
|
|
|
/// The `build.use-default-preprocessors` config option can be used to ensure
|
|
|
|
|
/// default preprocessors always run if they support the renderer.
|
2019-05-07 03:50:34 +07:00
|
|
|
fn preprocessor_should_run(
|
|
|
|
|
preprocessor: &dyn Preprocessor,
|
|
|
|
|
renderer: &dyn Renderer,
|
|
|
|
|
cfg: &Config,
|
2025-07-25 11:28:52 -07:00
|
|
|
) -> Result<bool> {
|
2018-09-10 18:55:58 +08:00
|
|
|
// default preprocessors should be run by default (if supported)
|
|
|
|
|
if cfg.build.use_default_preprocessors && is_default_preprocessor(preprocessor) {
|
2025-08-16 13:26:01 -07:00
|
|
|
return preprocessor.supports_renderer(renderer.name());
|
2018-09-10 18:55:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let key = format!("preprocessor.{}.renderers", preprocessor.name());
|
|
|
|
|
let renderer_name = renderer.name();
|
|
|
|
|
|
2025-07-25 11:28:52 -07:00
|
|
|
match cfg.get::<Vec<String>>(&key) {
|
|
|
|
|
Ok(Some(explicit_renderers)) => {
|
|
|
|
|
Ok(explicit_renderers.iter().any(|name| name == renderer_name))
|
|
|
|
|
}
|
2025-08-16 13:26:01 -07:00
|
|
|
Ok(None) => preprocessor.supports_renderer(renderer_name),
|
2025-07-25 11:28:52 -07:00
|
|
|
Err(e) => bail!("failed to get `{key}`: {e}"),
|
2018-09-10 18:55:58 +08:00
|
|
|
}
|
|
|
|
|
}
|