Finish move of MDBook to mdbook-driver

This commit is contained in:
Eric Huss 2025-07-21 21:42:58 -07:00
parent 5a31947eb7
commit 40745600a3
30 changed files with 380 additions and 382 deletions

5
Cargo.lock generated
View file

@ -1289,7 +1289,6 @@ dependencies = [
"tempfile",
"tokio",
"toml",
"topological-sort",
"tower-http",
"walkdir",
]
@ -1315,12 +1314,16 @@ dependencies = [
"anyhow",
"log",
"mdbook-core",
"mdbook-html",
"mdbook-preprocessor",
"mdbook-renderer",
"mdbook-summary",
"regex",
"serde_json",
"shlex",
"tempfile",
"toml",
"topological-sort",
]
[[package]]

View file

@ -44,6 +44,7 @@ sha2 = "0.10.9"
shlex = "1.3.0"
tempfile = "3.20.0"
toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037
topological-sort = "0.2.2"
[package]
name = "mdbook"
@ -86,7 +87,6 @@ serde_json.workspace = true
shlex.workspace = true
tempfile.workspace = true
toml.workspace = true
topological-sort = "0.2.2"
# Watch feature
notify = { version = "8.0.0", optional = true }

View file

@ -11,12 +11,16 @@ rust-version.workspace = true
anyhow.workspace = true
log.workspace = true
mdbook-core.workspace = true
mdbook-html.workspace = true
mdbook-preprocessor.workspace = true
mdbook-renderer.workspace = true
mdbook-summary.workspace = true
regex.workspace = true
serde_json.workspace = true
shlex.workspace = true
tempfile.workspace = true
toml.workspace = true
topological-sort.workspace = true
[lints]
workspace = true

View file

@ -170,7 +170,6 @@ impl Preprocessor for CmdPreprocessor {
}
}
#[cfg(false)] // Needs to wait for MDBook transfer
#[cfg(test)]
mod tests {
use super::*;
@ -178,7 +177,7 @@ mod tests {
use std::path::Path;
fn guide() -> MDBook {
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../guide");
MDBook::load(example).unwrap()
}

View file

@ -1,3 +1,5 @@
//! Support for initializing a new book.
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;

View file

@ -2,3 +2,8 @@
pub mod builtin_preprocessors;
pub mod builtin_renderers;
pub mod init;
mod load;
mod mdbook;
pub use mdbook::MDBook;

View file

@ -1,26 +1,18 @@
//! The internal representation of a book and infrastructure for loading it from
//! disk and building it.
//!
//! For examples on using `MDBook`, consult the [top-level documentation][1].
//!
//! [1]: ../index.html
//! The high-level interface for loading and rendering books.
mod book;
mod init;
pub use self::book::load_book;
pub use self::init::BookBuilder;
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};
use anyhow::{Context, Error, Result, bail};
use log::{debug, error, info, log_enabled, trace, warn};
pub use mdbook_core::book::{Book, BookItem, BookItems, Chapter, SectionNumber};
use mdbook_core::book::{Book, BookItem, BookItems};
use mdbook_core::config::{Config, RustEdition};
use mdbook_core::utils;
use mdbook_driver::builtin_preprocessors::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor};
use mdbook_driver::builtin_renderers::{CmdRenderer, MarkdownRenderer};
use mdbook_html::HtmlHandlebars;
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
use mdbook_renderer::{RenderContext, Renderer};
pub use mdbook_summary::{Link, Summary, SummaryItem, parse_summary};
use mdbook_summary::Summary;
use std::ffi::OsString;
use std::io::{IsTerminal, Write};
use std::path::{Path, PathBuf};
@ -29,6 +21,9 @@ use tempfile::Builder as TempFileBuilder;
use toml::Value;
use topological_sort::TopologicalSort;
#[cfg(test)]
mod tests;
/// The object used to manage and build a book.
pub struct MDBook {
/// The book's root directory.
@ -102,7 +97,7 @@ impl MDBook {
let root = book_root.into();
let src_dir = root.join(&config.book.src);
let book = book::load_book(src_dir, &config.build)?;
let book = load_book(src_dir, &config.build)?;
let renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?;
@ -125,7 +120,7 @@ impl MDBook {
let root = book_root.into();
let src_dir = root.join(&config.book.src);
let book = book::load_book_from_disk(&summary, src_dir)?;
let book = load_book_from_disk(&summary, src_dir)?;
let renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?;
@ -144,8 +139,8 @@ impl MDBook {
/// `(section: String, bookitem: &BookItem)`
///
/// ```no_run
/// # use mdbook::MDBook;
/// # use mdbook::book::BookItem;
/// # use mdbook_driver::MDBook;
/// # use mdbook_core::book::BookItem;
/// # let book = MDBook::load("mybook").unwrap();
/// for item in book.iter() {
/// match *item {
@ -618,282 +613,3 @@ fn preprocessor_should_run(
preprocessor.supports_renderer(renderer_name)
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use toml::value::Table;
#[test]
fn config_defaults_to_html_renderer_if_empty() {
let cfg = Config::default();
// make sure we haven't got anything in the `output` table
assert!(cfg.get("output").is_none());
let got = determine_renderers(&cfg);
assert_eq!(got.len(), 1);
assert_eq!(got[0].name(), "html");
}
#[test]
fn add_a_random_renderer_to_the_config() {
let mut cfg = Config::default();
cfg.set("output.random", Table::new()).unwrap();
let got = determine_renderers(&cfg);
assert_eq!(got.len(), 1);
assert_eq!(got[0].name(), "random");
}
#[test]
fn add_a_random_renderer_with_custom_command_to_the_config() {
let mut cfg = Config::default();
let mut table = Table::new();
table.insert("command".to_string(), Value::String("false".to_string()));
cfg.set("output.random", table).unwrap();
let got = determine_renderers(&cfg);
assert_eq!(got.len(), 1);
assert_eq!(got[0].name(), "random");
}
#[test]
fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
let cfg = Config::default();
// make sure we haven't got anything in the `preprocessor` table
assert!(cfg.get("preprocessor").is_none());
let got = determine_preprocessors(&cfg);
assert!(got.is_ok());
assert_eq!(got.as_ref().unwrap().len(), 2);
assert_eq!(got.as_ref().unwrap()[0].name(), "index");
assert_eq!(got.as_ref().unwrap()[1].name(), "links");
}
#[test]
fn use_default_preprocessors_works() {
let mut cfg = Config::default();
cfg.build.use_default_preprocessors = false;
let got = determine_preprocessors(&cfg).unwrap();
assert_eq!(got.len(), 0);
}
#[test]
fn can_determine_third_party_preprocessors() {
let cfg_str = r#"
[book]
title = "Some Book"
[preprocessor.random]
[build]
build-dir = "outputs"
create-missing = false
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure the `preprocessor.random` table exists
assert!(cfg.get_preprocessor("random").is_some());
let got = determine_preprocessors(&cfg).unwrap();
assert!(got.into_iter().any(|p| p.name() == "random"));
}
#[test]
fn preprocessors_can_provide_their_own_commands() {
let cfg_str = r#"
[preprocessor.random]
command = "python random.py"
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure the `preprocessor.random` table exists
let random = cfg.get_preprocessor("random").unwrap();
let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
assert_eq!(random, "python random.py");
}
#[test]
fn preprocessor_before_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
before = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_after_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
after = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_order_is_honored() {
let cfg_str = r#"
[preprocessor.random]
before = [ "last" ]
after = [ "index" ]
[preprocessor.last]
after = [ "links", "index" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
let index = |name| {
preprocessors
.iter()
.enumerate()
.find(|(_, preprocessor)| preprocessor.name() == name)
.unwrap()
.0
};
let assert_before = |before, after| {
if index(before) >= index(after) {
eprintln!("Preprocessor order:");
for preprocessor in &preprocessors {
eprintln!(" {}", preprocessor.name());
}
panic!("{before} should come before {after}");
}
};
assert_before("index", "random");
assert_before("index", "last");
assert_before("random", "last");
assert_before("links", "last");
}
#[test]
fn cyclic_dependencies_are_detected() {
let cfg_str = r#"
[preprocessor.links]
before = [ "index" ]
[preprocessor.index]
before = [ "links" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn dependencies_dont_register_undefined_preprocessors() {
let cfg_str = r#"
[preprocessor.links]
before = [ "random" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(
!preprocessors
.iter()
.any(|preprocessor| preprocessor.name() == "random")
);
}
#[test]
fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
let cfg_str = r#"
[preprocessor.random]
before = [ "links" ]
[build]
use-default-preprocessors = false
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(
!preprocessors
.iter()
.any(|preprocessor| preprocessor.name() == "links")
);
}
#[test]
fn config_respects_preprocessor_selection() {
let cfg_str = r#"
[preprocessor.links]
renderers = ["html"]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// double-check that we can access preprocessor.links.renderers[0]
let html = cfg
.get_preprocessor("links")
.and_then(|links| links.get("renderers"))
.and_then(Value::as_array)
.and_then(|renderers| renderers.get(0))
.and_then(Value::as_str)
.unwrap();
assert_eq!(html, "html");
let html_renderer = HtmlHandlebars;
let pre = LinkPreprocessor::new();
let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg);
assert!(should_run);
}
struct BoolPreprocessor(bool);
impl Preprocessor for BoolPreprocessor {
fn name(&self) -> &str {
"bool-preprocessor"
}
fn run(&self, _ctx: &PreprocessorContext, _book: Book) -> Result<Book> {
unimplemented!()
}
fn supports_renderer(&self, _renderer: &str) -> bool {
self.0
}
}
#[test]
fn preprocessor_should_run_falls_back_to_supports_renderer_method() {
let cfg = Config::default();
let html = HtmlHandlebars::new();
let should_be = true;
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
assert_eq!(got, should_be);
let should_be = false;
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
assert_eq!(got, should_be);
}
}

View file

@ -0,0 +1,275 @@
use super::*;
use std::str::FromStr;
use toml::value::Table;
#[test]
fn config_defaults_to_html_renderer_if_empty() {
let cfg = Config::default();
// make sure we haven't got anything in the `output` table
assert!(cfg.get("output").is_none());
let got = determine_renderers(&cfg);
assert_eq!(got.len(), 1);
assert_eq!(got[0].name(), "html");
}
#[test]
fn add_a_random_renderer_to_the_config() {
let mut cfg = Config::default();
cfg.set("output.random", Table::new()).unwrap();
let got = determine_renderers(&cfg);
assert_eq!(got.len(), 1);
assert_eq!(got[0].name(), "random");
}
#[test]
fn add_a_random_renderer_with_custom_command_to_the_config() {
let mut cfg = Config::default();
let mut table = Table::new();
table.insert("command".to_string(), Value::String("false".to_string()));
cfg.set("output.random", table).unwrap();
let got = determine_renderers(&cfg);
assert_eq!(got.len(), 1);
assert_eq!(got[0].name(), "random");
}
#[test]
fn config_defaults_to_link_and_index_preprocessor_if_not_set() {
let cfg = Config::default();
// make sure we haven't got anything in the `preprocessor` table
assert!(cfg.get("preprocessor").is_none());
let got = determine_preprocessors(&cfg);
assert!(got.is_ok());
assert_eq!(got.as_ref().unwrap().len(), 2);
assert_eq!(got.as_ref().unwrap()[0].name(), "index");
assert_eq!(got.as_ref().unwrap()[1].name(), "links");
}
#[test]
fn use_default_preprocessors_works() {
let mut cfg = Config::default();
cfg.build.use_default_preprocessors = false;
let got = determine_preprocessors(&cfg).unwrap();
assert_eq!(got.len(), 0);
}
#[test]
fn can_determine_third_party_preprocessors() {
let cfg_str = r#"
[book]
title = "Some Book"
[preprocessor.random]
[build]
build-dir = "outputs"
create-missing = false
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure the `preprocessor.random` table exists
assert!(cfg.get_preprocessor("random").is_some());
let got = determine_preprocessors(&cfg).unwrap();
assert!(got.into_iter().any(|p| p.name() == "random"));
}
#[test]
fn preprocessors_can_provide_their_own_commands() {
let cfg_str = r#"
[preprocessor.random]
command = "python random.py"
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// make sure the `preprocessor.random` table exists
let random = cfg.get_preprocessor("random").unwrap();
let random = get_custom_preprocessor_cmd("random", &Value::Table(random.clone()));
assert_eq!(random, "python random.py");
}
#[test]
fn preprocessor_before_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
before = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_after_must_be_array() {
let cfg_str = r#"
[preprocessor.random]
after = 0
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn preprocessor_order_is_honored() {
let cfg_str = r#"
[preprocessor.random]
before = [ "last" ]
after = [ "index" ]
[preprocessor.last]
after = [ "links", "index" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
let index = |name| {
preprocessors
.iter()
.enumerate()
.find(|(_, preprocessor)| preprocessor.name() == name)
.unwrap()
.0
};
let assert_before = |before, after| {
if index(before) >= index(after) {
eprintln!("Preprocessor order:");
for preprocessor in &preprocessors {
eprintln!(" {}", preprocessor.name());
}
panic!("{before} should come before {after}");
}
};
assert_before("index", "random");
assert_before("index", "last");
assert_before("random", "last");
assert_before("links", "last");
}
#[test]
fn cyclic_dependencies_are_detected() {
let cfg_str = r#"
[preprocessor.links]
before = [ "index" ]
[preprocessor.index]
before = [ "links" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
assert!(determine_preprocessors(&cfg).is_err());
}
#[test]
fn dependencies_dont_register_undefined_preprocessors() {
let cfg_str = r#"
[preprocessor.links]
before = [ "random" ]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(
!preprocessors
.iter()
.any(|preprocessor| preprocessor.name() == "random")
);
}
#[test]
fn dependencies_dont_register_builtin_preprocessors_if_disabled() {
let cfg_str = r#"
[preprocessor.random]
before = [ "links" ]
[build]
use-default-preprocessors = false
"#;
let cfg = Config::from_str(cfg_str).unwrap();
let preprocessors = determine_preprocessors(&cfg).unwrap();
assert!(
!preprocessors
.iter()
.any(|preprocessor| preprocessor.name() == "links")
);
}
#[test]
fn config_respects_preprocessor_selection() {
let cfg_str = r#"
[preprocessor.links]
renderers = ["html"]
"#;
let cfg = Config::from_str(cfg_str).unwrap();
// double-check that we can access preprocessor.links.renderers[0]
let html = cfg
.get_preprocessor("links")
.and_then(|links| links.get("renderers"))
.and_then(Value::as_array)
.and_then(|renderers| renderers.get(0))
.and_then(Value::as_str)
.unwrap();
assert_eq!(html, "html");
let html_renderer = HtmlHandlebars;
let pre = LinkPreprocessor::new();
let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg);
assert!(should_run);
}
struct BoolPreprocessor(bool);
impl Preprocessor for BoolPreprocessor {
fn name(&self) -> &str {
"bool-preprocessor"
}
fn run(&self, _ctx: &PreprocessorContext, _book: Book) -> Result<Book> {
unimplemented!()
}
fn supports_renderer(&self, _renderer: &str) -> bool {
self.0
}
}
#[test]
fn preprocessor_should_run_falls_back_to_supports_renderer_method() {
let cfg = Config::default();
let html = HtmlHandlebars::new();
let should_be = true;
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
assert_eq!(got, should_be);
let should_be = false;
let got = preprocessor_should_run(&BoolPreprocessor(should_be), &html, &cfg);
assert_eq!(got, should_be);
}

View file

@ -37,14 +37,14 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> {
let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;
let book_version = Version::parse(&ctx.mdbook_version)?;
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
let version_req = VersionReq::parse(mdbook_core::MDBOOK_VERSION)?;
if !version_req.matches(&book_version) {
eprintln!(
"Warning: The {} plugin was built against version {} of mdbook, \
but we're being called from version {}",
pre.name(),
mdbook::MDBOOK_VERSION,
mdbook_core::MDBOOK_VERSION,
ctx.mdbook_version
);
}

View file

@ -6,7 +6,7 @@ fn remove_emphasis_works() {
// Workaround for https://github.com/rust-lang/mdBook/issues/1424
std::env::set_current_dir("examples/remove-emphasis").unwrap();
let book = mdbook::MDBook::load(".").unwrap();
let book = mdbook_driver::MDBook::load(".").unwrap();
book.build().unwrap();
let ch1 = std::fs::read_to_string("book/chapter_1.html").unwrap();
assert!(ch1.contains("This has light emphasis and bold emphasis."));

View file

@ -1,7 +1,7 @@
use super::command_prelude::*;
use crate::{get_book_dir, open};
use anyhow::Result;
use mdbook::MDBook;
use mdbook_driver::MDBook;
use std::path::PathBuf;
// Create clap subcommand arguments

View file

@ -2,7 +2,7 @@ use super::command_prelude::*;
use crate::get_book_dir;
use anyhow::Context;
use anyhow::Result;
use mdbook::MDBook;
use mdbook_driver::MDBook;
use std::mem::take;
use std::path::PathBuf;
use std::{fmt, fs};

View file

@ -1,8 +1,8 @@
use crate::get_book_dir;
use anyhow::Result;
use clap::{ArgMatches, Command as ClapCommand, arg};
use mdbook::MDBook;
use mdbook_core::config;
use mdbook_driver::MDBook;
use std::io;
use std::io::Write;
use std::process::Command;

View file

@ -9,8 +9,8 @@ use axum::routing::get;
use clap::builder::NonEmptyStringValueParser;
use futures_util::StreamExt;
use futures_util::sink::SinkExt;
use mdbook::MDBook;
use mdbook_core::utils::fs::get_404_output_file;
use mdbook_driver::MDBook;
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use tokio::sync::broadcast;

View file

@ -3,7 +3,7 @@ use crate::get_book_dir;
use anyhow::Result;
use clap::ArgAction;
use clap::builder::NonEmptyStringValueParser;
use mdbook::MDBook;
use mdbook_driver::MDBook;
use std::path::PathBuf;
// Create clap subcommand arguments

View file

@ -1,7 +1,7 @@
use super::command_prelude::*;
use crate::{get_book_dir, open};
use anyhow::Result;
use mdbook::MDBook;
use mdbook_driver::MDBook;
use std::path::{Path, PathBuf};
mod native;

View file

@ -1,7 +1,7 @@
//! A filesystem watcher using native operating system facilities.
use ignore::gitignore::Gitignore;
use mdbook::MDBook;
use mdbook_driver::MDBook;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::thread::sleep;

View file

@ -5,7 +5,7 @@
//! had problems correctly reporting changes.
use ignore::gitignore::Gitignore;
use mdbook::MDBook;
use mdbook_driver::MDBook;
use pathdiff::diff_paths;
use std::collections::HashMap;
use std::fs::FileType;

View file

@ -28,7 +28,7 @@
//! the `MDBook::init()` method.
//!
//! ```rust,no_run
//! use mdbook::MDBook;
//! use mdbook_driver::MDBook;
//! use mdbook_core::config::Config;
//!
//! let root_dir = "/path/to/book/root";
@ -48,7 +48,7 @@
//! You can also load an existing book and build it.
//!
//! ```rust,no_run
//! use mdbook::MDBook;
//! use mdbook_driver::MDBook;
//!
//! let root_dir = "/path/to/book/root";
//!
@ -79,10 +79,3 @@
//! [`RenderContext`]: mdbook_renderer::RenderContext
//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html
//! [`Config`]: mdbook_core::config::Config
pub mod book;
pub use crate::book::BookItem;
pub use crate::book::MDBook;
pub use mdbook_core::MDBOOK_VERSION;
pub use mdbook_core::config::Config;

View file

@ -1,7 +1,7 @@
//! Utility for building and running tests against mdbook.
use mdbook::MDBook;
use mdbook::book::BookBuilder;
use mdbook_driver::MDBook;
use mdbook_driver::init::BookBuilder;
use snapbox::IntoData;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
@ -424,7 +424,8 @@ fn assert(root: &Path) -> snapbox::Assert {
regex!(r"(?m)(?<redacted>20\d\d-\d{2}-\d{2} \d{2}:\d{2}:\d{2})"),
)
.unwrap();
subs.insert("[VERSION]", mdbook::MDBOOK_VERSION).unwrap();
subs.insert("[VERSION]", mdbook_core::MDBOOK_VERSION)
.unwrap();
subs.extend(LITERAL_REDACTIONS.into_iter().cloned())
.unwrap();

View file

@ -10,8 +10,8 @@ use crate::prelude::*;
fn basic_build() {
BookTest::from_dir("build/basic_build").run("build", |cmd| {
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book`
"#]]);
@ -46,8 +46,8 @@ fn create_missing() {
fn no_reserved_filename() {
BookTest::from_dir("build/no_reserved_filename").run("build", |cmd| {
cmd.expect_failure().expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: print.md is reserved for internal use

View file

@ -45,9 +45,9 @@ fn recursive_include() {
BookTest::from_dir("includes/all_includes")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [ERROR] (mdbook_driver::builtin_preprocessors::links): Stack depth exceeded in recursive.md. Check for cyclic includes
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book`
"#]]);

View file

@ -1,7 +1,8 @@
//! Tests for `mdbook init`.
use crate::prelude::*;
use mdbook::{Config, MDBook};
use mdbook_core::config::Config;
use mdbook_driver::MDBook;
use std::path::PathBuf;
// Tests "init" with no args.
@ -18,7 +19,7 @@ All done, no errors...
"#]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
[TIMESTAMP] [INFO] (mdbook_driver::init): Creating a new book with stub content
"#]]);
})
@ -84,7 +85,7 @@ All done, no errors...
"#]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
[TIMESTAMP] [INFO] (mdbook_driver::init): Creating a new book with stub content
"#]]);
})
@ -116,7 +117,7 @@ All done, no errors...
"#]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book::init): Creating a new book with stub content
[TIMESTAMP] [INFO] (mdbook_driver::init): Creating a new book with stub content
"#]])
.args(&["--title", "Example title"]);

View file

@ -20,8 +20,8 @@ fn footnotes() {
BookTest::from_dir("markdown/footnotes")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [WARN] (mdbook_markdown): footnote `multiple-definitions` in <unknown> defined multiple times - not updating to new definition
[TIMESTAMP] [WARN] (mdbook_markdown): footnote `unused` in `<unknown>` is defined but not referenced
[TIMESTAMP] [WARN] (mdbook_markdown): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition

View file

@ -2,7 +2,7 @@
use crate::prelude::*;
use anyhow::Result;
use mdbook::book::Book;
use mdbook_core::book::Book;
use mdbook_driver::builtin_preprocessors::CmdPreprocessor;
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
use std::sync::{Arc, Mutex};
@ -47,8 +47,8 @@ fn runs_preprocessors() {
fn nop_preprocessor() {
BookTest::from_dir("preprocessor/nop_preprocessor").run("build", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book`
"#]]);
@ -63,7 +63,7 @@ fn failing_preprocessor() {
cmd.expect_failure()
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
Boom!!1!
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: The "nop-preprocessor" preprocessor exited unsuccessfully with [EXIT_STATUS]: 1 status

View file

@ -22,8 +22,8 @@ fn redirects_are_emitted_correctly() {
fn redirect_removed_with_fragments_only() {
BookTest::from_dir("redirects/redirect_removed_with_fragments_only").run("build", |cmd| {
cmd.expect_failure().expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: Unable to emit redirects
[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: redirect entry for `old-file.html` only has source paths with `#` fragments
@ -38,8 +38,8 @@ There must be an entry without the `#` fragment to determine the default destina
fn redirect_existing_page() {
BookTest::from_dir("redirects/redirect_existing_page").run("build", |cmd| {
cmd.expect_failure().expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: redirect found for existing chapter at `/chapter_1.html`
Either delete the redirect or remove the chapter.

View file

@ -64,8 +64,8 @@ fn failing_command() {
cmd.expect_failure()
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the failing backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the failing backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "failing" renderer
[TIMESTAMP] [ERROR] (mdbook_driver::builtin_renderers): Renderer exited with non-zero return code.
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
@ -82,8 +82,8 @@ fn missing_renderer() {
cmd.expect_failure()
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the missing backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the missing backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "missing" renderer
[TIMESTAMP] [ERROR] (mdbook_driver::builtin_renderers): The command `trduyvbhijnorgevfuhn` wasn't found, is the "missing" backend installed? If you want to ignore this error when the "missing" backend is not installed, set `optional = true` in the `[output.missing]` section of the book.toml configuration file.
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
@ -99,8 +99,8 @@ fn missing_renderer() {
fn missing_optional_not_fatal() {
BookTest::from_dir("renderer/missing_optional_not_fatal").run("build", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the missing backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the missing backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "missing" renderer
[TIMESTAMP] [WARN] (mdbook_driver::builtin_renderers): The command `trduyvbhijnorgevfuhn` for backend `missing` was not found, but was marked as optional.
@ -131,8 +131,8 @@ Hello World!
"#]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the arguments backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the arguments backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "arguments" renderer
"#]]);
@ -156,8 +156,8 @@ fn backends_receive_render_context_via_stdin() {
)
.run("build", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the cat-to-file backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the cat-to-file backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "cat-to-file" renderer
"#]]);
@ -234,8 +234,8 @@ fn legacy_relative_command_path() {
)
.run("build", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the myrenderer backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the myrenderer backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "myrenderer" renderer
"#]]);
@ -253,8 +253,8 @@ fn legacy_relative_command_path() {
)
.run("build", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the myrenderer backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the myrenderer backend
[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "myrenderer" renderer
[TIMESTAMP] [WARN] (mdbook_driver::builtin_renderers): Renderer command `../renderers/myrenderer[EXE]` uses a path relative to the renderer output directory `[ROOT]/book`. This was previously accepted, but has been deprecated. Relative executable paths should be relative to the book root.

View file

@ -1,8 +1,7 @@
//! Tests for search support.
use crate::prelude::*;
use mdbook::BookItem;
use mdbook::book::Chapter;
use mdbook_core::book::{BookItem, Chapter};
use snapbox::file;
use std::path::{Path, PathBuf};
@ -135,8 +134,8 @@ fn with_no_source_path() {
fn chapter_settings_validation_error() {
BookTest::from_dir("search/chapter_settings_validation_error").run("build", |cmd| {
cmd.expect_failure().expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: [output.html.search.chapter] key `does-not-exist` does not match any chapter paths

View file

@ -7,9 +7,9 @@ use crate::prelude::*;
fn passing_tests() {
BookTest::from_dir("test/passing_tests").run("test", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Intro': "intro.md"
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 1': "passing1.md"
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 2': "passing2.md"
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Intro': "intro.md"
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Passing 1': "passing1.md"
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Passing 2': "passing2.md"
"#]]);
});
@ -27,8 +27,8 @@ fn failing_tests() {
// still includes a little bit of output, so if that is a problem,
// add more redactions.
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Failing Tests': "failing.md"
[TIMESTAMP] [ERROR] (mdbook::book): rustdoc returned an error:
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Failing Tests': "failing.md"
[TIMESTAMP] [ERROR] (mdbook_driver::mdbook): rustdoc returned an error:
--- stdout
@ -38,8 +38,8 @@ test failing.md - Failing_Tests (line 3) ... FAILED
thread 'main' panicked at failing.md:3:1:
fail
...
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Failing Include': "failing_include.md"
[TIMESTAMP] [ERROR] (mdbook::book): rustdoc returned an error:
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Failing Include': "failing_include.md"
[TIMESTAMP] [ERROR] (mdbook_driver::mdbook): rustdoc returned an error:
--- stdout
...
@ -62,14 +62,14 @@ fn test_individual_chapter() {
cmd.args(&["Passing 1"])
.expect_stdout(str![[""]])
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 1': "passing1.md"
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Passing 1': "passing1.md"
"#]]);
})
// Can also be a source path.
.run("test -c passing2.md", |cmd| {
cmd.expect_stdout(str![[""]]).expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Testing chapter 'Passing 2': "passing2.md"
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Testing chapter 'Passing 2': "passing2.md"
"#]]);
});

View file

@ -9,8 +9,8 @@ fn missing_theme() {
.run("build", |cmd| {
cmd.expect_failure()
.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed
[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: theme dir [ROOT]/./non-existent-directory does not exist
@ -24,8 +24,8 @@ fn empty_theme() {
BookTest::from_dir("theme/empty_theme").run("build", |cmd| {
std::fs::create_dir(cmd.dir.join("theme")).unwrap();
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book`
"#]]);
@ -145,8 +145,8 @@ fn copy_fonts_false_no_theme() {
BookTest::from_dir("theme/copy_fonts_false_no_theme")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [WARN] (mdbook_html::html_handlebars::static_files): output.html.copy-fonts is deprecated.
This book appears to have copy-fonts=false in book.toml without a fonts.css file.
Add an empty `theme/fonts/fonts.css` file to squelch this warning.
@ -164,8 +164,8 @@ fn copy_fonts_false_with_empty_fonts_css() {
BookTest::from_dir("theme/copy_fonts_false_with_empty_fonts_css")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book`
"#]]);
@ -180,8 +180,8 @@ fn copy_fonts_false_with_fonts_css() {
BookTest::from_dir("theme/copy_fonts_false_with_fonts_css")
.run("build", |cmd| {
cmd.expect_stderr(str![[r#"
[TIMESTAMP] [INFO] (mdbook::book): Book building has started
[TIMESTAMP] [INFO] (mdbook::book): Running the html backend
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Book building has started
[TIMESTAMP] [INFO] (mdbook_driver::mdbook): Running the html backend
[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book`
"#]]);