From 5264074c1b9233be7983be8cff298ee2f784ea76 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:14:25 -0700 Subject: [PATCH 01/44] Move common lint controls to Cargo.toml This moves lint overrides to Cargo.toml so that they can more easily be shared across crates. --- Cargo.toml | 4 ++++ examples/nop-preprocessor.rs | 4 +++- examples/remove-emphasis/test.rs | 2 ++ src/config.rs | 2 -- src/lib.rs | 3 --- src/main.rs | 2 ++ tests/gui/runner.rs | 6 ++++++ 7 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b15eed77..3b7aa63f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,10 @@ all = { level = "allow", priority = -2 } correctness = { level = "warn", priority = -1 } complexity = { level = "warn", priority = -1 } +[workspace.lints.rust] +missing_docs = "warn" +rust_2018_idioms = "warn" + [package] name = "mdbook" version = "0.4.52" diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index a384dfda..ae593afc 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -1,3 +1,5 @@ +//! A basic example of a preprocessor that does nothing. + use crate::nop_lib::Nop; use clap::{Arg, ArgMatches, Command}; use mdbook::book::Book; @@ -7,7 +9,7 @@ use semver::{Version, VersionReq}; use std::io; use std::process; -pub fn make_app() -> Command { +fn make_app() -> Command { Command::new("nop-preprocessor") .about("A mdbook preprocessor which does precisely nothing") .subcommand( diff --git a/examples/remove-emphasis/test.rs b/examples/remove-emphasis/test.rs index 1741712b..e29d7d0f 100644 --- a/examples/remove-emphasis/test.rs +++ b/examples/remove-emphasis/test.rs @@ -1,3 +1,5 @@ +//! A test to ensure that the remove-emphasis example works. + #[test] fn remove_emphasis_works() { // Tests that the remove-emphasis example works as expected. diff --git a/src/config.rs b/src/config.rs index 7ef8bcef..3fe16929 100644 --- a/src/config.rs +++ b/src/config.rs @@ -47,8 +47,6 @@ //! # run().unwrap() //! ``` -#![deny(missing_docs)] - use log::{debug, trace, warn}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 8a8cb3c9..5f521761 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,9 +80,6 @@ //! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html //! [`Config`]: config::Config -#![deny(missing_docs)] -#![deny(rust_2018_idioms)] - pub mod book; pub mod config; pub mod preprocess; diff --git a/src/main.rs b/src/main.rs index 3e576c5b..975b5a21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +//! The mdbook CLI. + #[macro_use] extern crate clap; #[macro_use] diff --git a/tests/gui/runner.rs b/tests/gui/runner.rs index 3177ff4f..b8d4bacc 100644 --- a/tests/gui/runner.rs +++ b/tests/gui/runner.rs @@ -1,3 +1,9 @@ +//! The GUI test runner. +//! +//! This uses the browser-ui-test npm package to use a headless Chrome to +//! exercise the behavior of rendered books. See `CONTRIBUTING.md` for more +//! information. + use serde_json::Value; use std::collections::HashSet; use std::env::current_dir; From d6d5d6e674be8d83d84dbc081992d9109d0e515e Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:17:59 -0700 Subject: [PATCH 02/44] Move common package settings to shared workspace table This moves common settings that can be shared across crates to the shared workspace table. This will make it easier to maintain these settings when adding more crates. --- Cargo.toml | 14 ++++++++++---- .../mdbook-remove-emphasis/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b7aa63f..3ac37343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,12 @@ complexity = { level = "warn", priority = -1 } missing_docs = "warn" rust_2018_idioms = "warn" +[workspace.package] +edition = "2021" +license = "MPL-2.0" +repository = "https://github.com/rust-lang/mdBook" +rust-version = "1.82.0" # Keep in sync with installation.md and .github/workflows/main.yml + [package] name = "mdbook" version = "0.4.52" @@ -19,14 +25,14 @@ authors = [ "Matt Ickstadt " ] documentation = "https://rust-lang.github.io/mdBook/index.html" -edition = "2021" +edition.workspace = true exclude = ["/guide/*"] keywords = ["book", "gitbook", "rustbook", "markdown"] -license = "MPL-2.0" +license.workspace = true readme = "README.md" -repository = "https://github.com/rust-lang/mdBook" +repository.workspace = true description = "Creates a book from markdown files" -rust-version = "1.82" # Keep in sync with installation.md and .github/workflows/main.yml +rust-version.workspace = true [dependencies] anyhow = "1.0.71" diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml index 7571b18d..38f7e907 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml +++ b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mdbook-remove-emphasis" version = "0.1.0" -edition = "2021" +edition.workspace = true [dependencies] mdbook = { version = "0.4.40", path = "../../.." } From 0de13cf5a913ed818a40a7961e35e82c8a39b6bd Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:18:53 -0700 Subject: [PATCH 03/44] Update CI to test the whole workspace This updates the CI jobs to ensure that all crates in the workspace are tested. This will be needed when more crates are added. --- .github/workflows/main.yml | 4 ++-- examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73f940b4..6ce056cd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,9 +48,9 @@ jobs: - name: Install Rust run: bash ci/install-rust.sh ${{ matrix.rust }} ${{ matrix.target }} - name: Build and run tests - run: cargo test --locked --target ${{ matrix.target }} + run: cargo test --workspace --locked --target ${{ matrix.target }} - name: Test no default - run: cargo test --no-default-features --target ${{ matrix.target }} + run: cargo test --workspace --no-default-features --target ${{ matrix.target }} aarch64-cross-builds: runs-on: ubuntu-22.04 diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml index 38f7e907..0ef48dd5 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml +++ b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml @@ -8,3 +8,8 @@ mdbook = { version = "0.4.40", path = "../../.." } pulldown-cmark = { version = "0.12.2", default-features = false } pulldown-cmark-to-cmark = "18.0.0" serde_json = "1.0.132" + +[[bin]] +name = "mdbook-remove-emphasis" +# This is tested through a separate test from the main package. +test = false From d5a505e0c6f3434109bf5d8aacacd8279add847c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:30:43 -0700 Subject: [PATCH 04/44] Update to Rust 2024 --- .github/workflows/main.yml | 2 +- Cargo.toml | 4 ++-- guide/src/guide/installation.md | 2 +- src/book/book.rs | 4 +--- src/book/mod.rs | 2 +- src/config.rs | 9 ++++++--- src/renderer/html_handlebars/static_files.rs | 8 ++++---- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ce056cd..18b4b82e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,7 +40,7 @@ jobs: - name: msrv os: ubuntu-22.04 # sync MSRV with docs: guide/src/guide/installation.md and Cargo.toml - rust: 1.82.0 + rust: 1.85.0 target: x86_64-unknown-linux-gnu name: ${{ matrix.name }} steps: diff --git a/Cargo.toml b/Cargo.toml index 3ac37343..4e2f2251 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,10 @@ missing_docs = "warn" rust_2018_idioms = "warn" [workspace.package] -edition = "2021" +edition = "2024" license = "MPL-2.0" repository = "https://github.com/rust-lang/mdBook" -rust-version = "1.82.0" # Keep in sync with installation.md and .github/workflows/main.yml +rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflows/main.yml [package] name = "mdbook" diff --git a/guide/src/guide/installation.md b/guide/src/guide/installation.md index 61bb2731..72beb50f 100644 --- a/guide/src/guide/installation.md +++ b/guide/src/guide/installation.md @@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`. To build the `mdbook` executable from source, you will first need to install Rust and Cargo. Follow the instructions on the [Rust installation page]. -mdBook currently requires at least Rust version 1.82. +mdBook currently requires at least Rust version 1.85. Once you have installed Rust, the following command can be used to build and install mdBook: diff --git a/src/book/book.rs b/src/book/book.rs index fe71fbb2..72efa989 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -252,9 +252,7 @@ fn load_summary_item + Clone>( ) -> Result { match item { SummaryItem::Separator => Ok(BookItem::Separator), - SummaryItem::Link(ref link) => { - load_chapter(link, src_dir, parent_names).map(BookItem::Chapter) - } + SummaryItem::Link(link) => load_chapter(link, src_dir, parent_names).map(BookItem::Chapter), SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())), } } diff --git a/src/book/mod.rs b/src/book/mod.rs index da88767a..dffac05f 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -611,7 +611,7 @@ fn preprocessor_should_run( let key = format!("preprocessor.{}.renderers", preprocessor.name()); let renderer_name = renderer.name(); - if let Some(Value::Array(ref explicit_renderers)) = cfg.get(&key) { + if let Some(Value::Array(explicit_renderers)) = cfg.get(&key) { return explicit_renderers .iter() .filter_map(Value::as_str) diff --git a/src/config.rs b/src/config.rs index 3fe16929..41290436 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1144,7 +1144,8 @@ mod tests { assert!(cfg.get(key).is_none()); let encoded_key = encode_env_var(key); - env::set_var(encoded_key, value); + // TODO: This is unsafe, and should be rewritten to use a process. + unsafe { env::set_var(encoded_key, value) }; cfg.update_from_env(); @@ -1164,7 +1165,8 @@ mod tests { assert!(cfg.get(key).is_none()); let encoded_key = encode_env_var(key); - env::set_var(encoded_key, value_str); + // TODO: This is unsafe, and should be rewritten to use a process. + unsafe { env::set_var(encoded_key, value_str) }; cfg.update_from_env(); @@ -1183,7 +1185,8 @@ mod tests { assert_ne!(cfg.book.title, Some(should_be.clone())); - env::set_var("MDBOOK_BOOK__TITLE", &should_be); + // TODO: This is unsafe, and should be rewritten to use a process. + unsafe { env::set_var("MDBOOK_BOOK__TITLE", &should_be) }; cfg.update_from_env(); assert_eq!(cfg.book.title, Some(should_be)); diff --git a/src/renderer/html_handlebars/static_files.rs b/src/renderer/html_handlebars/static_files.rs index e1531f42..ac205415 100644 --- a/src/renderer/html_handlebars/static_files.rs +++ b/src/renderer/html_handlebars/static_files.rs @@ -172,7 +172,7 @@ impl StaticFiles { use std::io::Read; for static_file in &mut self.static_files { match static_file { - StaticFile::Builtin { + &mut StaticFile::Builtin { ref mut filename, ref data, } => { @@ -193,7 +193,7 @@ impl StaticFiles { } } } - StaticFile::Additional { + &mut StaticFile::Additional { ref mut filename, ref input_location, } => { @@ -263,8 +263,8 @@ impl StaticFiles { write_file(destination, filename, &data)?; } StaticFile::Additional { - ref input_location, - ref filename, + input_location, + filename, } => { let output_location = destination.join(filename); debug!( From c7b67e363bb9ce3383636ee615e8e761bf185b33 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:31:41 -0700 Subject: [PATCH 05/44] Rustfmt for 2024 --- .../mdbook-remove-emphasis/src/main.rs | 2 +- src/book/book.rs | 2 +- src/book/mod.rs | 20 +- src/book/summary.rs | 10 +- src/cmd/build.rs | 2 +- src/cmd/command_prelude.rs | 2 +- src/cmd/init.rs | 4 +- src/cmd/serve.rs | 6 +- src/cmd/test.rs | 4 +- src/cmd/watch.rs | 2 +- src/config.rs | 2 +- src/lib.rs | 2 +- src/preprocess/links.rs | 6 +- src/renderer/html_handlebars/hbs_renderer.rs | 188 +++++++++++------- src/renderer/html_handlebars/static_files.rs | 2 +- src/utils/mod.rs | 2 +- tests/testsuite/book_test.rs | 2 +- tests/testsuite/search.rs | 7 +- 18 files changed, 162 insertions(+), 103 deletions(-) diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs b/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs index e3a5d607..6112a692 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs +++ b/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs @@ -1,10 +1,10 @@ //! This is a demonstration of an mdBook preprocessor which parses markdown //! and removes any instances of emphasis. +use mdbook::BookItem; use mdbook::book::{Book, Chapter}; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; -use mdbook::BookItem; use pulldown_cmark::{Event, Parser, Tag, TagEnd}; use std::io; diff --git a/src/book/book.rs b/src/book/book.rs index 72efa989..fe5c3f38 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -4,7 +4,7 @@ use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; +use super::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use crate::config::BuildConfig; use crate::errors::*; use crate::utils::bracket_escape; diff --git a/src/book/mod.rs b/src/book/mod.rs index dffac05f..7c3f0b4c 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -9,9 +9,9 @@ mod book; mod init; mod summary; -pub use self::book::{load_book, Book, BookItem, BookItems, Chapter}; +pub use self::book::{Book, BookItem, BookItems, Chapter, load_book}; pub use self::init::BookBuilder; -pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; +pub use self::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use log::{debug, error, info, log_enabled, trace, warn}; use std::ffi::OsString; @@ -817,9 +817,11 @@ mod tests { let preprocessors = determine_preprocessors(&cfg).unwrap(); - assert!(!preprocessors - .iter() - .any(|preprocessor| preprocessor.name() == "random")); + assert!( + !preprocessors + .iter() + .any(|preprocessor| preprocessor.name() == "random") + ); } #[test] @@ -836,9 +838,11 @@ mod tests { let preprocessors = determine_preprocessors(&cfg).unwrap(); - assert!(!preprocessors - .iter() - .any(|preprocessor| preprocessor.name() == "links")); + assert!( + !preprocessors + .iter() + .any(|preprocessor| preprocessor.name() == "links") + ); } #[test] diff --git a/src/book/summary.rs b/src/book/summary.rs index d25b5667..01d6f5f6 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -776,7 +776,10 @@ mod tests { assert!(got.is_err()); let error_message = got.err().unwrap().to_string(); - assert_eq!(error_message, "failed to parse SUMMARY.md line 2, column 1: Suffix chapters cannot be followed by a list"); + assert_eq!( + error_message, + "failed to parse SUMMARY.md line 2, column 1: Suffix chapters cannot be followed by a list" + ); } #[test] @@ -788,7 +791,10 @@ mod tests { assert!(got.is_err()); let error_message = got.err().unwrap().to_string(); - assert_eq!(error_message, "failed to parse SUMMARY.md line 1, column 0: Suffix chapters cannot be followed by a list"); + assert_eq!( + error_message, + "failed to parse SUMMARY.md line 1, column 0: Suffix chapters cannot be followed by a list" + ); } #[test] diff --git a/src/cmd/build.rs b/src/cmd/build.rs index e40e5c0c..53792e4e 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,7 +1,7 @@ use super::command_prelude::*; use crate::{get_book_dir, open}; -use mdbook::errors::Result; use mdbook::MDBook; +use mdbook::errors::Result; use std::path::PathBuf; // Create clap subcommand arguments diff --git a/src/cmd/command_prelude.rs b/src/cmd/command_prelude.rs index 37199425..d5df3af9 100644 --- a/src/cmd/command_prelude.rs +++ b/src/cmd/command_prelude.rs @@ -1,6 +1,6 @@ //! Helpers for building the command-line arguments for commands. -pub use clap::{arg, Arg, ArgMatches, Command}; +pub use clap::{Arg, ArgMatches, Command, arg}; use std::path::PathBuf; pub trait CommandExt: Sized { diff --git a/src/cmd/init.rs b/src/cmd/init.rs index f15fb968..b31beee9 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,8 +1,8 @@ use crate::get_book_dir; -use clap::{arg, ArgMatches, Command as ClapCommand}; +use clap::{ArgMatches, Command as ClapCommand, arg}; +use mdbook::MDBook; use mdbook::config; use mdbook::errors::Result; -use mdbook::MDBook; use std::io; use std::io::Write; use std::process::Command; diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index beab121f..e3a0abdd 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -2,15 +2,15 @@ use super::command_prelude::*; #[cfg(feature = "watch")] use super::watch; use crate::{get_book_dir, open}; +use axum::Router; use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade}; use axum::routing::get; -use axum::Router; use clap::builder::NonEmptyStringValueParser; -use futures_util::sink::SinkExt; use futures_util::StreamExt; +use futures_util::sink::SinkExt; +use mdbook::MDBook; use mdbook::errors::*; use mdbook::utils::fs::get_404_output_file; -use mdbook::MDBook; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; use tokio::sync::broadcast; diff --git a/src/cmd/test.rs b/src/cmd/test.rs index d41e9ef9..d32410f6 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -1,9 +1,9 @@ use super::command_prelude::*; use crate::get_book_dir; -use clap::builder::NonEmptyStringValueParser; use clap::ArgAction; -use mdbook::errors::Result; +use clap::builder::NonEmptyStringValueParser; use mdbook::MDBook; +use mdbook::errors::Result; use std::path::PathBuf; // Create clap subcommand arguments diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index 7adb2bbb..b20cbd11 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,7 +1,7 @@ use super::command_prelude::*; use crate::{get_book_dir, open}; -use mdbook::errors::Result; use mdbook::MDBook; +use mdbook::errors::Result; use std::path::{Path, PathBuf}; mod native; diff --git a/src/config.rs b/src/config.rs index 41290436..ef0f5dfd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,8 +55,8 @@ use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use std::str::FromStr; -use toml::value::Table; use toml::Value; +use toml::value::Table; use crate::errors::*; use crate::utils::{self, toml_ext::TomlExt}; diff --git a/src/lib.rs b/src/lib.rs index 5f521761..93c41769 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,6 @@ pub use crate::renderer::Renderer; /// The error types used through out this crate. pub mod errors { - pub(crate) use anyhow::{bail, ensure, Context}; + pub(crate) use anyhow::{Context, bail, ensure}; pub use anyhow::{Error, Result}; } diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index 951a3436..93eaf813 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -684,8 +684,7 @@ mod tests { #[test] fn test_find_playgrounds_with_properties() { - let s = - "Some random text with escaped playground {{#playground file.rs editable }} and some \ + let s = "Some random text with escaped playground {{#playground file.rs editable }} and some \ more\n text {{#playground my.rs editable no_run should_panic}} ..."; let res = find_links(s).collect::>(); @@ -714,8 +713,7 @@ mod tests { #[test] fn test_find_all_link_types() { - let s = - "Some random text with escaped playground {{#include file.rs}} and \\{{#contents are \ + let s = "Some random text with escaped playground {{#include file.rs}} and \\{{#contents are \ insignifficant in escaped link}} some more\n text {{#playground my.rs editable \ no_run should_panic}} ..."; diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index b1ea7520..98203b60 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -1,8 +1,8 @@ use crate::book::{Book, BookItem}; use crate::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; use crate::errors::*; -use crate::renderer::html_handlebars::helpers; use crate::renderer::html_handlebars::StaticFiles; +use crate::renderer::html_handlebars::helpers; use crate::renderer::{RenderContext, Renderer}; use crate::theme::{self, Theme}; use crate::utils; @@ -1066,20 +1066,34 @@ mod tests { #[test] fn add_playground() { let inputs = [ - ("x()", - "
# #![allow(unused)]\n# fn main() {\nx()\n# }
"), - ("fn main() {}", - "
fn main() {}
"), - ("let s = \"foo\n # bar\n\";", - "
let s = \"foo\n # bar\n\";
"), - ("let s = \"foo\n ## bar\n\";", - "
let s = \"foo\n ## bar\n\";
"), - ("let s = \"foo\n # bar\n#\n\";", - "
let s = \"foo\n # bar\n#\n\";
"), - ("let s = \"foo\n # bar\n\";", - "let s = \"foo\n # bar\n\";"), - ("#![no_std]\nlet s = \"foo\";\n #[some_attr]", - "
#![no_std]\nlet s = \"foo\";\n #[some_attr]
"), + ( + "x()", + "
# #![allow(unused)]\n# fn main() {\nx()\n# }
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "let s = \"foo\n # bar\n\";", + "
let s = \"foo\n # bar\n\";
", + ), + ( + "let s = \"foo\n ## bar\n\";", + "
let s = \"foo\n ## bar\n\";
", + ), + ( + "let s = \"foo\n # bar\n#\n\";", + "
let s = \"foo\n # bar\n#\n\";
", + ), + ( + "let s = \"foo\n # bar\n\";", + "let s = \"foo\n # bar\n\";", + ), + ( + "#![no_std]\nlet s = \"foo\";\n #[some_attr]", + "
#![no_std]\nlet s = \"foo\";\n #[some_attr]
", + ), ]; for (src, should_be) in &inputs { let got = add_playground_pre( @@ -1096,14 +1110,22 @@ mod tests { #[test] fn add_playground_edition2015() { let inputs = [ - ("x()", - "
# #![allow(unused)]\n# fn main() {\nx()\n# }
"), - ("fn main() {}", - "
fn main() {}
"), - ("fn main() {}", - "
fn main() {}
"), - ("fn main() {}", - "
fn main() {}
"), + ( + "x()", + "
# #![allow(unused)]\n# fn main() {\nx()\n# }
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), ]; for (src, should_be) in &inputs { let got = add_playground_pre( @@ -1120,14 +1142,22 @@ mod tests { #[test] fn add_playground_edition2018() { let inputs = [ - ("x()", - "
# #![allow(unused)]\n# fn main() {\nx()\n# }
"), - ("fn main() {}", - "
fn main() {}
"), - ("fn main() {}", - "
fn main() {}
"), - ("fn main() {}", - "
fn main() {}
"), + ( + "x()", + "
# #![allow(unused)]\n# fn main() {\nx()\n# }
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), ]; for (src, should_be) in &inputs { let got = add_playground_pre( @@ -1144,14 +1174,22 @@ mod tests { #[test] fn add_playground_edition2021() { let inputs = [ - ("x()", - "
# #![allow(unused)]\n# fn main() {\nx()\n# }
"), - ("fn main() {}", - "
fn main() {}
"), - ("fn main() {}", - "
fn main() {}
"), - ("fn main() {}", - "
fn main() {}
"), + ( + "x()", + "
# #![allow(unused)]\n# fn main() {\nx()\n# }
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), + ( + "fn main() {}", + "
fn main() {}
", + ), ]; for (src, should_be) in &inputs { let got = add_playground_pre( @@ -1169,31 +1207,39 @@ mod tests { #[test] fn hide_lines_language_rust() { let inputs = [ - ( - "
\n# #![allow(unused)]\n# fn main() {\nx()\n# }
", - "
\n#![allow(unused)]\nfn main() {\nx()\n}
",), - // # must be followed by a space for a line to be hidden - ( - "
\n#fn main() {\nx()\n#}
", - "
\n#fn main() {\nx()\n#}
",), - ( - "
fn main() {}
", - "
fn main() {}
",), - ( - "
let s = \"foo\n # bar\n\";
", - "
let s = \"foo\n bar\n\";
",), - ( - "
let s = \"foo\n ## bar\n\";
", - "
let s = \"foo\n # bar\n\";
",), - ( - "
let s = \"foo\n # bar\n#\n\";
", - "
let s = \"foo\n bar\n\n\";
",), - ( - "let s = \"foo\n # bar\n\";", - "let s = \"foo\n bar\n\";",), - ( - "
#![no_std]\nlet s = \"foo\";\n #[some_attr]
", - "
#![no_std]\nlet s = \"foo\";\n #[some_attr]
",), + ( + "
\n# #![allow(unused)]\n# fn main() {\nx()\n# }
", + "
\n#![allow(unused)]\nfn main() {\nx()\n}
", + ), + // # must be followed by a space for a line to be hidden + ( + "
\n#fn main() {\nx()\n#}
", + "
\n#fn main() {\nx()\n#}
", + ), + ( + "
fn main() {}
", + "
fn main() {}
", + ), + ( + "
let s = \"foo\n # bar\n\";
", + "
let s = \"foo\n bar\n\";
", + ), + ( + "
let s = \"foo\n ## bar\n\";
", + "
let s = \"foo\n # bar\n\";
", + ), + ( + "
let s = \"foo\n # bar\n#\n\";
", + "
let s = \"foo\n bar\n\n\";
", + ), + ( + "let s = \"foo\n # bar\n\";", + "let s = \"foo\n bar\n\";", + ), + ( + "
#![no_std]\nlet s = \"foo\";\n #[some_attr]
", + "
#![no_std]\nlet s = \"foo\";\n #[some_attr]
", + ), ]; for (src, should_be) in &inputs { let got = hide_lines(src, &Code::default()); @@ -1204,12 +1250,14 @@ mod tests { #[test] fn hide_lines_language_other() { let inputs = [ - ( - "~hidden()\nnothidden():\n~ hidden()\n ~hidden()\n nothidden()", - "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n",), - ( - "!!!hidden()\nnothidden():\n!!! hidden()\n !!!hidden()\n nothidden()", - "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n",), + ( + "~hidden()\nnothidden():\n~ hidden()\n ~hidden()\n nothidden()", + "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n", + ), + ( + "!!!hidden()\nnothidden():\n!!! hidden()\n !!!hidden()\n nothidden()", + "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n", + ), ]; for (src, should_be) in &inputs { let got = hide_lines( diff --git a/src/renderer/html_handlebars/static_files.rs b/src/renderer/html_handlebars/static_files.rs index ac205415..a3cd1322 100644 --- a/src/renderer/html_handlebars/static_files.rs +++ b/src/renderer/html_handlebars/static_files.rs @@ -5,7 +5,7 @@ use log::{debug, warn}; use crate::config::HtmlConfig; use crate::errors::*; use crate::renderer::html_handlebars::helpers::resources::ResourceHelper; -use crate::theme::{self, playground_editor, Theme}; +use crate::theme::{self, Theme, playground_editor}; use crate::utils; use std::borrow::Cow; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 597f0ea4..14fdd0ad 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,7 +5,7 @@ mod string; pub(crate) mod toml_ext; use crate::errors::Error; use log::error; -use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd}; +use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd, html}; use regex::Regex; use std::borrow::Cow; diff --git a/tests/testsuite/book_test.rs b/tests/testsuite/book_test.rs index 427c38d1..ed5ebe83 100644 --- a/tests/testsuite/book_test.rs +++ b/tests/testsuite/book_test.rs @@ -1,7 +1,7 @@ //! Utility for building and running tests against mdbook. -use mdbook::book::BookBuilder; use mdbook::MDBook; +use mdbook::book::BookBuilder; use snapbox::IntoData; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs index 8ab571b2..5eca0e43 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -1,8 +1,8 @@ //! Tests for search support. use crate::prelude::*; -use mdbook::book::Chapter; use mdbook::BookItem; +use mdbook::book::Chapter; use snapbox::file; use std::path::{Path, PathBuf}; @@ -62,7 +62,10 @@ fn reasonable_search_index() { // See note about InlineHtml in search.rs. Ideally the `alert()` part // should not be in the index, but we don't have a way to scrub inline // html. - assert_eq!(docs[&sneaky]["body"], "I put <HTML> in here! Sneaky inline event alert(\"inline\");. But regular inline is indexed."); + assert_eq!( + docs[&sneaky]["body"], + "I put <HTML> in here! Sneaky inline event alert(\"inline\");. But regular inline is indexed." + ); assert_eq!( docs[&no_headers]["breadcrumbs"], "First Chapter » No Headers" From 1499e850ac0bb63df5e4f55bcf281d9ee0a4e627 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:33:59 -0700 Subject: [PATCH 06/44] Add .git-blame-ignore-revs This is to help make it easier to traverse blame history for things like formatting commits. --- .git-blame-ignore-revs | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..94c043e3 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +# Use `git config blame.ignorerevsfile .git-blame-ignore-revs` to make `git blame` ignore the following commits. + +# Rustfmt for 2024 +c7b67e363bb9ce3383636ee615e8e761bf185b33 From 877a3af671ecb7de27e3e561d7d6d2d905b150d6 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:44:51 -0700 Subject: [PATCH 07/44] Add rustfmt.toml This is intended to help with editor integration for using the correct style edition. --- rustfmt.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..35011368 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +style_edition = "2024" From 4a655ff2a38b9ebd505a04f7e6414ea04a8d8f9d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 10:57:23 -0700 Subject: [PATCH 08/44] Add mdbook-core This is intended as a shared, internal library that will be used by other mdbook crates. The intention is that those crates will either directly use, or reexport items from this crate. Initially this includes MDBOOK_VERSION, which will get reexported from the preprocessor and renderer crates. --- Cargo.lock | 7 ++++++- Cargo.toml | 10 +++++++++- crates/mdbook-core/Cargo.toml | 13 +++++++++++++ crates/mdbook-core/src/lib.rs | 7 +++++++ src/lib.rs | 7 +------ 5 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 crates/mdbook-core/Cargo.toml create mode 100644 crates/mdbook-core/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dc7e85eb..c44924f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1270,6 +1270,7 @@ dependencies = [ "hex", "ignore", "log", + "mdbook-core", "memchr", "notify", "notify-debouncer-mini", @@ -1293,6 +1294,10 @@ dependencies = [ "walkdir", ] +[[package]] +name = "mdbook-core" +version = "0.5.0-alpha.1" + [[package]] name = "mdbook-remove-emphasis" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4e2f2251..cf8e05fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,9 @@ [workspace] -members = [".", "examples/remove-emphasis/mdbook-remove-emphasis"] +members = [ + ".", + "crates/*", + "examples/remove-emphasis/mdbook-remove-emphasis", +] [workspace.lints.clippy] all = { level = "allow", priority = -2 } @@ -16,6 +20,9 @@ license = "MPL-2.0" repository = "https://github.com/rust-lang/mdBook" rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflows/main.yml +[workspace.dependencies] +mdbook-core = { path = "crates/mdbook-core" } + [package] name = "mdbook" version = "0.4.52" @@ -43,6 +50,7 @@ env_logger = "0.11.1" handlebars = "6.0" hex = "0.4.3" log = "0.4.17" +mdbook-core.workspace = true memchr = "2.5.0" opener = "0.8.1" pulldown-cmark = { version = "0.10.0", default-features = false, features = ["html"] } # Do not update, part of the public api. diff --git a/crates/mdbook-core/Cargo.toml b/crates/mdbook-core/Cargo.toml new file mode 100644 index 00000000..b8888e9c --- /dev/null +++ b/crates/mdbook-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mdbook-core" +version = "0.5.0-alpha.1" +description = "The base support library for mdbook, intended for internal use only" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/mdbook-core/src/lib.rs b/crates/mdbook-core/src/lib.rs new file mode 100644 index 00000000..152102dd --- /dev/null +++ b/crates/mdbook-core/src/lib.rs @@ -0,0 +1,7 @@ +//! The base support library for mdbook, intended for internal use only. + +/// The current version of `mdbook`. +/// +/// This is provided as a way for custom preprocessors and renderers to do +/// compatibility checks. +pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/lib.rs b/src/lib.rs index 93c41769..73195279 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,16 +88,11 @@ pub mod renderer; pub mod theme; pub mod utils; -/// The current version of `mdbook`. -/// -/// This is provided as a way for custom preprocessors and renderers to do -/// compatibility checks. -pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); - pub use crate::book::BookItem; pub use crate::book::MDBook; pub use crate::config::Config; pub use crate::renderer::Renderer; +pub use mdbook_core::MDBOOK_VERSION; /// The error types used through out this crate. pub mod errors { From bc3399cc22a93d70da6b9c9c1c1da905b4445c31 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 11:00:41 -0700 Subject: [PATCH 09/44] Update version to 0.5.0-alpha.1 This bumps the main crate to 0.5.0-alpha.1 in preparation for the 0.5 release. --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c44924f5..a5a997bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1255,7 +1255,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "mdbook" -version = "0.4.52" +version = "0.5.0-alpha.1" dependencies = [ "ammonia", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index cf8e05fd..fef052e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ mdbook-core = { path = "crates/mdbook-core" } [package] name = "mdbook" -version = "0.4.52" +version = "0.5.0-alpha.1" authors = [ "Mathieu David ", "Michael-F-Bryan ", diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml index 0ef48dd5..44f9bdd0 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml +++ b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition.workspace = true [dependencies] -mdbook = { version = "0.4.40", path = "../../.." } +mdbook = { path = "../../.." } pulldown-cmark = { version = "0.12.2", default-features = false } pulldown-cmark-to-cmark = "18.0.0" serde_json = "1.0.132" From 461884f10928c18233b7ecba39008a54cbe198e7 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 11:07:57 -0700 Subject: [PATCH 10/44] Add mdbook-preprocessor and mdbook-renderer These are two new crates intended to support implementing preprocessors and renderers. Currently these stubs just have MDBOOK_VERSION, but future commits will migrate more code to these crates. --- Cargo.lock | 14 ++++++++++++++ crates/mdbook-preprocessor/Cargo.toml | 14 ++++++++++++++ crates/mdbook-preprocessor/src/lib.rs | 3 +++ crates/mdbook-renderer/Cargo.toml | 14 ++++++++++++++ crates/mdbook-renderer/src/lib.rs | 3 +++ 5 files changed, 48 insertions(+) create mode 100644 crates/mdbook-preprocessor/Cargo.toml create mode 100644 crates/mdbook-preprocessor/src/lib.rs create mode 100644 crates/mdbook-renderer/Cargo.toml create mode 100644 crates/mdbook-renderer/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a5a997bf..b19b6304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1298,6 +1298,13 @@ dependencies = [ name = "mdbook-core" version = "0.5.0-alpha.1" +[[package]] +name = "mdbook-preprocessor" +version = "0.5.0-alpha.1" +dependencies = [ + "mdbook-core", +] + [[package]] name = "mdbook-remove-emphasis" version = "0.1.0" @@ -1308,6 +1315,13 @@ dependencies = [ "serde_json", ] +[[package]] +name = "mdbook-renderer" +version = "0.5.0-alpha.1" +dependencies = [ + "mdbook-core", +] + [[package]] name = "memchr" version = "2.7.5" diff --git a/crates/mdbook-preprocessor/Cargo.toml b/crates/mdbook-preprocessor/Cargo.toml new file mode 100644 index 00000000..c39ff408 --- /dev/null +++ b/crates/mdbook-preprocessor/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mdbook-preprocessor" +version = "0.5.0-alpha.1" +description = "Library to assist implementing an mdBook preprocessor" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +mdbook-core.workspace = true + +[lints] +workspace = true diff --git a/crates/mdbook-preprocessor/src/lib.rs b/crates/mdbook-preprocessor/src/lib.rs new file mode 100644 index 00000000..e4d97bb4 --- /dev/null +++ b/crates/mdbook-preprocessor/src/lib.rs @@ -0,0 +1,3 @@ +//! Library to assist implementing an mdbook preprocessor. + +pub use mdbook_core::MDBOOK_VERSION; diff --git a/crates/mdbook-renderer/Cargo.toml b/crates/mdbook-renderer/Cargo.toml new file mode 100644 index 00000000..2ec83e47 --- /dev/null +++ b/crates/mdbook-renderer/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mdbook-renderer" +version = "0.5.0-alpha.1" +description = "Library to assist implementing an mdBook renderer" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +mdbook-core.workspace = true + +[lints] +workspace = true diff --git a/crates/mdbook-renderer/src/lib.rs b/crates/mdbook-renderer/src/lib.rs new file mode 100644 index 00000000..8b09630d --- /dev/null +++ b/crates/mdbook-renderer/src/lib.rs @@ -0,0 +1,3 @@ +//! Library to assist implementing an mdbook renderer. + +pub use mdbook_core::MDBOOK_VERSION; From f51d89ba02cdbc7f053c4745c34c343ea7897503 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 11:37:46 -0700 Subject: [PATCH 11/44] Move error types to mdbook-core This moves Result and Error to mdbook-core with the anticipation of using them in user crates. For now, the internal APIs will be using anyhow directly, but the intent is to transition more of these to mdbook-core where it makes sense. --- Cargo.lock | 4 ++++ Cargo.toml | 3 ++- crates/mdbook-core/Cargo.toml | 1 + crates/mdbook-core/src/lib.rs | 5 +++++ examples/nop-preprocessor.rs | 2 +- .../mdbook-remove-emphasis/Cargo.toml | 1 + .../mdbook-remove-emphasis/src/main.rs | 2 +- src/book/book.rs | 2 +- src/book/init.rs | 2 +- src/book/mod.rs | 2 +- src/book/summary.rs | 2 +- src/cmd/build.rs | 2 +- src/cmd/clean.rs | 5 +++-- src/cmd/init.rs | 2 +- src/cmd/serve.rs | 2 +- src/cmd/test.rs | 2 +- src/cmd/watch.rs | 2 +- src/config.rs | 7 +++--- src/front-end/mod.rs | 16 ++++++-------- src/lib.rs | 6 ----- src/preprocess/cmd.rs | 2 +- src/preprocess/index.rs | 2 +- src/preprocess/links.rs | 2 +- src/preprocess/mod.rs | 2 +- src/renderer/html_handlebars/hbs_renderer.rs | 4 ++-- src/renderer/html_handlebars/search.rs | 6 ++--- src/renderer/html_handlebars/static_files.rs | 2 +- src/renderer/markdown_renderer.rs | 2 +- src/renderer/mod.rs | 22 +++++++++---------- src/utils/fs.rs | 2 +- src/utils/mod.rs | 10 ++++----- tests/testsuite/preprocessor.rs | 2 +- tests/testsuite/renderer.rs | 2 +- tests/testsuite/toc.rs | 2 +- 34 files changed, 67 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b19b6304..732e62a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1297,6 +1297,9 @@ dependencies = [ [[package]] name = "mdbook-core" version = "0.5.0-alpha.1" +dependencies = [ + "anyhow", +] [[package]] name = "mdbook-preprocessor" @@ -1309,6 +1312,7 @@ dependencies = [ name = "mdbook-remove-emphasis" version = "0.1.0" dependencies = [ + "anyhow", "mdbook", "pulldown-cmark 0.12.2", "pulldown-cmark-to-cmark", diff --git a/Cargo.toml b/Cargo.toml index fef052e1..ee570234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ repository = "https://github.com/rust-lang/mdBook" rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflows/main.yml [workspace.dependencies] +anyhow = "1.0.98" mdbook-core = { path = "crates/mdbook-core" } [package] @@ -42,7 +43,7 @@ description = "Creates a book from markdown files" rust-version.workspace = true [dependencies] -anyhow = "1.0.71" +anyhow.workspace = true chrono = { version = "0.4.24", default-features = false, features = ["clock"] } clap = { version = "4.3.12", features = ["cargo", "wrap_help"] } clap_complete = "4.3.2" diff --git a/crates/mdbook-core/Cargo.toml b/crates/mdbook-core/Cargo.toml index b8888e9c..301bfe87 100644 --- a/crates/mdbook-core/Cargo.toml +++ b/crates/mdbook-core/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow.workspace = true [lints] workspace = true diff --git a/crates/mdbook-core/src/lib.rs b/crates/mdbook-core/src/lib.rs index 152102dd..d7298926 100644 --- a/crates/mdbook-core/src/lib.rs +++ b/crates/mdbook-core/src/lib.rs @@ -5,3 +5,8 @@ /// This is provided as a way for custom preprocessors and renderers to do /// compatibility checks. pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// The error types used in mdbook. +pub mod errors { + pub use anyhow::{Error, Result}; +} diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index ae593afc..f85fd820 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -1,9 +1,9 @@ //! A basic example of a preprocessor that does nothing. use crate::nop_lib::Nop; +use anyhow::Error; use clap::{Arg, ArgMatches, Command}; use mdbook::book::Book; -use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; use semver::{Version, VersionReq}; use std::io; diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml index 44f9bdd0..cd3e8c83 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml +++ b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition.workspace = true [dependencies] +anyhow.workspace = true mdbook = { path = "../../.." } pulldown-cmark = { version = "0.12.2", default-features = false } pulldown-cmark-to-cmark = "18.0.0" diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs b/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs index 6112a692..88f58506 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs +++ b/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs @@ -1,9 +1,9 @@ //! This is a demonstration of an mdBook preprocessor which parses markdown //! and removes any instances of emphasis. +use anyhow::Error; use mdbook::BookItem; use mdbook::book::{Book, Chapter}; -use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; use pulldown_cmark::{Event, Parser, Tag, TagEnd}; use std::io; diff --git a/src/book/book.rs b/src/book/book.rs index fe5c3f38..769c503d 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -6,8 +6,8 @@ use std::path::{Path, PathBuf}; use super::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use crate::config::BuildConfig; -use crate::errors::*; use crate::utils::bracket_escape; +use anyhow::{Context, Result}; use log::debug; use serde::{Deserialize, Serialize}; diff --git a/src/book/init.rs b/src/book/init.rs index faca1d09..b7372e33 100644 --- a/src/book/init.rs +++ b/src/book/init.rs @@ -4,9 +4,9 @@ use std::path::PathBuf; use super::MDBook; use crate::config::Config; -use crate::errors::*; use crate::theme; use crate::utils::fs::write_file; +use anyhow::{Context, Result}; use log::{debug, error, info, trace}; /// A helper for setting up a new book and its directory structure. diff --git a/src/book/mod.rs b/src/book/mod.rs index 7c3f0b4c..117d6ed6 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -13,6 +13,7 @@ pub use self::book::{Book, BookItem, BookItems, Chapter, load_book}; pub use self::init::BookBuilder; pub use self::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; +use anyhow::{Context, Error, Result, bail}; use log::{debug, error, info, log_enabled, trace, warn}; use std::ffi::OsString; use std::io::{IsTerminal, Write}; @@ -22,7 +23,6 @@ use tempfile::Builder as TempFileBuilder; use toml::Value; use topological_sort::TopologicalSort; -use crate::errors::*; use crate::preprocess::{ CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext, }; diff --git a/src/book/summary.rs b/src/book/summary.rs index 01d6f5f6..72021842 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -1,4 +1,4 @@ -use crate::errors::*; +use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; use memchr::Memchr; use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd}; diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 53792e4e..05b0bfd2 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,7 +1,7 @@ use super::command_prelude::*; use crate::{get_book_dir, open}; +use anyhow::Result; use mdbook::MDBook; -use mdbook::errors::Result; use std::path::PathBuf; // Create clap subcommand arguments diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index ec77537e..5a06bc6f 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -1,6 +1,7 @@ use super::command_prelude::*; use crate::get_book_dir; use anyhow::Context; +use anyhow::Result; use mdbook::MDBook; use std::mem::take; use std::path::PathBuf; @@ -15,7 +16,7 @@ pub fn make_subcommand() -> Command { } // Clean command implementation -pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> { +pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let book = MDBook::load(book_dir)?; @@ -47,7 +48,7 @@ pub struct Clean { } impl Clean { - fn new(dir: &PathBuf) -> mdbook::errors::Result { + fn new(dir: &PathBuf) -> Result { let mut files = vec![dir.clone()]; let mut children = Vec::new(); let mut num_files_removed = 0; diff --git a/src/cmd/init.rs b/src/cmd/init.rs index b31beee9..ddfeaad4 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,8 +1,8 @@ use crate::get_book_dir; +use anyhow::Result; use clap::{ArgMatches, Command as ClapCommand, arg}; use mdbook::MDBook; use mdbook::config; -use mdbook::errors::Result; use std::io; use std::io::Write; use std::process::Command; diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index e3a0abdd..85866260 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -2,6 +2,7 @@ use super::command_prelude::*; #[cfg(feature = "watch")] use super::watch; use crate::{get_book_dir, open}; +use anyhow::Result; use axum::Router; use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade}; use axum::routing::get; @@ -9,7 +10,6 @@ use clap::builder::NonEmptyStringValueParser; use futures_util::StreamExt; use futures_util::sink::SinkExt; use mdbook::MDBook; -use mdbook::errors::*; use mdbook::utils::fs::get_404_output_file; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; diff --git a/src/cmd/test.rs b/src/cmd/test.rs index d32410f6..fecbc8b0 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -1,9 +1,9 @@ use super::command_prelude::*; use crate::get_book_dir; +use anyhow::Result; use clap::ArgAction; use clap::builder::NonEmptyStringValueParser; use mdbook::MDBook; -use mdbook::errors::Result; use std::path::PathBuf; // Create clap subcommand arguments diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index b20cbd11..8050079e 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,7 +1,7 @@ use super::command_prelude::*; use crate::{get_book_dir, open}; +use anyhow::Result; use mdbook::MDBook; -use mdbook::errors::Result; use std::path::{Path, PathBuf}; mod native; diff --git a/src/config.rs b/src/config.rs index ef0f5dfd..394154ae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ //! # Examples //! //! ```rust -//! # use mdbook::errors::*; +//! # use anyhow::Result; //! use std::path::PathBuf; //! use std::str::FromStr; //! use mdbook::Config; @@ -47,6 +47,8 @@ //! # run().unwrap() //! ``` +use crate::utils::{self, toml_ext::TomlExt}; +use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; @@ -58,9 +60,6 @@ use std::str::FromStr; use toml::Value; use toml::value::Table; -use crate::errors::*; -use crate::utils::{self, toml_ext::TomlExt}; - /// The overall configuration object for MDBook, essentially an in-memory /// representation of `book.toml`. #[derive(Debug, Clone, PartialEq)] diff --git a/src/front-end/mod.rs b/src/front-end/mod.rs index 8fd09fc2..fe7b4a2f 100644 --- a/src/front-end/mod.rs +++ b/src/front-end/mod.rs @@ -1,18 +1,16 @@ #![allow(missing_docs)] -pub mod playground_editor; - -pub mod fonts; - -#[cfg(feature = "search")] -pub mod searcher; - +use anyhow::Result; +use log::warn; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; -use crate::errors::*; -use log::warn; +pub mod fonts; +pub mod playground_editor; +#[cfg(feature = "search")] +pub mod searcher; + pub static INDEX: &[u8] = include_bytes!("templates/index.hbs"); pub static HEAD: &[u8] = include_bytes!("templates/head.hbs"); pub static REDIRECT: &[u8] = include_bytes!("templates/redirect.hbs"); diff --git a/src/lib.rs b/src/lib.rs index 73195279..7abd2e15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,9 +93,3 @@ pub use crate::book::MDBook; pub use crate::config::Config; pub use crate::renderer::Renderer; pub use mdbook_core::MDBOOK_VERSION; - -/// The error types used through out this crate. -pub mod errors { - pub(crate) use anyhow::{Context, bail, ensure}; - pub use anyhow::{Error, Result}; -} diff --git a/src/preprocess/cmd.rs b/src/preprocess/cmd.rs index 149dabda..dfd0681e 100644 --- a/src/preprocess/cmd.rs +++ b/src/preprocess/cmd.rs @@ -1,6 +1,6 @@ use super::{Preprocessor, PreprocessorContext}; use crate::book::Book; -use crate::errors::*; +use anyhow::{Context, Result, bail, ensure}; use log::{debug, trace, warn}; use shlex::Shlex; use std::io::{self, Read, Write}; diff --git a/src/preprocess/index.rs b/src/preprocess/index.rs index 1e58e294..3b2666d7 100644 --- a/src/preprocess/index.rs +++ b/src/preprocess/index.rs @@ -3,7 +3,7 @@ use std::{path::Path, sync::LazyLock}; use super::{Preprocessor, PreprocessorContext}; use crate::book::{Book, BookItem}; -use crate::errors::*; +use anyhow::Result; use log::warn; /// A preprocessor for converting file name `README.md` to `index.md` since diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index 93eaf813..d431ea20 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -1,8 +1,8 @@ -use crate::errors::*; use crate::utils::{ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, }; +use anyhow::{Context, Result}; use regex::{CaptureMatches, Captures, Regex}; use std::fs; use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo}; diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index df01a3db..98daf94e 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -10,7 +10,7 @@ mod links; use crate::book::Book; use crate::config::Config; -use crate::errors::*; +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::cell::RefCell; diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 98203b60..4bef3cb3 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -1,11 +1,11 @@ use crate::book::{Book, BookItem}; use crate::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; -use crate::errors::*; use crate::renderer::html_handlebars::StaticFiles; use crate::renderer::html_handlebars::helpers; use crate::renderer::{RenderContext, Renderer}; use crate::theme::{self, Theme}; use crate::utils; +use crate::utils::fs::get_404_output_file; use std::borrow::Cow; use std::collections::BTreeMap; @@ -14,7 +14,7 @@ use std::fs::{self, File}; use std::path::{Path, PathBuf}; use std::sync::LazyLock; -use crate::utils::fs::get_404_output_file; +use anyhow::{Context, Result, bail}; use handlebars::Handlebars; use log::{debug, info, trace, warn}; use regex::{Captures, Regex}; diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index ea8ae422..3299257d 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -3,17 +3,17 @@ use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::LazyLock; +use anyhow::{Context, Result, bail}; use elasticlunr::{Index, IndexBuilder}; +use log::{debug, warn}; use pulldown_cmark::*; +use serde::Serialize; use crate::book::{Book, BookItem, Chapter}; use crate::config::{Search, SearchChapterSettings}; -use crate::errors::*; use crate::renderer::html_handlebars::StaticFiles; use crate::theme::searcher; use crate::utils; -use log::{debug, warn}; -use serde::Serialize; const MAX_WORD_LENGTH_TO_INDEX: usize = 80; diff --git a/src/renderer/html_handlebars/static_files.rs b/src/renderer/html_handlebars/static_files.rs index a3cd1322..1b6aa9f7 100644 --- a/src/renderer/html_handlebars/static_files.rs +++ b/src/renderer/html_handlebars/static_files.rs @@ -1,9 +1,9 @@ //! Support for writing static files. +use anyhow::{Context, Result}; use log::{debug, warn}; use crate::config::HtmlConfig; -use crate::errors::*; use crate::renderer::html_handlebars::helpers::resources::ResourceHelper; use crate::theme::{self, Theme, playground_editor}; use crate::utils; diff --git a/src/renderer/markdown_renderer.rs b/src/renderer/markdown_renderer.rs index 4a5a5c2a..ac80aaa2 100644 --- a/src/renderer/markdown_renderer.rs +++ b/src/renderer/markdown_renderer.rs @@ -1,7 +1,7 @@ use crate::book::BookItem; -use crate::errors::*; use crate::renderer::{RenderContext, Renderer}; use crate::utils; +use anyhow::{Context, Result}; use log::trace; use std::fs; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 1c97f8f2..b06983d2 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -11,26 +11,24 @@ //! [For Developers]: https://rust-lang.github.io/mdBook/for_developers/index.html //! [RenderContext]: struct.RenderContext.html -pub use self::html_handlebars::HtmlHandlebars; -pub use self::markdown_renderer::MarkdownRenderer; - -mod html_handlebars; -mod markdown_renderer; - +use crate::book::Book; +use crate::config::Config; +use anyhow::{Context, Result, bail}; +use log::{error, info, trace, warn}; +use serde::{Deserialize, Serialize}; use shlex::Shlex; use std::collections::HashMap; use std::fs; use std::io::{self, ErrorKind, Read}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; - -use crate::book::Book; -use crate::config::Config; -use crate::errors::*; -use log::{error, info, trace, warn}; use toml::Value; -use serde::{Deserialize, Serialize}; +pub use self::html_handlebars::HtmlHandlebars; +pub use self::markdown_renderer::MarkdownRenderer; + +mod html_handlebars; +mod markdown_renderer; /// An arbitrary `mdbook` backend. /// diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 4d07f8fc..3d126b7a 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -1,6 +1,6 @@ //! Filesystem utilities and helpers. -use crate::errors::*; +use anyhow::{Context, Result}; use log::{debug, trace}; use std::fs::{self, File}; use std::io::Write; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 14fdd0ad..50feb025 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,19 +1,19 @@ //! Various helpers and utilities. -pub mod fs; -mod string; -pub(crate) mod toml_ext; -use crate::errors::Error; +use anyhow::Error; use log::error; use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd, html}; use regex::Regex; - use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Write; use std::path::Path; use std::sync::LazyLock; +pub mod fs; +mod string; +pub(crate) mod toml_ext; + pub use self::string::{ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index db8322af..b2188db1 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -1,8 +1,8 @@ //! Tests for custom preprocessors. use crate::prelude::*; +use anyhow::Result; use mdbook::book::Book; -use mdbook::errors::Result; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; use std::sync::{Arc, Mutex}; diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index 1e162447..45739d09 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -1,7 +1,7 @@ //! Tests for custom renderers. use crate::prelude::*; -use mdbook::errors::Result; +use anyhow::Result; use mdbook::renderer::{RenderContext, Renderer}; use snapbox::IntoData; use std::fs::File; diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs index eafbdc48..35b5e5ab 100644 --- a/tests/testsuite/toc.rs +++ b/tests/testsuite/toc.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use anyhow::Context; -use mdbook::errors::*; +use anyhow::Result; use select::document::Document; use select::predicate::{Attr, Class, Name, Predicate}; use std::fs; From a224bfd7d7a669c609e2f375b5057e788e7aca96 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 11:57:58 -0700 Subject: [PATCH 12/44] Move utils to mdbook-core This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-core. Additional commits will refactor/move/remove items. --- {src => crates/mdbook-core/src}/utils/fs.rs | 0 {src => crates/mdbook-core/src}/utils/mod.rs | 0 {src => crates/mdbook-core/src}/utils/string.rs | 0 {src => crates/mdbook-core/src}/utils/toml_ext.rs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {src => crates/mdbook-core/src}/utils/fs.rs (100%) rename {src => crates/mdbook-core/src}/utils/mod.rs (100%) rename {src => crates/mdbook-core/src}/utils/string.rs (100%) rename {src => crates/mdbook-core/src}/utils/toml_ext.rs (100%) diff --git a/src/utils/fs.rs b/crates/mdbook-core/src/utils/fs.rs similarity index 100% rename from src/utils/fs.rs rename to crates/mdbook-core/src/utils/fs.rs diff --git a/src/utils/mod.rs b/crates/mdbook-core/src/utils/mod.rs similarity index 100% rename from src/utils/mod.rs rename to crates/mdbook-core/src/utils/mod.rs diff --git a/src/utils/string.rs b/crates/mdbook-core/src/utils/string.rs similarity index 100% rename from src/utils/string.rs rename to crates/mdbook-core/src/utils/string.rs diff --git a/src/utils/toml_ext.rs b/crates/mdbook-core/src/utils/toml_ext.rs similarity index 100% rename from src/utils/toml_ext.rs rename to crates/mdbook-core/src/utils/toml_ext.rs From fc76a47d6e496bd7de3c68fc82ffeee46b89f7e9 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 12:20:21 -0700 Subject: [PATCH 13/44] Finish move of utils to mdbook-core This updates everything for the move of utils to mdbook-core. There will be followup commits that will be moving and refactoring these utils. This simply moves them over unchanged (except visibility). --- Cargo.lock | 5 +++++ Cargo.toml | 15 ++++++++++----- crates/mdbook-core/Cargo.toml | 7 +++++++ crates/mdbook-core/src/lib.rs | 1 + crates/mdbook-core/src/utils/fs.rs | 2 +- crates/mdbook-core/src/utils/mod.rs | 8 +++++--- crates/mdbook-core/src/utils/toml_ext.rs | 9 ++++++++- src/book/book.rs | 2 +- src/book/init.rs | 2 +- src/book/mod.rs | 2 +- src/cmd/serve.rs | 2 +- src/config.rs | 6 ++++-- src/lib.rs | 1 - src/main.rs | 2 +- src/preprocess/links.rs | 4 ++-- src/renderer/html_handlebars/hbs_renderer.rs | 4 ++-- .../html_handlebars/helpers/navigation.rs | 2 +- src/renderer/html_handlebars/helpers/resources.rs | 2 +- src/renderer/html_handlebars/helpers/toc.rs | 2 +- src/renderer/html_handlebars/search.rs | 2 +- src/renderer/html_handlebars/static_files.rs | 6 +++--- src/renderer/markdown_renderer.rs | 2 +- tests/testsuite/book_test.rs | 2 +- tests/testsuite/build.rs | 8 ++++---- tests/testsuite/markdown.rs | 8 ++++---- tests/testsuite/preprocessor.rs | 2 +- tests/testsuite/redirects.rs | 10 +++++----- tests/testsuite/renderer.rs | 10 +++++----- tests/testsuite/search.rs | 4 ++-- tests/testsuite/test.rs | 4 ++-- tests/testsuite/theme.rs | 4 ++-- 31 files changed, 84 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 732e62a9..b7555940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1299,6 +1299,11 @@ name = "mdbook-core" version = "0.5.0-alpha.1" dependencies = [ "anyhow", + "log", + "pulldown-cmark 0.10.3", + "regex", + "tempfile", + "toml", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ee570234..a8c4331c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,12 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow [workspace.dependencies] anyhow = "1.0.98" +log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } +pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. +regex = "1.11.1" +tempfile = "3.20.0" +toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037 [package] name = "mdbook" @@ -50,18 +55,18 @@ clap_complete = "4.3.2" env_logger = "0.11.1" handlebars = "6.0" hex = "0.4.3" -log = "0.4.17" +log.workspace = true mdbook-core.workspace = true memchr = "2.5.0" opener = "0.8.1" -pulldown-cmark = { version = "0.10.0", default-features = false, features = ["html"] } # Do not update, part of the public api. -regex = "1.8.1" +pulldown-cmark.workspace = true +regex.workspace = true serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" sha2 = "0.10.8" shlex = "1.3.0" -tempfile = "3.4.0" -toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037 +tempfile.workspace = true +toml.workspace = true topological-sort = "0.2.2" # Watch feature diff --git a/crates/mdbook-core/Cargo.toml b/crates/mdbook-core/Cargo.toml index 301bfe87..a92a899a 100644 --- a/crates/mdbook-core/Cargo.toml +++ b/crates/mdbook-core/Cargo.toml @@ -9,6 +9,13 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true +log.workspace = true +pulldown-cmark.workspace = true +regex.workspace = true +toml.workspace = true + +[dev-dependencies] +tempfile.workspace = true [lints] workspace = true diff --git a/crates/mdbook-core/src/lib.rs b/crates/mdbook-core/src/lib.rs index d7298926..347d6668 100644 --- a/crates/mdbook-core/src/lib.rs +++ b/crates/mdbook-core/src/lib.rs @@ -10,3 +10,4 @@ pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); pub mod errors { pub use anyhow::{Error, Result}; } +pub mod utils; diff --git a/crates/mdbook-core/src/utils/fs.rs b/crates/mdbook-core/src/utils/fs.rs index 3d126b7a..c8500a73 100644 --- a/crates/mdbook-core/src/utils/fs.rs +++ b/crates/mdbook-core/src/utils/fs.rs @@ -29,7 +29,7 @@ pub fn write_file>(build_dir: &Path, filename: P, content: &[u8]) /// /// ```rust /// # use std::path::Path; -/// # use mdbook::utils::fs::path_to_root; +/// # use mdbook_core::utils::fs::path_to_root; /// let path = Path::new("some/relative/path"); /// assert_eq!(path_to_root(path), "../../"); /// ``` diff --git a/crates/mdbook-core/src/utils/mod.rs b/crates/mdbook-core/src/utils/mod.rs index 50feb025..56bfbb97 100644 --- a/crates/mdbook-core/src/utils/mod.rs +++ b/crates/mdbook-core/src/utils/mod.rs @@ -12,7 +12,7 @@ use std::sync::LazyLock; pub mod fs; mod string; -pub(crate) mod toml_ext; +pub mod toml_ext; pub use self::string::{ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, @@ -424,7 +424,8 @@ pub fn log_backtrace(e: &Error) { } } -pub(crate) fn special_escape(mut s: &str) -> String { +/// Escape characters to make it safe for an HTML string. +pub fn special_escape(mut s: &str) -> String { let mut escaped = String::with_capacity(s.len()); let needs_escape: &[char] = &['<', '>', '\'', '"', '\\', '&']; while let Some(next) = s.find(needs_escape) { @@ -444,7 +445,8 @@ pub(crate) fn special_escape(mut s: &str) -> String { escaped } -pub(crate) fn bracket_escape(mut s: &str) -> String { +/// Escape `<` and `>` for HTML. +pub fn bracket_escape(mut s: &str) -> String { let mut escaped = String::with_capacity(s.len()); let needs_escape: &[char] = &['<', '>']; while let Some(next) = s.find(needs_escape) { diff --git a/crates/mdbook-core/src/utils/toml_ext.rs b/crates/mdbook-core/src/utils/toml_ext.rs index bf25ad11..7975ed57 100644 --- a/crates/mdbook-core/src/utils/toml_ext.rs +++ b/crates/mdbook-core/src/utils/toml_ext.rs @@ -1,9 +1,16 @@ +//! Helper for working with toml types. + use toml::value::{Table, Value}; -pub(crate) trait TomlExt { +/// Helper for working with toml types. +pub trait TomlExt { + /// Read a dotted key. fn read(&self, key: &str) -> Option<&Value>; + /// Read a dotted key for a mutable value. fn read_mut(&mut self, key: &str) -> Option<&mut Value>; + /// Insert with a dotted key. fn insert(&mut self, key: &str, value: Value); + /// Delete a dotted key value. fn delete(&mut self, key: &str) -> Option; } diff --git a/src/book/book.rs b/src/book/book.rs index 769c503d..4e902664 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -6,9 +6,9 @@ use std::path::{Path, PathBuf}; use super::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use crate::config::BuildConfig; -use crate::utils::bracket_escape; use anyhow::{Context, Result}; use log::debug; +use mdbook_core::utils::bracket_escape; use serde::{Deserialize, Serialize}; /// Load a book into memory from its `src/` directory. diff --git a/src/book/init.rs b/src/book/init.rs index b7372e33..07e8a43b 100644 --- a/src/book/init.rs +++ b/src/book/init.rs @@ -5,9 +5,9 @@ use std::path::PathBuf; use super::MDBook; use crate::config::Config; use crate::theme; -use crate::utils::fs::write_file; use anyhow::{Context, Result}; use log::{debug, error, info, trace}; +use mdbook_core::utils::fs::write_file; /// A helper for setting up a new book and its directory structure. #[derive(Debug, Clone, PartialEq)] diff --git a/src/book/mod.rs b/src/book/mod.rs index 117d6ed6..6d9cc670 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -27,7 +27,7 @@ use crate::preprocess::{ CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext, }; use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; -use crate::utils; +use mdbook_core::utils; use crate::config::{Config, RustEdition}; diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 85866260..1d3ab6b9 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -10,7 +10,7 @@ use clap::builder::NonEmptyStringValueParser; use futures_util::StreamExt; use futures_util::sink::SinkExt; use mdbook::MDBook; -use mdbook::utils::fs::get_404_output_file; +use mdbook_core::utils::fs::get_404_output_file; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; use tokio::sync::broadcast; diff --git a/src/config.rs b/src/config.rs index 394154ae..4f91ceae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -50,6 +50,8 @@ use crate::utils::{self, toml_ext::TomlExt}; use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; +use mdbook_core::utils::log_backtrace; +use mdbook_core::utils::toml_ext::TomlExt; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::env; @@ -182,7 +184,7 @@ impl Config { Ok(Some(config)) => Some(config), Ok(None) => None, Err(e) => { - utils::log_backtrace(&e); + log_backtrace(&e); None } } @@ -812,7 +814,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {} #[cfg(test)] mod tests { use super::*; - use crate::utils::fs::get_404_output_file; + use mdbook_core::utils::fs::get_404_output_file; use serde_json::json; const COMPLEX_CONFIG: &str = r#" diff --git a/src/lib.rs b/src/lib.rs index 7abd2e15..d0b1af8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,6 @@ pub mod preprocess; pub mod renderer; #[path = "front-end/mod.rs"] pub mod theme; -pub mod utils; pub use crate::book::BookItem; pub use crate::book::MDBook; diff --git a/src/main.rs b/src/main.rs index 975b5a21..e4e8d55f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use clap::{Arg, ArgMatches, Command}; use clap_complete::Shell; use env_logger::Builder; use log::LevelFilter; -use mdbook::utils; +use mdbook_core::utils; use std::env; use std::ffi::OsStr; use std::io::Write; diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index d431ea20..02d7c4d7 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -1,8 +1,8 @@ -use crate::utils::{ +use anyhow::{Context, Result}; +use mdbook_core::utils::{ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, }; -use anyhow::{Context, Result}; use regex::{CaptureMatches, Captures, Regex}; use std::fs; use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo}; diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 4bef3cb3..dd90de55 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -4,8 +4,8 @@ use crate::renderer::html_handlebars::StaticFiles; use crate::renderer::html_handlebars::helpers; use crate::renderer::{RenderContext, Renderer}; use crate::theme::{self, Theme}; -use crate::utils; -use crate::utils::fs::get_404_output_file; +use mdbook_core::utils; +use mdbook_core::utils::fs::get_404_output_file; use std::borrow::Cow; use std::collections::BTreeMap; diff --git a/src/renderer/html_handlebars/helpers/navigation.rs b/src/renderer/html_handlebars/helpers/navigation.rs index 12c69027..1fd43dcf 100644 --- a/src/renderer/html_handlebars/helpers/navigation.rs +++ b/src/renderer/html_handlebars/helpers/navigation.rs @@ -5,8 +5,8 @@ use handlebars::{ Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason, Renderable, }; -use crate::utils; use log::{debug, trace}; +use mdbook_core::utils; use serde_json::json; type StringMap = BTreeMap; diff --git a/src/renderer/html_handlebars/helpers/resources.rs b/src/renderer/html_handlebars/helpers/resources.rs index e8818f05..88adafed 100644 --- a/src/renderer/html_handlebars/helpers/resources.rs +++ b/src/renderer/html_handlebars/helpers/resources.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::utils; +use mdbook_core::utils; use handlebars::{ Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason, diff --git a/src/renderer/html_handlebars/helpers/toc.rs b/src/renderer/html_handlebars/helpers/toc.rs index a3419ce8..9528a355 100644 --- a/src/renderer/html_handlebars/helpers/toc.rs +++ b/src/renderer/html_handlebars/helpers/toc.rs @@ -1,7 +1,7 @@ use std::path::Path; use std::{cmp::Ordering, collections::BTreeMap}; -use crate::utils::special_escape; +use mdbook_core::utils::special_escape; use handlebars::{ Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason, diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 3299257d..ba9cd274 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -6,6 +6,7 @@ use std::sync::LazyLock; use anyhow::{Context, Result, bail}; use elasticlunr::{Index, IndexBuilder}; use log::{debug, warn}; +use mdbook_core::utils; use pulldown_cmark::*; use serde::Serialize; @@ -13,7 +14,6 @@ use crate::book::{Book, BookItem, Chapter}; use crate::config::{Search, SearchChapterSettings}; use crate::renderer::html_handlebars::StaticFiles; use crate::theme::searcher; -use crate::utils; const MAX_WORD_LENGTH_TO_INDEX: usize = 80; diff --git a/src/renderer/html_handlebars/static_files.rs b/src/renderer/html_handlebars/static_files.rs index 1b6aa9f7..ffd07687 100644 --- a/src/renderer/html_handlebars/static_files.rs +++ b/src/renderer/html_handlebars/static_files.rs @@ -2,11 +2,11 @@ use anyhow::{Context, Result}; use log::{debug, warn}; +use mdbook_core::utils; use crate::config::HtmlConfig; use crate::renderer::html_handlebars::helpers::resources::ResourceHelper; use crate::theme::{self, Theme, playground_editor}; -use crate::utils; use std::borrow::Cow; use std::collections::HashMap; @@ -227,7 +227,7 @@ impl StaticFiles { } pub fn write_files(self, destination: &Path) -> Result { - use crate::utils::fs::write_file; + use mdbook_core::utils::fs::write_file; use regex::bytes::{Captures, Regex}; // The `{{ resource "name" }}` directive in static resources look like // handlebars syntax, even if they technically aren't. @@ -302,7 +302,7 @@ mod tests { use super::*; use crate::config::HtmlConfig; use crate::theme::Theme; - use crate::utils::fs::write_file; + use mdbook_core::utils::fs::write_file; use tempfile::TempDir; #[test] diff --git a/src/renderer/markdown_renderer.rs b/src/renderer/markdown_renderer.rs index ac80aaa2..638b21f0 100644 --- a/src/renderer/markdown_renderer.rs +++ b/src/renderer/markdown_renderer.rs @@ -1,8 +1,8 @@ use crate::book::BookItem; use crate::renderer::{RenderContext, Renderer}; -use crate::utils; use anyhow::{Context, Result}; use log::trace; +use mdbook_core::utils; use std::fs; #[derive(Default)] diff --git a/tests/testsuite/book_test.rs b/tests/testsuite/book_test.rs index ed5ebe83..57b5f3f3 100644 --- a/tests/testsuite/book_test.rs +++ b/tests/testsuite/book_test.rs @@ -35,7 +35,7 @@ impl BookTest { let dir = Path::new("tests/testsuite").join(dir); assert!(dir.exists(), "{dir:?} should exist"); let tmp = Self::new_tmp(); - mdbook::utils::fs::copy_files_except_ext( + mdbook_core::utils::fs::copy_files_except_ext( &dir, &tmp, true, diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 1737bdf9..98972c4b 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -24,8 +24,8 @@ fn basic_build() { fn failure_on_missing_file() { BookTest::from_dir("build/missing_file").run("build", |cmd| { cmd.expect_failure().expect_stderr(str![[r#" -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Chapter file not found, ./chapter_1.md -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [NOT_FOUND] +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Chapter file not found, ./chapter_1.md +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: [NOT_FOUND] "#]]); }); @@ -48,8 +48,8 @@ fn no_reserved_filename() { cmd.expect_failure().expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: print.md is reserved for internal use +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: print.md is reserved for internal use "#]]); }); diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs index 72643369..3fc08425 100644 --- a/tests/testsuite/markdown.rs +++ b/tests/testsuite/markdown.rs @@ -22,10 +22,10 @@ fn footnotes() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [WARN] (mdbook::utils): footnote `multiple-definitions` in defined multiple times - not updating to new definition -[TIMESTAMP] [WARN] (mdbook::utils): footnote `unused` in `` is defined but not referenced -[TIMESTAMP] [WARN] (mdbook::utils): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition -[TIMESTAMP] [WARN] (mdbook::utils): footnote `unused` in `footnotes.md` is defined but not referenced +[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `multiple-definitions` in defined multiple times - not updating to new definition +[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `unused` in `` is defined but not referenced +[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition +[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `unused` in `footnotes.md` is defined but not referenced [TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index b2188db1..51f4a2a2 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -64,7 +64,7 @@ fn failing_preprocessor() { .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started Boom!!1! -[TIMESTAMP] [ERROR] (mdbook::utils): Error: The "nop-preprocessor" preprocessor exited unsuccessfully with [EXIT_STATUS]: 1 status +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: The "nop-preprocessor" preprocessor exited unsuccessfully with [EXIT_STATUS]: 1 status "#]]); }); diff --git a/tests/testsuite/redirects.rs b/tests/testsuite/redirects.rs index d92bf766..93fd9113 100644 --- a/tests/testsuite/redirects.rs +++ b/tests/testsuite/redirects.rs @@ -24,9 +24,9 @@ fn redirect_removed_with_fragments_only() { cmd.expect_failure().expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: Unable to emit redirects -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: redirect entry for `old-file.html` only has source paths with `#` fragments +[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 There must be an entry without the `#` fragment to determine the default destination. "#]]); @@ -40,8 +40,8 @@ fn redirect_existing_page() { cmd.expect_failure().expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: redirect found for existing chapter at `/chapter_1.html` +[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. "#]]); diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index 45739d09..923bd5dd 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -68,8 +68,8 @@ fn failing_command() { [TIMESTAMP] [INFO] (mdbook::book): Running the failing backend [TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "failing" renderer [TIMESTAMP] [ERROR] (mdbook::renderer): Renderer exited with non-zero return code. -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: The "failing" renderer failed +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: The "failing" renderer failed "#]]); }); @@ -86,9 +86,9 @@ fn missing_renderer() { [TIMESTAMP] [INFO] (mdbook::book): Running the missing backend [TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "missing" renderer [TIMESTAMP] [ERROR] (mdbook::renderer): 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::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: Unable to start the backend -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [NOT_FOUND] +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Rendering failed +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: Unable to start the backend +[TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: [NOT_FOUND] "#]]); }); diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs index 5eca0e43..a8f05120 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -137,8 +137,8 @@ fn chapter_settings_validation_error() { cmd.expect_failure().expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: [output.html.search.chapter] key `does-not-exist` does not match any chapter paths +[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 "#]]); }); diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index 54be8695..5fd2cc7e 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -48,7 +48,7 @@ test failing_include.md - Failing_Include (line 3) ... FAILED thread 'main' panicked at failing_include.md:3:1: failing! ... -[TIMESTAMP] [ERROR] (mdbook::utils): Error: One or more tests failed +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: One or more tests failed "#]]); }); @@ -82,7 +82,7 @@ fn chapter_not_found() { cmd.expect_failure() .expect_stdout(str![[""]]) .expect_stderr(str![[r#" -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Chapter not found: bogus +[TIMESTAMP] [ERROR] (mdbook_core::utils): Error: Chapter not found: bogus "#]]); }); diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs index b22ae4e2..8ebb0c64 100644 --- a/tests/testsuite/theme.rs +++ b/tests/testsuite/theme.rs @@ -11,8 +11,8 @@ cmd.expect_failure() .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [ERROR] (mdbook::utils): Error: Rendering failed -[TIMESTAMP] [ERROR] (mdbook::utils): [TAB]Caused By: theme dir [ROOT]/./non-existent-directory does not exist +[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 "#]]); }); From 4ae5a53791106585c769ef4ffc07248da5187b99 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 12:21:54 -0700 Subject: [PATCH 14/44] Move config to mdbook-core This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-core. Additional commits will refactor/move/remove items. --- {src => crates/mdbook-core/src}/config.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src => crates/mdbook-core/src}/config.rs (100%) diff --git a/src/config.rs b/crates/mdbook-core/src/config.rs similarity index 100% rename from src/config.rs rename to crates/mdbook-core/src/config.rs From 02b6628048f89a4147f63e74c524d26d57ffc895 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 13:26:57 -0700 Subject: [PATCH 15/44] Finish move of config to mdbook-core This updates everything for the move of config to mdbook-core. There will be followup commits that will be moving and refactoring the config. This simply moves it over unchanged. --- Cargo.lock | 2 ++ Cargo.toml | 6 ++++-- crates/mdbook-core/Cargo.toml | 2 ++ crates/mdbook-core/src/config.rs | 9 ++++----- crates/mdbook-core/src/lib.rs | 4 +++- src/book/book.rs | 2 +- src/book/init.rs | 2 +- src/book/mod.rs | 5 ++--- src/cmd/init.rs | 2 +- src/lib.rs | 7 +++---- src/preprocess/mod.rs | 17 ++++++++--------- src/renderer/html_handlebars/hbs_renderer.rs | 11 +++++------ src/renderer/html_handlebars/search.rs | 2 +- src/renderer/html_handlebars/static_files.rs | 4 ++-- src/renderer/mod.rs | 2 +- 15 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7555940..31657a6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,6 +1302,8 @@ dependencies = [ "log", "pulldown-cmark 0.10.3", "regex", + "serde", + "serde_json", "tempfile", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index a8c4331c..3428025c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. regex = "1.11.1" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" tempfile = "3.20.0" toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037 @@ -61,8 +63,8 @@ memchr = "2.5.0" opener = "0.8.1" pulldown-cmark.workspace = true regex.workspace = true -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde.workspace = true +serde_json.workspace = true sha2 = "0.10.8" shlex = "1.3.0" tempfile.workspace = true diff --git a/crates/mdbook-core/Cargo.toml b/crates/mdbook-core/Cargo.toml index a92a899a..80e9fcb0 100644 --- a/crates/mdbook-core/Cargo.toml +++ b/crates/mdbook-core/Cargo.toml @@ -12,6 +12,8 @@ anyhow.workspace = true log.workspace = true pulldown-cmark.workspace = true regex.workspace = true +serde.workspace = true +serde_json.workspace = true toml.workspace = true [dev-dependencies] diff --git a/crates/mdbook-core/src/config.rs b/crates/mdbook-core/src/config.rs index 4f91ceae..9ab564c2 100644 --- a/crates/mdbook-core/src/config.rs +++ b/crates/mdbook-core/src/config.rs @@ -12,7 +12,7 @@ //! # use anyhow::Result; //! use std::path::PathBuf; //! use std::str::FromStr; -//! use mdbook::Config; +//! use mdbook_core::config::Config; //! use toml::Value; //! //! # fn run() -> Result<()> { @@ -47,11 +47,10 @@ //! # run().unwrap() //! ``` -use crate::utils::{self, toml_ext::TomlExt}; +use crate::utils::log_backtrace; +use crate::utils::toml_ext::TomlExt; use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; -use mdbook_core::utils::log_backtrace; -use mdbook_core::utils::toml_ext::TomlExt; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::env; @@ -814,7 +813,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {} #[cfg(test)] mod tests { use super::*; - use mdbook_core::utils::fs::get_404_output_file; + use crate::utils::fs::get_404_output_file; use serde_json::json; const COMPLEX_CONFIG: &str = r#" diff --git a/crates/mdbook-core/src/lib.rs b/crates/mdbook-core/src/lib.rs index 347d6668..36878aaf 100644 --- a/crates/mdbook-core/src/lib.rs +++ b/crates/mdbook-core/src/lib.rs @@ -6,8 +6,10 @@ /// compatibility checks. pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub mod config; +pub mod utils; + /// The error types used in mdbook. pub mod errors { pub use anyhow::{Error, Result}; } -pub mod utils; diff --git a/src/book/book.rs b/src/book/book.rs index 4e902664..1242c625 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -5,9 +5,9 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use super::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; -use crate::config::BuildConfig; use anyhow::{Context, Result}; use log::debug; +use mdbook_core::config::BuildConfig; use mdbook_core::utils::bracket_escape; use serde::{Deserialize, Serialize}; diff --git a/src/book/init.rs b/src/book/init.rs index 07e8a43b..13e0d687 100644 --- a/src/book/init.rs +++ b/src/book/init.rs @@ -3,10 +3,10 @@ use std::io::Write; use std::path::PathBuf; use super::MDBook; -use crate::config::Config; use crate::theme; use anyhow::{Context, Result}; use log::{debug, error, info, trace}; +use mdbook_core::config::Config; use mdbook_core::utils::fs::write_file; /// A helper for setting up a new book and its directory structure. diff --git a/src/book/mod.rs b/src/book/mod.rs index 6d9cc670..d3433b4e 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -15,6 +15,8 @@ pub use self::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary use anyhow::{Context, Error, Result, bail}; use log::{debug, error, info, log_enabled, trace, warn}; +use mdbook_core::config::{Config, RustEdition}; +use mdbook_core::utils; use std::ffi::OsString; use std::io::{IsTerminal, Write}; use std::path::{Path, PathBuf}; @@ -27,9 +29,6 @@ use crate::preprocess::{ CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext, }; use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; -use mdbook_core::utils; - -use crate::config::{Config, RustEdition}; /// The object used to manage and build a book. pub struct MDBook { diff --git a/src/cmd/init.rs b/src/cmd/init.rs index ddfeaad4..386ea4aa 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -2,7 +2,7 @@ use crate::get_book_dir; use anyhow::Result; use clap::{ArgMatches, Command as ClapCommand, arg}; use mdbook::MDBook; -use mdbook::config; +use mdbook_core::config; use std::io; use std::io::Write; use std::process::Command; diff --git a/src/lib.rs b/src/lib.rs index d0b1af8b..76c744f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ //! //! ```rust,no_run //! use mdbook::MDBook; -//! use mdbook::config::Config; +//! use mdbook_core::config::Config; //! //! let root_dir = "/path/to/book/root"; //! @@ -78,10 +78,9 @@ //! [user guide]: https://rust-lang.github.io/mdBook/ //! [`RenderContext`]: renderer::RenderContext //! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html -//! [`Config`]: config::Config +//! [`Config`]: mdbook_core::config::Config pub mod book; -pub mod config; pub mod preprocess; pub mod renderer; #[path = "front-end/mod.rs"] @@ -89,6 +88,6 @@ pub mod theme; pub use crate::book::BookItem; pub use crate::book::MDBook; -pub use crate::config::Config; pub use crate::renderer::Renderer; pub use mdbook_core::MDBOOK_VERSION; +pub use mdbook_core::config::Config; diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index 98daf94e..1134ad4d 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -1,5 +1,13 @@ //! Book preprocessing. +use crate::book::Book; +use anyhow::Result; +use mdbook_core::config::Config; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::path::PathBuf; + pub use self::cmd::CmdPreprocessor; pub use self::index::IndexPreprocessor; pub use self::links::LinkPreprocessor; @@ -8,15 +16,6 @@ mod cmd; mod index; mod links; -use crate::book::Book; -use crate::config::Config; -use anyhow::Result; - -use serde::{Deserialize, Serialize}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::path::PathBuf; - /// Extra information for a `Preprocessor` to give them more context when /// processing a book. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index dd90de55..e601c82b 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -1,11 +1,8 @@ use crate::book::{Book, BookItem}; -use crate::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; use crate::renderer::html_handlebars::StaticFiles; use crate::renderer::html_handlebars::helpers; use crate::renderer::{RenderContext, Renderer}; use crate::theme::{self, Theme}; -use mdbook_core::utils; -use mdbook_core::utils::fs::get_404_output_file; use std::borrow::Cow; use std::collections::BTreeMap; @@ -17,6 +14,9 @@ use std::sync::LazyLock; use anyhow::{Context, Result, bail}; use handlebars::Handlebars; use log::{debug, info, trace, warn}; +use mdbook_core::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; +use mdbook_core::utils; +use mdbook_core::utils::fs::get_404_output_file; use regex::{Captures, Regex}; use serde_json::json; @@ -400,7 +400,7 @@ impl Renderer for HtmlHandlebars { // Render search index #[cfg(feature = "search")] { - let default = crate::config::Search::default(); + let default = mdbook_core::config::Search::default(); let search = html_config.search.as_ref().unwrap_or(&default); if search.enable { super::search::create_files(&search, &mut static_files, &book)?; @@ -1008,9 +1008,8 @@ fn collect_redirects_for_path( #[cfg(test)] mod tests { - use crate::config::TextDirection; - use super::*; + use mdbook_core::config::TextDirection; use pretty_assertions::assert_eq; #[test] diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index ba9cd274..054e16d9 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -6,12 +6,12 @@ use std::sync::LazyLock; use anyhow::{Context, Result, bail}; use elasticlunr::{Index, IndexBuilder}; use log::{debug, warn}; +use mdbook_core::config::{Search, SearchChapterSettings}; use mdbook_core::utils; use pulldown_cmark::*; use serde::Serialize; use crate::book::{Book, BookItem, Chapter}; -use crate::config::{Search, SearchChapterSettings}; use crate::renderer::html_handlebars::StaticFiles; use crate::theme::searcher; diff --git a/src/renderer/html_handlebars/static_files.rs b/src/renderer/html_handlebars/static_files.rs index ffd07687..de03585e 100644 --- a/src/renderer/html_handlebars/static_files.rs +++ b/src/renderer/html_handlebars/static_files.rs @@ -2,9 +2,9 @@ use anyhow::{Context, Result}; use log::{debug, warn}; +use mdbook_core::config::HtmlConfig; use mdbook_core::utils; -use crate::config::HtmlConfig; use crate::renderer::html_handlebars::helpers::resources::ResourceHelper; use crate::theme::{self, Theme, playground_editor}; @@ -300,8 +300,8 @@ impl StaticFiles { #[cfg(test)] mod tests { use super::*; - use crate::config::HtmlConfig; use crate::theme::Theme; + use mdbook_core::config::HtmlConfig; use mdbook_core::utils::fs::write_file; use tempfile::TempDir; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b06983d2..ce4bc025 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -12,9 +12,9 @@ //! [RenderContext]: struct.RenderContext.html use crate::book::Book; -use crate::config::Config; use anyhow::{Context, Result, bail}; use log::{error, info, trace, warn}; +use mdbook_core::config::Config; use serde::{Deserialize, Serialize}; use shlex::Shlex; use std::collections::HashMap; From bd3e5559622257202ad0d91eb8191677114d72a1 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 13:43:56 -0700 Subject: [PATCH 16/44] Add mdbook-summary This new crate will hold the Summary types and parsing support. --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/mdbook-summary/Cargo.toml | 13 +++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 crates/mdbook-summary/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 31657a6e..0fbbeceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1333,6 +1333,10 @@ dependencies = [ "mdbook-core", ] +[[package]] +name = "mdbook-summary" +version = "0.5.0-alpha.1" + [[package]] name = "memchr" version = "2.7.5" diff --git a/Cargo.toml b/Cargo.toml index 3428025c..9db4957b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow anyhow = "1.0.98" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } +mdbook-summary = { path = "crates/mdbook-summary" } pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } diff --git a/crates/mdbook-summary/Cargo.toml b/crates/mdbook-summary/Cargo.toml new file mode 100644 index 00000000..7315091e --- /dev/null +++ b/crates/mdbook-summary/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mdbook-summary" +version = "0.5.0-alpha.1" +description = "Summary parser for mdBook" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] + +[lints] +workspace = true From 29f936b1eb973e980caaa55e17130d7c6abe3552 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 13:47:44 -0700 Subject: [PATCH 17/44] Move summary to mdbook-summary This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-summary. Additional commits will refactor/move/remove items. --- src/book/summary.rs => crates/mdbook-summary/src/lib.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/book/summary.rs => crates/mdbook-summary/src/lib.rs (100%) diff --git a/src/book/summary.rs b/crates/mdbook-summary/src/lib.rs similarity index 100% rename from src/book/summary.rs rename to crates/mdbook-summary/src/lib.rs From 7bcdfe6f0f05f2a476000497c86df4fa100e9b32 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 13:55:41 -0700 Subject: [PATCH 18/44] Finish move of summary to mdbook-summary This updates everything for the move of summary to mdbook-summary. There will be followup commits that will be doing more cleanup here. --- Cargo.lock | 8 ++++++++ Cargo.toml | 4 +++- crates/mdbook-summary/Cargo.toml | 5 +++++ crates/mdbook-summary/src/lib.rs | 2 ++ src/book/book.rs | 2 +- src/book/mod.rs | 3 +-- 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fbbeceb..475fb147 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,7 @@ dependencies = [ "ignore", "log", "mdbook-core", + "mdbook-summary", "memchr", "notify", "notify-debouncer-mini", @@ -1336,6 +1337,13 @@ dependencies = [ [[package]] name = "mdbook-summary" version = "0.5.0-alpha.1" +dependencies = [ + "anyhow", + "log", + "memchr", + "pulldown-cmark 0.10.3", + "serde", +] [[package]] name = "memchr" diff --git a/Cargo.toml b/Cargo.toml index 9db4957b..16858e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ anyhow = "1.0.98" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } mdbook-summary = { path = "crates/mdbook-summary" } +memchr = "2.7.5" pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } @@ -60,7 +61,8 @@ handlebars = "6.0" hex = "0.4.3" log.workspace = true mdbook-core.workspace = true -memchr = "2.5.0" +mdbook-summary.workspace = true +memchr.workspace = true opener = "0.8.1" pulldown-cmark.workspace = true regex.workspace = true diff --git a/crates/mdbook-summary/Cargo.toml b/crates/mdbook-summary/Cargo.toml index 7315091e..94a59248 100644 --- a/crates/mdbook-summary/Cargo.toml +++ b/crates/mdbook-summary/Cargo.toml @@ -8,6 +8,11 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow.workspace = true +log.workspace = true +memchr.workspace = true +pulldown-cmark.workspace = true +serde.workspace = true [lints] workspace = true diff --git a/crates/mdbook-summary/src/lib.rs b/crates/mdbook-summary/src/lib.rs index 72021842..cacb753e 100644 --- a/crates/mdbook-summary/src/lib.rs +++ b/crates/mdbook-summary/src/lib.rs @@ -1,3 +1,5 @@ +//! Summary parser for mdBook. + use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; use memchr::Memchr; diff --git a/src/book/book.rs b/src/book/book.rs index 1242c625..e7f92f4e 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -4,11 +4,11 @@ use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use super::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use anyhow::{Context, Result}; use log::debug; use mdbook_core::config::BuildConfig; use mdbook_core::utils::bracket_escape; +use mdbook_summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use serde::{Deserialize, Serialize}; /// Load a book into memory from its `src/` directory. diff --git a/src/book/mod.rs b/src/book/mod.rs index d3433b4e..07e2b4d2 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -7,16 +7,15 @@ mod book; mod init; -mod summary; pub use self::book::{Book, BookItem, BookItems, Chapter, load_book}; pub use self::init::BookBuilder; -pub use self::summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use anyhow::{Context, Error, Result, bail}; use log::{debug, error, info, log_enabled, trace, warn}; use mdbook_core::config::{Config, RustEdition}; use mdbook_core::utils; +pub use mdbook_summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; use std::ffi::OsString; use std::io::{IsTerminal, Write}; use std::path::{Path, PathBuf}; From e123879c8cdcf52a3ebe961aae556176f51d8ea4 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 14:47:11 -0700 Subject: [PATCH 19/44] Move Book to mdbook-core This moves the Book definition to mdbook-core, along with related types it needs. --- Cargo.lock | 1 + crates/mdbook-core/src/book.rs | 242 +++++++++++++++++++ crates/mdbook-core/src/book/tests.rs | 124 ++++++++++ crates/mdbook-core/src/lib.rs | 1 + crates/mdbook-summary/Cargo.toml | 1 + crates/mdbook-summary/src/lib.rs | 55 +---- src/book/book.rs | 340 ++------------------------- src/book/mod.rs | 6 +- 8 files changed, 389 insertions(+), 381 deletions(-) create mode 100644 crates/mdbook-core/src/book.rs create mode 100644 crates/mdbook-core/src/book/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 475fb147..337aa70f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1340,6 +1340,7 @@ version = "0.5.0-alpha.1" dependencies = [ "anyhow", "log", + "mdbook-core", "memchr", "pulldown-cmark 0.10.3", "serde", diff --git a/crates/mdbook-core/src/book.rs b/crates/mdbook-core/src/book.rs new file mode 100644 index 00000000..0f8048bc --- /dev/null +++ b/crates/mdbook-core/src/book.rs @@ -0,0 +1,242 @@ +//! A tree structure representing a book. + +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; +use std::fmt::{self, Display, Formatter}; +use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; + +/// A tree structure representing a book. +/// +/// For the moment a book is just a collection of [`BookItems`] which are +/// accessible by either iterating (immutably) over the book with [`iter()`], or +/// recursively applying a closure to each section to mutate the chapters, using +/// [`for_each_mut()`]. +/// +/// [`iter()`]: #method.iter +/// [`for_each_mut()`]: #method.for_each_mut +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct Book { + /// The sections in this book. + pub sections: Vec, + __non_exhaustive: (), +} + +impl Book { + /// Create an empty book. + pub fn new() -> Self { + Default::default() + } + + /// Creates a new book with the given items. + pub fn new_with_items(items: Vec) -> Book { + Book { + sections: items, + __non_exhaustive: (), + } + } + + /// Get a depth-first iterator over the items in the book. + pub fn iter(&self) -> BookItems<'_> { + BookItems { + items: self.sections.iter().collect(), + } + } + + /// Recursively apply a closure to each item in the book, allowing you to + /// mutate them. + /// + /// # Note + /// + /// Unlike the `iter()` method, this requires a closure instead of returning + /// an iterator. This is because using iterators can possibly allow you + /// to have iterator invalidation errors. + pub fn for_each_mut(&mut self, mut func: F) + where + F: FnMut(&mut BookItem), + { + for_each_mut(&mut func, &mut self.sections); + } + + /// Append a `BookItem` to the `Book`. + pub fn push_item>(&mut self, item: I) -> &mut Self { + self.sections.push(item.into()); + self + } +} + +fn for_each_mut<'a, F, I>(func: &mut F, items: I) +where + F: FnMut(&mut BookItem), + I: IntoIterator, +{ + for item in items { + if let BookItem::Chapter(ch) = item { + for_each_mut(func, &mut ch.sub_items); + } + + func(item); + } +} + +/// Enum representing any type of item which can be added to a book. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum BookItem { + /// A nested chapter. + Chapter(Chapter), + /// A section separator. + Separator, + /// A part title. + PartTitle(String), +} + +impl From for BookItem { + fn from(other: Chapter) -> BookItem { + BookItem::Chapter(other) + } +} + +/// The representation of a "chapter", usually mapping to a single file on +/// disk however it may contain multiple sub-chapters. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct Chapter { + /// The chapter's name. + pub name: String, + /// The chapter's contents. + pub content: String, + /// The chapter's section number, if it has one. + pub number: Option, + /// Nested items. + pub sub_items: Vec, + /// The chapter's location, relative to the `SUMMARY.md` file. + /// + /// **Note**: After the index preprocessor runs, any README files will be + /// modified to be `index.md`. If you need access to the actual filename + /// on disk, use [`Chapter::source_path`] instead. + /// + /// This is `None` for a draft chapter. + pub path: Option, + /// The chapter's source file, relative to the `SUMMARY.md` file. + /// + /// **Note**: Beware that README files will internally be treated as + /// `index.md` via the [`Chapter::path`] field. The `source_path` field + /// exists if you need access to the true file path. + /// + /// This is `None` for a draft chapter, or a synthetically generated + /// chapter that has no file on disk. + pub source_path: Option, + /// An ordered list of the names of each chapter above this one in the hierarchy. + pub parent_names: Vec, +} + +impl Chapter { + /// Create a new chapter with the provided content. + pub fn new>( + name: &str, + content: String, + p: P, + parent_names: Vec, + ) -> Chapter { + let path: PathBuf = p.into(); + Chapter { + name: name.to_string(), + content, + path: Some(path.clone()), + source_path: Some(path), + parent_names, + ..Default::default() + } + } + + /// Create a new draft chapter that is not attached to a source markdown file (and thus + /// has no content). + pub fn new_draft(name: &str, parent_names: Vec) -> Self { + Chapter { + name: name.to_string(), + content: String::new(), + path: None, + source_path: None, + parent_names, + ..Default::default() + } + } + + /// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file. + pub fn is_draft_chapter(&self) -> bool { + self.path.is_none() + } +} + +impl Display for Chapter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(ref section_number) = self.number { + write!(f, "{section_number} ")?; + } + + write!(f, "{}", self.name) + } +} + +/// A section number like "1.2.3", basically just a newtype'd `Vec` with +/// a pretty `Display` impl. +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] +pub struct SectionNumber(pub Vec); + +impl Display for SectionNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if self.0.is_empty() { + write!(f, "0") + } else { + for item in &self.0 { + write!(f, "{item}.")?; + } + Ok(()) + } + } +} + +impl Deref for SectionNumber { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SectionNumber { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl FromIterator for SectionNumber { + fn from_iter>(it: I) -> Self { + SectionNumber(it.into_iter().collect()) + } +} + +/// A depth-first iterator over the items in a book. +/// +/// # Note +/// +/// This struct shouldn't be created directly, instead prefer the +/// [`Book::iter()`] method. +pub struct BookItems<'a> { + items: VecDeque<&'a BookItem>, +} + +impl<'a> Iterator for BookItems<'a> { + type Item = &'a BookItem; + + fn next(&mut self) -> Option { + let item = self.items.pop_front(); + + if let Some(BookItem::Chapter(ch)) = item { + // if we wanted a breadth-first iterator we'd `extend()` here + for sub_item in ch.sub_items.iter().rev() { + self.items.push_front(sub_item); + } + } + + item + } +} diff --git a/crates/mdbook-core/src/book/tests.rs b/crates/mdbook-core/src/book/tests.rs new file mode 100644 index 00000000..b0b33a08 --- /dev/null +++ b/crates/mdbook-core/src/book/tests.rs @@ -0,0 +1,124 @@ +use super::*; + +#[test] +fn section_number_has_correct_dotted_representation() { + let inputs = vec![ + (vec![0], "0."), + (vec![1, 3], "1.3."), + (vec![1, 2, 3], "1.2.3."), + ]; + + for (input, should_be) in inputs { + let section_number = SectionNumber(input).to_string(); + assert_eq!(section_number, should_be); + } +} + + #[test] + fn book_iter_iterates_over_sequential_items() { + let sections = vec![ + BookItem::Chapter(Chapter { + name: String::from("Chapter 1"), + content: String::from("# Chapter 1"), + ..Default::default() + }), + BookItem::Separator, + ]; + let book = Book::new_with_sections(sections); + + let should_be: Vec<_> = book.sections.iter().collect(); + + let got: Vec<_> = book.iter().collect(); + + assert_eq!(got, should_be); + } + + #[test] + fn for_each_mut_visits_all_items() { + let sections = vec![ + BookItem::Chapter(Chapter { + name: String::from("Chapter 1"), + content: String::from("# Chapter 1"), + number: None, + path: Some(PathBuf::from("Chapter_1/index.md")), + source_path: Some(PathBuf::from("Chapter_1/index.md")), + parent_names: Vec::new(), + sub_items: vec![ + BookItem::Chapter(Chapter::new( + "Hello World", + String::new(), + "Chapter_1/hello.md", + Vec::new(), + )), + BookItem::Separator, + BookItem::Chapter(Chapter::new( + "Goodbye World", + String::new(), + "Chapter_1/goodbye.md", + Vec::new(), + )), + ], + }), + BookItem::Separator, + ]; + let mut book = Book::new_with_sections(sections); + + let num_items = book.iter().count(); + let mut visited = 0; + + book.for_each_mut(|_| visited += 1); + + assert_eq!(visited, num_items); + } + + #[test] + fn iterate_over_nested_book_items() { + let sections = vec![ + BookItem::Chapter(Chapter { + name: String::from("Chapter 1"), + content: String::from("# Chapter 1"), + number: None, + path: Some(PathBuf::from("Chapter_1/index.md")), + source_path: Some(PathBuf::from("Chapter_1/index.md")), + parent_names: Vec::new(), + sub_items: vec![ + BookItem::Chapter(Chapter::new( + "Hello World", + String::new(), + "Chapter_1/hello.md", + Vec::new(), + )), + BookItem::Separator, + BookItem::Chapter(Chapter::new( + "Goodbye World", + String::new(), + "Chapter_1/goodbye.md", + Vec::new(), + )), + ], + }), + BookItem::Separator, + ]; + let book = Book::new_with_sections(sections); + + let got: Vec<_> = book.iter().collect(); + + assert_eq!(got.len(), 5); + + // checking the chapter names are in the order should be sufficient here... + let chapter_names: Vec = got + .into_iter() + .filter_map(|i| match *i { + BookItem::Chapter(ref ch) => Some(ch.name.clone()), + _ => None, + }) + .collect(); + let should_be: Vec<_> = vec![ + String::from("Chapter 1"), + String::from("Hello World"), + String::from("Goodbye World"), + ]; + + assert_eq!(chapter_names, should_be); + } + diff --git a/crates/mdbook-core/src/lib.rs b/crates/mdbook-core/src/lib.rs index 36878aaf..959ac9b0 100644 --- a/crates/mdbook-core/src/lib.rs +++ b/crates/mdbook-core/src/lib.rs @@ -6,6 +6,7 @@ /// compatibility checks. pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub mod book; pub mod config; pub mod utils; diff --git a/crates/mdbook-summary/Cargo.toml b/crates/mdbook-summary/Cargo.toml index 94a59248..4d52abb2 100644 --- a/crates/mdbook-summary/Cargo.toml +++ b/crates/mdbook-summary/Cargo.toml @@ -10,6 +10,7 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true log.workspace = true +mdbook-core.workspace = true memchr.workspace = true pulldown-cmark.workspace = true serde.workspace = true diff --git a/crates/mdbook-summary/src/lib.rs b/crates/mdbook-summary/src/lib.rs index cacb753e..ee1f74b3 100644 --- a/crates/mdbook-summary/src/lib.rs +++ b/crates/mdbook-summary/src/lib.rs @@ -2,12 +2,12 @@ use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; +pub use mdbook_core::book::SectionNumber; use memchr::Memchr; use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; -use std::fmt::{self, Display, Formatter}; -use std::ops::{Deref, DerefMut}; +use std::fmt::Display; use std::path::{Path, PathBuf}; /// Parse the text from a `SUMMARY.md` file into a sort of "recipe" to be @@ -635,61 +635,10 @@ fn stringify_events(events: Vec>) -> String { .collect() } -/// A section number like "1.2.3", basically just a newtype'd `Vec` with -/// a pretty `Display` impl. -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] -pub struct SectionNumber(pub Vec); - -impl Display for SectionNumber { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.0.is_empty() { - write!(f, "0") - } else { - for item in &self.0 { - write!(f, "{item}.")?; - } - Ok(()) - } - } -} - -impl Deref for SectionNumber { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SectionNumber { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FromIterator for SectionNumber { - fn from_iter>(it: I) -> Self { - SectionNumber(it.into_iter().collect()) - } -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn section_number_has_correct_dotted_representation() { - let inputs = vec![ - (vec![0], "0."), - (vec![1, 3], "1.3."), - (vec![1, 2, 3], "1.2.3."), - ]; - - for (input, should_be) in inputs { - let section_number = SectionNumber(input).to_string(); - assert_eq!(section_number, should_be); - } - } - #[test] fn parse_initial_title() { let src = "# Summary"; diff --git a/src/book/book.rs b/src/book/book.rs index e7f92f4e..d2866c47 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -1,15 +1,12 @@ -use std::collections::VecDeque; -use std::fmt::{self, Display, Formatter}; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; - use anyhow::{Context, Result}; use log::debug; +use mdbook_core::book::{Book, BookItem, Chapter}; use mdbook_core::config::BuildConfig; use mdbook_core::utils::bracket_escape; -use mdbook_summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; -use serde::{Deserialize, Serialize}; +use mdbook_summary::{Link, Summary, SummaryItem, parse_summary}; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::Path; /// Load a book into memory from its `src/` directory. pub fn load_book>(src_dir: P, cfg: &BuildConfig) -> Result { @@ -65,159 +62,6 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> { Ok(()) } -/// A dumb tree structure representing a book. -/// -/// For the moment a book is just a collection of [`BookItems`] which are -/// accessible by either iterating (immutably) over the book with [`iter()`], or -/// recursively applying a closure to each section to mutate the chapters, using -/// [`for_each_mut()`]. -/// -/// [`iter()`]: #method.iter -/// [`for_each_mut()`]: #method.for_each_mut -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct Book { - /// The sections in this book. - pub sections: Vec, - __non_exhaustive: (), -} - -impl Book { - /// Create an empty book. - pub fn new() -> Self { - Default::default() - } - - /// Get a depth-first iterator over the items in the book. - pub fn iter(&self) -> BookItems<'_> { - BookItems { - items: self.sections.iter().collect(), - } - } - - /// Recursively apply a closure to each item in the book, allowing you to - /// mutate them. - /// - /// # Note - /// - /// Unlike the `iter()` method, this requires a closure instead of returning - /// an iterator. This is because using iterators can possibly allow you - /// to have iterator invalidation errors. - pub fn for_each_mut(&mut self, mut func: F) - where - F: FnMut(&mut BookItem), - { - for_each_mut(&mut func, &mut self.sections); - } - - /// Append a `BookItem` to the `Book`. - pub fn push_item>(&mut self, item: I) -> &mut Self { - self.sections.push(item.into()); - self - } -} - -pub fn for_each_mut<'a, F, I>(func: &mut F, items: I) -where - F: FnMut(&mut BookItem), - I: IntoIterator, -{ - for item in items { - if let BookItem::Chapter(ch) = item { - for_each_mut(func, &mut ch.sub_items); - } - - func(item); - } -} - -/// Enum representing any type of item which can be added to a book. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum BookItem { - /// A nested chapter. - Chapter(Chapter), - /// A section separator. - Separator, - /// A part title. - PartTitle(String), -} - -impl From for BookItem { - fn from(other: Chapter) -> BookItem { - BookItem::Chapter(other) - } -} - -/// The representation of a "chapter", usually mapping to a single file on -/// disk however it may contain multiple sub-chapters. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct Chapter { - /// The chapter's name. - pub name: String, - /// The chapter's contents. - pub content: String, - /// The chapter's section number, if it has one. - pub number: Option, - /// Nested items. - pub sub_items: Vec, - /// The chapter's location, relative to the `SUMMARY.md` file. - /// - /// **Note**: After the index preprocessor runs, any README files will be - /// modified to be `index.md`. If you need access to the actual filename - /// on disk, use [`Chapter::source_path`] instead. - /// - /// This is `None` for a draft chapter. - pub path: Option, - /// The chapter's source file, relative to the `SUMMARY.md` file. - /// - /// **Note**: Beware that README files will internally be treated as - /// `index.md` via the [`Chapter::path`] field. The `source_path` field - /// exists if you need access to the true file path. - /// - /// This is `None` for a draft chapter, or a synthetically generated - /// chapter that has no file on disk. - pub source_path: Option, - /// An ordered list of the names of each chapter above this one in the hierarchy. - pub parent_names: Vec, -} - -impl Chapter { - /// Create a new chapter with the provided content. - pub fn new>( - name: &str, - content: String, - p: P, - parent_names: Vec, - ) -> Chapter { - let path: PathBuf = p.into(); - Chapter { - name: name.to_string(), - content, - path: Some(path.clone()), - source_path: Some(path), - parent_names, - ..Default::default() - } - } - - /// Create a new draft chapter that is not attached to a source markdown file (and thus - /// has no content). - pub fn new_draft(name: &str, parent_names: Vec) -> Self { - Chapter { - name: name.to_string(), - content: String::new(), - path: None, - source_path: None, - parent_names, - ..Default::default() - } - } - - /// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file. - pub fn is_draft_chapter(&self) -> bool { - self.path.is_none() - } -} - /// Use the provided `Summary` to load a `Book` from disk. /// /// You need to pass in the book's source directory because all the links in @@ -239,10 +83,7 @@ pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) chapters.push(chapter); } - Ok(Book { - sections: chapters, - __non_exhaustive: (), - }) + Ok(Book::new_with_items(chapters)) } fn load_summary_item + Clone>( @@ -310,46 +151,11 @@ fn load_chapter>( Ok(ch) } -/// A depth-first iterator over the items in a book. -/// -/// # Note -/// -/// This struct shouldn't be created directly, instead prefer the -/// [`Book::iter()`] method. -pub struct BookItems<'a> { - items: VecDeque<&'a BookItem>, -} - -impl<'a> Iterator for BookItems<'a> { - type Item = &'a BookItem; - - fn next(&mut self) -> Option { - let item = self.items.pop_front(); - - if let Some(BookItem::Chapter(ch)) = item { - // if we wanted a breadth-first iterator we'd `extend()` here - for sub_item in ch.sub_items.iter().rev() { - self.items.push_front(sub_item); - } - } - - item - } -} - -impl Display for Chapter { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(ref section_number) = self.number { - write!(f, "{section_number} ")?; - } - - write!(f, "{}", self.name) - } -} - #[cfg(test)] mod tests { use super::*; + use mdbook_core::book::SectionNumber; + use std::path::PathBuf; use tempfile::{Builder as TempFileBuilder, TempDir}; const DUMMY_SRC: &str = " @@ -480,136 +286,20 @@ And here is some \ numbered_chapters: vec![SummaryItem::Link(link)], ..Default::default() }; - let should_be = Book { - sections: vec![BookItem::Chapter(Chapter { - name: String::from("Chapter 1"), - content: String::from(DUMMY_SRC), - path: Some(PathBuf::from("chapter_1.md")), - source_path: Some(PathBuf::from("chapter_1.md")), - ..Default::default() - })], + let sections = vec![BookItem::Chapter(Chapter { + name: String::from("Chapter 1"), + content: String::from(DUMMY_SRC), + path: Some(PathBuf::from("chapter_1.md")), + source_path: Some(PathBuf::from("chapter_1.md")), ..Default::default() - }; + })]; + let should_be = Book::new_with_items(sections); let got = load_book_from_disk(&summary, temp.path()).unwrap(); assert_eq!(got, should_be); } - #[test] - fn book_iter_iterates_over_sequential_items() { - let book = Book { - sections: vec![ - BookItem::Chapter(Chapter { - name: String::from("Chapter 1"), - content: String::from(DUMMY_SRC), - ..Default::default() - }), - BookItem::Separator, - ], - ..Default::default() - }; - - let should_be: Vec<_> = book.sections.iter().collect(); - - let got: Vec<_> = book.iter().collect(); - - assert_eq!(got, should_be); - } - - #[test] - fn iterate_over_nested_book_items() { - let book = Book { - sections: vec![ - BookItem::Chapter(Chapter { - name: String::from("Chapter 1"), - content: String::from(DUMMY_SRC), - number: None, - path: Some(PathBuf::from("Chapter_1/index.md")), - source_path: Some(PathBuf::from("Chapter_1/index.md")), - parent_names: Vec::new(), - sub_items: vec![ - BookItem::Chapter(Chapter::new( - "Hello World", - String::new(), - "Chapter_1/hello.md", - Vec::new(), - )), - BookItem::Separator, - BookItem::Chapter(Chapter::new( - "Goodbye World", - String::new(), - "Chapter_1/goodbye.md", - Vec::new(), - )), - ], - }), - BookItem::Separator, - ], - ..Default::default() - }; - - let got: Vec<_> = book.iter().collect(); - - assert_eq!(got.len(), 5); - - // checking the chapter names are in the order should be sufficient here... - let chapter_names: Vec = got - .into_iter() - .filter_map(|i| match *i { - BookItem::Chapter(ref ch) => Some(ch.name.clone()), - _ => None, - }) - .collect(); - let should_be: Vec<_> = vec![ - String::from("Chapter 1"), - String::from("Hello World"), - String::from("Goodbye World"), - ]; - - assert_eq!(chapter_names, should_be); - } - - #[test] - fn for_each_mut_visits_all_items() { - let mut book = Book { - sections: vec![ - BookItem::Chapter(Chapter { - name: String::from("Chapter 1"), - content: String::from(DUMMY_SRC), - number: None, - path: Some(PathBuf::from("Chapter_1/index.md")), - source_path: Some(PathBuf::from("Chapter_1/index.md")), - parent_names: Vec::new(), - sub_items: vec![ - BookItem::Chapter(Chapter::new( - "Hello World", - String::new(), - "Chapter_1/hello.md", - Vec::new(), - )), - BookItem::Separator, - BookItem::Chapter(Chapter::new( - "Goodbye World", - String::new(), - "Chapter_1/goodbye.md", - Vec::new(), - )), - ], - }), - BookItem::Separator, - ], - ..Default::default() - }; - - let num_items = book.iter().count(); - let mut visited = 0; - - book.for_each_mut(|_| visited += 1); - - assert_eq!(visited, num_items); - } - #[test] fn cant_load_chapters_with_an_empty_path() { let (_, temp) = dummy_link(); diff --git a/src/book/mod.rs b/src/book/mod.rs index 07e2b4d2..efcfd391 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -8,14 +8,14 @@ mod book; mod init; -pub use self::book::{Book, BookItem, BookItems, Chapter, load_book}; +pub use self::book::load_book; pub use self::init::BookBuilder; - 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::config::{Config, RustEdition}; use mdbook_core::utils; -pub use mdbook_summary::{Link, SectionNumber, Summary, SummaryItem, parse_summary}; +pub use mdbook_summary::{Link, Summary, SummaryItem, parse_summary}; use std::ffi::OsString; use std::io::{IsTerminal, Write}; use std::path::{Path, PathBuf}; From 12285f505d8fbf0d2ac6ff5e89e23d60bfd1c59d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 15:19:18 -0700 Subject: [PATCH 20/44] Move preprocessor types to mdbook-preprocessor This sets up mdbook-preprocessor with the intent of being the core library that preprocessors use to implement the necessary interactions. --- Cargo.lock | 7 +- Cargo.toml | 2 + crates/mdbook-preprocessor/Cargo.toml | 3 + crates/mdbook-preprocessor/src/lib.rs | 73 +++++++++++++++++++ examples/nop-preprocessor.rs | 14 ++-- .../mdbook-remove-emphasis/Cargo.toml | 3 +- .../mdbook-remove-emphasis/src/main.rs | 15 ++-- src/book/mod.rs | 5 +- src/preprocess/cmd.rs | 12 +-- src/preprocess/index.rs | 7 +- src/preprocess/links.rs | 7 +- src/preprocess/mod.rs | 59 --------------- tests/testsuite/preprocessor.rs | 3 +- 13 files changed, 111 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 337aa70f..90db9f34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,7 @@ dependencies = [ "ignore", "log", "mdbook-core", + "mdbook-preprocessor", "mdbook-summary", "memchr", "notify", @@ -1313,15 +1314,17 @@ dependencies = [ name = "mdbook-preprocessor" version = "0.5.0-alpha.1" dependencies = [ + "anyhow", "mdbook-core", + "serde", + "serde_json", ] [[package]] name = "mdbook-remove-emphasis" version = "0.1.0" dependencies = [ - "anyhow", - "mdbook", + "mdbook-preprocessor", "pulldown-cmark 0.12.2", "pulldown-cmark-to-cmark", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 16858e37..afdd72b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow anyhow = "1.0.98" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } +mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } mdbook-summary = { path = "crates/mdbook-summary" } memchr = "2.7.5" pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. @@ -61,6 +62,7 @@ handlebars = "6.0" hex = "0.4.3" log.workspace = true mdbook-core.workspace = true +mdbook-preprocessor.workspace = true mdbook-summary.workspace = true memchr.workspace = true opener = "0.8.1" diff --git a/crates/mdbook-preprocessor/Cargo.toml b/crates/mdbook-preprocessor/Cargo.toml index c39ff408..c4ad0fc1 100644 --- a/crates/mdbook-preprocessor/Cargo.toml +++ b/crates/mdbook-preprocessor/Cargo.toml @@ -8,7 +8,10 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow.workspace = true mdbook-core.workspace = true +serde.workspace = true +serde_json.workspace = true [lints] workspace = true diff --git a/crates/mdbook-preprocessor/src/lib.rs b/crates/mdbook-preprocessor/src/lib.rs index e4d97bb4..b1d77420 100644 --- a/crates/mdbook-preprocessor/src/lib.rs +++ b/crates/mdbook-preprocessor/src/lib.rs @@ -1,3 +1,76 @@ //! Library to assist implementing an mdbook preprocessor. +use anyhow::Context; +use mdbook_core::book::Book; +use mdbook_core::config::Config; +use mdbook_core::errors::Result; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::io::Read; +use std::path::PathBuf; + pub use mdbook_core::MDBOOK_VERSION; +pub use mdbook_core::book; +pub use mdbook_core::config; +pub use mdbook_core::errors; + +/// An operation which is run immediately after loading a book into memory and +/// before it gets rendered. +pub trait Preprocessor { + /// Get the `Preprocessor`'s name. + fn name(&self) -> &str; + + /// Run this `Preprocessor`, allowing it to update the book before it is + /// given to a renderer. + fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result; + + /// A hint to `MDBook` whether this preprocessor is compatible with a + /// particular renderer. + /// + /// By default, always returns `true`. + fn supports_renderer(&self, _renderer: &str) -> bool { + true + } +} + +/// Extra information for a `Preprocessor` to give them more context when +/// processing a book. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PreprocessorContext { + /// The location of the book directory on disk. + pub root: PathBuf, + /// The book configuration (`book.toml`). + pub config: Config, + /// The `Renderer` this preprocessor is being used with. + pub renderer: String, + /// The calling `mdbook` version. + pub mdbook_version: String, + /// Internal mapping of chapter titles. + /// + /// This is used internally by mdbook to compute custom chapter titles. + /// This should not be used outside of mdbook's internals. + #[serde(skip)] + pub chapter_titles: RefCell>, + #[serde(skip)] + __non_exhaustive: (), +} + +impl PreprocessorContext { + /// Create a new `PreprocessorContext`. + pub fn new(root: PathBuf, config: Config, renderer: String) -> Self { + PreprocessorContext { + root, + config, + renderer, + mdbook_version: crate::MDBOOK_VERSION.to_string(), + chapter_titles: RefCell::new(HashMap::new()), + __non_exhaustive: (), + } + } +} + +/// Parses the input given to a preprocessor. +pub fn parse_input(reader: R) -> Result<(PreprocessorContext, Book)> { + serde_json::from_reader(reader).with_context(|| "Unable to parse the input") +} diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index f85fd820..b2e0fb40 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -1,10 +1,10 @@ //! A basic example of a preprocessor that does nothing. use crate::nop_lib::Nop; -use anyhow::Error; use clap::{Arg, ArgMatches, Command}; -use mdbook::book::Book; -use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; +use mdbook_preprocessor::book::Book; +use mdbook_preprocessor::errors::Result; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use semver::{Version, VersionReq}; use std::io; use std::process; @@ -33,8 +33,8 @@ fn main() { } } -fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { - let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; +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)?; @@ -88,7 +88,7 @@ mod nop_lib { "nop-preprocessor" } - fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result { + fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result { // In testing we want to tell the preprocessor to blow up by setting a // particular config value if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) { @@ -149,7 +149,7 @@ mod nop_lib { ]"##; let input_json = input_json.as_bytes(); - let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap(); + let (ctx, book) = mdbook_preprocessor::parse_input(input_json).unwrap(); let expected_book = book.clone(); let result = Nop::new().run(&ctx, book); assert!(result.is_ok()); diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml index cd3e8c83..1b55e291 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml +++ b/examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml @@ -4,8 +4,7 @@ version = "0.1.0" edition.workspace = true [dependencies] -anyhow.workspace = true -mdbook = { path = "../../.." } +mdbook-preprocessor.workspace = true pulldown-cmark = { version = "0.12.2", default-features = false } pulldown-cmark-to-cmark = "18.0.0" serde_json = "1.0.132" diff --git a/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs b/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs index 88f58506..95974d8f 100644 --- a/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs +++ b/examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs @@ -1,10 +1,9 @@ //! This is a demonstration of an mdBook preprocessor which parses markdown //! and removes any instances of emphasis. -use anyhow::Error; -use mdbook::BookItem; -use mdbook::book::{Book, Chapter}; -use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; +use mdbook_preprocessor::book::{Book, BookItem, Chapter}; +use mdbook_preprocessor::errors::Result; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use pulldown_cmark::{Event, Parser, Tag, TagEnd}; use std::io; @@ -35,7 +34,7 @@ impl Preprocessor for RemoveEmphasis { "remove-emphasis" } - fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result { + fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result { let mut total = 0; book.for_each_mut(|item| { let BookItem::Chapter(ch) = item else { @@ -55,7 +54,7 @@ impl Preprocessor for RemoveEmphasis { } // ANCHOR: remove_emphasis -fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result { +fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result { let mut buf = String::with_capacity(chapter.content.len()); let events = Parser::new(&chapter.content).filter(|e| match e { @@ -71,9 +70,9 @@ fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Resu } // ANCHOR_END: remove_emphasis -pub fn handle_preprocessing() -> Result<(), Error> { +pub fn handle_preprocessing() -> Result<()> { let pre = RemoveEmphasis; - let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; + let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?; let processed_book = pre.run(&ctx, book)?; serde_json::to_writer(io::stdout(), &processed_book)?; diff --git a/src/book/mod.rs b/src/book/mod.rs index efcfd391..53109153 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -15,6 +15,7 @@ use log::{debug, error, info, log_enabled, trace, warn}; pub use mdbook_core::book::{Book, BookItem, BookItems, Chapter, SectionNumber}; use mdbook_core::config::{Config, RustEdition}; use mdbook_core::utils; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; pub use mdbook_summary::{Link, Summary, SummaryItem, parse_summary}; use std::ffi::OsString; use std::io::{IsTerminal, Write}; @@ -24,9 +25,7 @@ use tempfile::Builder as TempFileBuilder; use toml::Value; use topological_sort::TopologicalSort; -use crate::preprocess::{ - CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext, -}; +use crate::preprocess::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor}; use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; /// The object used to manage and build a book. diff --git a/src/preprocess/cmd.rs b/src/preprocess/cmd.rs index dfd0681e..754ddddc 100644 --- a/src/preprocess/cmd.rs +++ b/src/preprocess/cmd.rs @@ -1,9 +1,9 @@ -use super::{Preprocessor, PreprocessorContext}; use crate::book::Book; use anyhow::{Context, Result, bail, ensure}; use log::{debug, trace, warn}; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use shlex::Shlex; -use std::io::{self, Read, Write}; +use std::io::{self, Write}; use std::process::{Child, Command, Stdio}; /// A custom preprocessor which will shell out to a 3rd-party program. @@ -41,12 +41,6 @@ impl CmdPreprocessor { CmdPreprocessor { name, cmd } } - /// A convenience function custom preprocessors can use to parse the input - /// written to `stdin` by a `CmdRenderer`. - pub fn parse_input(reader: R) -> Result<(PreprocessorContext, Book)> { - serde_json::from_reader(reader).with_context(|| "Unable to parse the input") - } - fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) { let stdin = child.stdin.take().expect("Child has stdin"); @@ -200,7 +194,7 @@ mod tests { let mut buffer = Vec::new(); cmd.write_input(&mut buffer, &md.book, &ctx).unwrap(); - let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap(); + let (got_ctx, got_book) = mdbook_preprocessor::parse_input(buffer.as_slice()).unwrap(); assert_eq!(got_book, md.book); assert_eq!(got_ctx, ctx); diff --git a/src/preprocess/index.rs b/src/preprocess/index.rs index 3b2666d7..3b8dfd2a 100644 --- a/src/preprocess/index.rs +++ b/src/preprocess/index.rs @@ -1,10 +1,9 @@ -use regex::Regex; -use std::{path::Path, sync::LazyLock}; - -use super::{Preprocessor, PreprocessorContext}; use crate::book::{Book, BookItem}; use anyhow::Result; use log::warn; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; +use regex::Regex; +use std::{path::Path, sync::LazyLock}; /// A preprocessor for converting file name `README.md` to `index.md` since /// `README.md` is the de facto index file in markdown-based documentation. diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index 02d7c4d7..cfefbc8a 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -1,18 +1,17 @@ +use crate::book::{Book, BookItem}; use anyhow::{Context, Result}; +use log::{error, warn}; use mdbook_core::utils::{ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, }; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use regex::{CaptureMatches, Captures, Regex}; use std::fs; use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo}; use std::path::{Path, PathBuf}; use std::sync::LazyLock; -use super::{Preprocessor, PreprocessorContext}; -use crate::book::{Book, BookItem}; -use log::{error, warn}; - const ESCAPE_CHAR: char = '\\'; const MAX_LINK_NESTED_DEPTH: usize = 10; diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index 1134ad4d..d4906651 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -1,13 +1,5 @@ //! Book preprocessing. -use crate::book::Book; -use anyhow::Result; -use mdbook_core::config::Config; -use serde::{Deserialize, Serialize}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::path::PathBuf; - pub use self::cmd::CmdPreprocessor; pub use self::index::IndexPreprocessor; pub use self::links::LinkPreprocessor; @@ -15,54 +7,3 @@ pub use self::links::LinkPreprocessor; mod cmd; mod index; mod links; - -/// Extra information for a `Preprocessor` to give them more context when -/// processing a book. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct PreprocessorContext { - /// The location of the book directory on disk. - pub root: PathBuf, - /// The book configuration (`book.toml`). - pub config: Config, - /// The `Renderer` this preprocessor is being used with. - pub renderer: String, - /// The calling `mdbook` version. - pub mdbook_version: String, - #[serde(skip)] - pub(crate) chapter_titles: RefCell>, - #[serde(skip)] - __non_exhaustive: (), -} - -impl PreprocessorContext { - /// Create a new `PreprocessorContext`. - pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self { - PreprocessorContext { - root, - config, - renderer, - mdbook_version: crate::MDBOOK_VERSION.to_string(), - chapter_titles: RefCell::new(HashMap::new()), - __non_exhaustive: (), - } - } -} - -/// An operation which is run immediately after loading a book into memory and -/// before it gets rendered. -pub trait Preprocessor { - /// Get the `Preprocessor`'s name. - fn name(&self) -> &str; - - /// Run this `Preprocessor`, allowing it to update the book before it is - /// given to a renderer. - fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result; - - /// A hint to `MDBook` whether this preprocessor is compatible with a - /// particular renderer. - /// - /// By default, always returns `true`. - fn supports_renderer(&self, _renderer: &str) -> bool { - true - } -} diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index 51f4a2a2..b04f4b43 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -3,7 +3,8 @@ use crate::prelude::*; use anyhow::Result; use mdbook::book::Book; -use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; +use mdbook::preprocess::CmdPreprocessor; +use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use std::sync::{Arc, Mutex}; struct Spy(Arc>); From 3278f84373d6c51e6c6638092d8b2c705b1b4b70 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 15:30:51 -0700 Subject: [PATCH 21/44] Move renderer types to mdbook-renderer This sets up mdbook-renderer with the intent of being the core library that renderers use to implement the necessary interactions. --- Cargo.lock | 4 ++ Cargo.toml | 2 + crates/mdbook-renderer/Cargo.toml | 3 ++ crates/mdbook-renderer/src/lib.rs | 78 +++++++++++++++++++++++++++++++ src/book/mod.rs | 3 +- src/lib.rs | 3 +- src/renderer/mod.rs | 77 +----------------------------- tests/testsuite/renderer.rs | 2 +- 8 files changed, 93 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90db9f34..5c4626ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1272,6 +1272,7 @@ dependencies = [ "log", "mdbook-core", "mdbook-preprocessor", + "mdbook-renderer", "mdbook-summary", "memchr", "notify", @@ -1334,7 +1335,10 @@ dependencies = [ name = "mdbook-renderer" version = "0.5.0-alpha.1" dependencies = [ + "anyhow", "mdbook-core", + "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index afdd72b4..2c3cf492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ anyhow = "1.0.98" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } +mdbook-renderer = { path = "crates/mdbook-renderer" } mdbook-summary = { path = "crates/mdbook-summary" } memchr = "2.7.5" pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. @@ -63,6 +64,7 @@ hex = "0.4.3" log.workspace = true mdbook-core.workspace = true mdbook-preprocessor.workspace = true +mdbook-renderer.workspace = true mdbook-summary.workspace = true memchr.workspace = true opener = "0.8.1" diff --git a/crates/mdbook-renderer/Cargo.toml b/crates/mdbook-renderer/Cargo.toml index 2ec83e47..38869282 100644 --- a/crates/mdbook-renderer/Cargo.toml +++ b/crates/mdbook-renderer/Cargo.toml @@ -8,7 +8,10 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow.workspace = true mdbook-core.workspace = true +serde.workspace = true +serde_json.workspace = true [lints] workspace = true diff --git a/crates/mdbook-renderer/src/lib.rs b/crates/mdbook-renderer/src/lib.rs index 8b09630d..56365420 100644 --- a/crates/mdbook-renderer/src/lib.rs +++ b/crates/mdbook-renderer/src/lib.rs @@ -1,3 +1,81 @@ //! Library to assist implementing an mdbook renderer. +use anyhow::Context; +use mdbook_core::book::Book; +use mdbook_core::config::Config; +use mdbook_core::errors::Result; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::io::Read; +use std::path::PathBuf; + pub use mdbook_core::MDBOOK_VERSION; +pub use mdbook_core::book; +pub use mdbook_core::config; +pub use mdbook_core::errors; + +/// An mdbook backend. +pub trait Renderer { + /// The `Renderer`'s name. + fn name(&self) -> &str; + + /// Invoke the `Renderer`, passing in all the necessary information for + /// describing a book. + fn render(&self, ctx: &RenderContext) -> Result<()>; +} + +/// The context provided to all renderers. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct RenderContext { + /// Which version of `mdbook` did this come from (as written in `mdbook`'s + /// `Cargo.toml`). Useful if you know the renderer is only compatible with + /// certain versions of `mdbook`. + pub version: String, + /// The book's root directory. + pub root: PathBuf, + /// A loaded representation of the book itself. + pub book: Book, + /// The loaded configuration file. + pub config: Config, + /// Where the renderer *must* put any build artefacts generated. To allow + /// renderers to cache intermediate results, this directory is not + /// guaranteed to be empty or even exist. + pub destination: PathBuf, + /// Internal mapping of chapter titles. + /// + /// This is used internally by mdbook to compute custom chapter titles. + /// This should not be used outside of mdbook's internals. + #[serde(skip)] + pub chapter_titles: HashMap, + #[serde(skip)] + __non_exhaustive: (), +} + +impl RenderContext { + /// Create a new `RenderContext`. + pub fn new(root: P, book: Book, config: Config, destination: Q) -> RenderContext + where + P: Into, + Q: Into, + { + RenderContext { + book, + config, + version: crate::MDBOOK_VERSION.to_string(), + root: root.into(), + destination: destination.into(), + chapter_titles: HashMap::new(), + __non_exhaustive: (), + } + } + + /// Get the source directory's (absolute) path on disk. + pub fn source_dir(&self) -> PathBuf { + self.root.join(&self.config.book.src) + } + + /// Load a `RenderContext` from its JSON representation. + pub fn from_json(reader: R) -> Result { + serde_json::from_reader(reader).with_context(|| "Unable to deserialize the `RenderContext`") + } +} diff --git a/src/book/mod.rs b/src/book/mod.rs index 53109153..3b8879da 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -16,6 +16,7 @@ pub use mdbook_core::book::{Book, BookItem, BookItems, Chapter, SectionNumber}; use mdbook_core::config::{Config, RustEdition}; use mdbook_core::utils; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; +use mdbook_renderer::{RenderContext, Renderer}; pub use mdbook_summary::{Link, Summary, SummaryItem, parse_summary}; use std::ffi::OsString; use std::io::{IsTerminal, Write}; @@ -26,7 +27,7 @@ use toml::Value; use topological_sort::TopologicalSort; use crate::preprocess::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor}; -use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; +use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer}; /// The object used to manage and build a book. pub struct MDBook { diff --git a/src/lib.rs b/src/lib.rs index 76c744f0..7a8c6e00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ //! access to the various methods for working with the [`Config`]. //! //! [user guide]: https://rust-lang.github.io/mdBook/ -//! [`RenderContext`]: renderer::RenderContext +//! [`RenderContext`]: mdbook_renderer::RenderContext //! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html //! [`Config`]: mdbook_core::config::Config @@ -88,6 +88,5 @@ pub mod theme; pub use crate::book::BookItem; pub use crate::book::MDBook; -pub use crate::renderer::Renderer; pub use mdbook_core::MDBOOK_VERSION; pub use mdbook_core::config::Config; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ce4bc025..d639b005 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -11,15 +11,12 @@ //! [For Developers]: https://rust-lang.github.io/mdBook/for_developers/index.html //! [RenderContext]: struct.RenderContext.html -use crate::book::Book; use anyhow::{Context, Result, bail}; use log::{error, info, trace, warn}; -use mdbook_core::config::Config; -use serde::{Deserialize, Serialize}; +use mdbook_renderer::{RenderContext, Renderer}; use shlex::Shlex; -use std::collections::HashMap; use std::fs; -use std::io::{self, ErrorKind, Read}; +use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use toml::Value; @@ -30,76 +27,6 @@ pub use self::markdown_renderer::MarkdownRenderer; mod html_handlebars; mod markdown_renderer; -/// An arbitrary `mdbook` backend. -/// -/// Although it's quite possible for you to import `mdbook` as a library and -/// provide your own renderer, there are two main renderer implementations that -/// 99% of users will ever use: -/// -/// - [`HtmlHandlebars`] - the built-in HTML renderer -/// - [`CmdRenderer`] - a generic renderer which shells out to a program to do the -/// actual rendering -pub trait Renderer { - /// The `Renderer`'s name. - fn name(&self) -> &str; - - /// Invoke the `Renderer`, passing in all the necessary information for - /// describing a book. - fn render(&self, ctx: &RenderContext) -> Result<()>; -} - -/// The context provided to all renderers. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct RenderContext { - /// Which version of `mdbook` did this come from (as written in `mdbook`'s - /// `Cargo.toml`). Useful if you know the renderer is only compatible with - /// certain versions of `mdbook`. - pub version: String, - /// The book's root directory. - pub root: PathBuf, - /// A loaded representation of the book itself. - pub book: Book, - /// The loaded configuration file. - pub config: Config, - /// Where the renderer *must* put any build artefacts generated. To allow - /// renderers to cache intermediate results, this directory is not - /// guaranteed to be empty or even exist. - pub destination: PathBuf, - #[serde(skip)] - pub(crate) chapter_titles: HashMap, - #[serde(skip)] - __non_exhaustive: (), -} - -impl RenderContext { - /// Create a new `RenderContext`. - pub fn new(root: P, book: Book, config: Config, destination: Q) -> RenderContext - where - P: Into, - Q: Into, - { - RenderContext { - book, - config, - version: crate::MDBOOK_VERSION.to_string(), - root: root.into(), - destination: destination.into(), - chapter_titles: HashMap::new(), - __non_exhaustive: (), - } - } - - /// Get the source directory's (absolute) path on disk. - pub fn source_dir(&self) -> PathBuf { - self.root.join(&self.config.book.src) - } - - /// Load a `RenderContext` from its JSON representation. - pub fn from_json(reader: R) -> Result { - serde_json::from_reader(reader).with_context(|| "Unable to deserialize the `RenderContext`") - } -} - /// A generic renderer which will shell out to an arbitrary executable. /// /// # Rendering Protocol diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index 923bd5dd..abf45502 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use anyhow::Result; -use mdbook::renderer::{RenderContext, Renderer}; +use mdbook_renderer::{RenderContext, Renderer}; use snapbox::IntoData; use std::fs::File; use std::sync::{Arc, Mutex}; From 8f3b6b4776f641a6f5a6479e47b8611d8acae73b Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 15:46:36 -0700 Subject: [PATCH 22/44] Move markdown support to mdbook-markdown This moves all the code responsible for markdown processing to the mdbook-markdown crate. --- Cargo.lock | 10 + Cargo.toml | 2 + crates/mdbook-core/src/utils/mod.rs | 507 +------------------ crates/mdbook-markdown/Cargo.toml | 16 + crates/mdbook-markdown/src/lib.rs | 367 ++++++++++++++ crates/mdbook-markdown/src/tests.rs | 147 ++++++ src/renderer/html_handlebars/hbs_renderer.rs | 14 +- src/renderer/html_handlebars/helpers/toc.rs | 3 +- src/renderer/html_handlebars/search.rs | 3 +- tests/testsuite/markdown.rs | 8 +- 10 files changed, 556 insertions(+), 521 deletions(-) create mode 100644 crates/mdbook-markdown/Cargo.toml create mode 100644 crates/mdbook-markdown/src/lib.rs create mode 100644 crates/mdbook-markdown/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 5c4626ad..2a5589f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,7 @@ dependencies = [ "ignore", "log", "mdbook-core", + "mdbook-markdown", "mdbook-preprocessor", "mdbook-renderer", "mdbook-summary", @@ -1311,6 +1312,15 @@ dependencies = [ "toml", ] +[[package]] +name = "mdbook-markdown" +version = "0.5.0-alpha.1" +dependencies = [ + "log", + "pulldown-cmark 0.10.3", + "regex", +] + [[package]] name = "mdbook-preprocessor" version = "0.5.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 2c3cf492..b4cc16fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow anyhow = "1.0.98" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } +mdbook-markdown = { path = "crates/mdbook-markdown" } mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } mdbook-renderer = { path = "crates/mdbook-renderer" } mdbook-summary = { path = "crates/mdbook-summary" } @@ -63,6 +64,7 @@ handlebars = "6.0" hex = "0.4.3" log.workspace = true mdbook-core.workspace = true +mdbook-markdown.workspace = true mdbook-preprocessor.workspace = true mdbook-renderer.workspace = true mdbook-summary.workspace = true diff --git a/crates/mdbook-core/src/utils/mod.rs b/crates/mdbook-core/src/utils/mod.rs index 56bfbb97..caca5dcd 100644 --- a/crates/mdbook-core/src/utils/mod.rs +++ b/crates/mdbook-core/src/utils/mod.rs @@ -2,12 +2,9 @@ use anyhow::Error; use log::error; -use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd, html}; use regex::Regex; use std::borrow::Cow; use std::collections::HashMap; -use std::fmt::Write; -use std::path::Path; use std::sync::LazyLock; pub mod fs; @@ -83,338 +80,6 @@ pub fn unique_id_from_content(content: &str, id_counter: &mut HashMap(event: Event<'a>, path: Option<&Path>) -> Event<'a> { - static SCHEME_LINK: LazyLock = - LazyLock::new(|| Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap()); - static MD_LINK: LazyLock = - LazyLock::new(|| Regex::new(r"(?P.*)\.md(?P#.*)?").unwrap()); - - fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> { - if dest.starts_with('#') { - // Fragment-only link. - if let Some(path) = path { - let mut base = path.display().to_string(); - if base.ends_with(".md") { - base.replace_range(base.len() - 3.., ".html"); - } - return format!("{base}{dest}").into(); - } else { - return dest; - } - } - // Don't modify links with schemes like `https`. - if !SCHEME_LINK.is_match(&dest) { - // This is a relative link, adjust it as necessary. - let mut fixed_link = String::new(); - if let Some(path) = path { - let base = path - .parent() - .expect("path can't be empty") - .to_str() - .expect("utf-8 paths only"); - if !base.is_empty() { - write!(fixed_link, "{base}/").unwrap(); - } - } - - if let Some(caps) = MD_LINK.captures(&dest) { - fixed_link.push_str(&caps["link"]); - fixed_link.push_str(".html"); - if let Some(anchor) = caps.name("anchor") { - fixed_link.push_str(anchor.as_str()); - } - } else { - fixed_link.push_str(&dest); - }; - return CowStr::from(fixed_link); - } - dest - } - - fn fix_html<'a>(html: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> { - // This is a terrible hack, but should be reasonably reliable. Nobody - // should ever parse a tag with a regex. However, there isn't anything - // in Rust that I know of that is suitable for handling partial html - // fragments like those generated by pulldown_cmark. - // - // There are dozens of HTML tags/attributes that contain paths, so - // feel free to add more tags if desired; these are the only ones I - // care about right now. - static HTML_LINK: LazyLock = - LazyLock::new(|| Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap()); - - HTML_LINK - .replace_all(&html, |caps: ®ex::Captures<'_>| { - let fixed = fix(caps[2].into(), path); - format!("{}{}\"", &caps[1], fixed) - }) - .into_owned() - .into() - } - - match event { - Event::Start(Tag::Link { - link_type, - dest_url, - title, - id, - }) => Event::Start(Tag::Link { - link_type, - dest_url: fix(dest_url, path), - title, - id, - }), - Event::Start(Tag::Image { - link_type, - dest_url, - title, - id, - }) => Event::Start(Tag::Image { - link_type, - dest_url: fix(dest_url, path), - title, - id, - }), - Event::Html(html) => Event::Html(fix_html(html, path)), - Event::InlineHtml(html) => Event::InlineHtml(fix_html(html, path)), - _ => event, - } -} - -/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML. -pub fn render_markdown(text: &str, smart_punctuation: bool) -> String { - render_markdown_with_path(text, smart_punctuation, None) -} - -/// Creates a new pulldown-cmark parser of the given text. -pub fn new_cmark_parser(text: &str, smart_punctuation: bool) -> Parser<'_> { - let mut opts = Options::empty(); - opts.insert(Options::ENABLE_TABLES); - opts.insert(Options::ENABLE_FOOTNOTES); - opts.insert(Options::ENABLE_STRIKETHROUGH); - opts.insert(Options::ENABLE_TASKLISTS); - opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); - if smart_punctuation { - opts.insert(Options::ENABLE_SMART_PUNCTUATION); - } - Parser::new_ext(text, opts) -} - -/// Renders markdown to HTML. -/// -/// `path` should only be set if this is being generated for the consolidated -/// print page. It should point to the page being rendered relative to the -/// root of the book. -pub fn render_markdown_with_path( - text: &str, - smart_punctuation: bool, - path: Option<&Path>, -) -> String { - let mut body = String::with_capacity(text.len() * 3 / 2); - - // Based on - // https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/examples/footnote-rewrite.rs - - // This handling of footnotes is a two-pass process. This is done to - // support linkbacks, little arrows that allow you to jump back to the - // footnote reference. The first pass collects the footnote definitions. - // The second pass modifies those definitions to include the linkbacks, - // and inserts the definitions back into the `events` list. - - // This is a map of name -> (number, count) - // `name` is the name of the footnote. - // `number` is the footnote number displayed in the output. - // `count` is the number of references to this footnote (used for multiple - // linkbacks, and checking for unused footnotes). - let mut footnote_numbers = HashMap::new(); - // This is a map of name -> Vec - // `name` is the name of the footnote. - // The events list is the list of events needed to build the footnote definition. - let mut footnote_defs = HashMap::new(); - - // The following are used when currently processing a footnote definition. - // - // This is the name of the footnote (escaped). - let mut in_footnote_name = String::new(); - // This is the list of events to build the footnote definition. - let mut in_footnote = Vec::new(); - - let events = new_cmark_parser(text, smart_punctuation) - .map(clean_codeblock_headers) - .map(|event| adjust_links(event, path)) - .flat_map(|event| { - let (a, b) = wrap_tables(event); - a.into_iter().chain(b) - }) - // Footnote rewriting must go last to ensure inner definition contents - // are processed (since they get pulled out of the initial stream). - .filter_map(|event| { - match event { - Event::Start(Tag::FootnoteDefinition(name)) => { - if !in_footnote.is_empty() { - log::warn!("internal bug: nested footnote not expected in {path:?}"); - } - in_footnote_name = special_escape(&name); - None - } - Event::End(TagEnd::FootnoteDefinition) => { - let def_events = std::mem::take(&mut in_footnote); - let name = std::mem::take(&mut in_footnote_name); - - if footnote_defs.contains_key(&name) { - log::warn!( - "footnote `{name}` in {} defined multiple times - \ - not updating to new definition", - path.map_or_else(|| Cow::from(""), |p| p.to_string_lossy()) - ); - } else { - footnote_defs.insert(name, def_events); - } - None - } - Event::FootnoteReference(name) => { - let name = special_escape(&name); - let len = footnote_numbers.len() + 1; - let (n, count) = footnote_numbers.entry(name.clone()).or_insert((len, 0)); - *count += 1; - let html = Event::Html( - format!( - "\ - {n}\ - " - ) - .into(), - ); - if in_footnote_name.is_empty() { - Some(html) - } else { - // While inside a footnote, we need to accumulate. - in_footnote.push(html); - None - } - } - // While inside a footnote, accumulate all events into a local. - _ if !in_footnote_name.is_empty() => { - in_footnote.push(event); - None - } - _ => Some(event), - } - }); - - html::push_html(&mut body, events); - - if !footnote_defs.is_empty() { - add_footnote_defs( - &mut body, - path, - footnote_defs.into_iter().collect(), - &footnote_numbers, - ); - } - - body -} - -/// Adds all footnote definitions into `body`. -fn add_footnote_defs( - body: &mut String, - path: Option<&Path>, - mut defs: Vec<(String, Vec>)>, - numbers: &HashMap, -) { - // Remove unused. - defs.retain(|(name, _)| { - if !numbers.contains_key(name) { - log::warn!( - "footnote `{name}` in `{}` is defined but not referenced", - path.map_or_else(|| Cow::from(""), |p| p.to_string_lossy()) - ); - false - } else { - true - } - }); - - defs.sort_by_cached_key(|(name, _)| numbers[name].0); - - body.push_str( - "
\n\ -
    ", - ); - - // Insert the backrefs to the definition, and put the definitions in the output. - for (name, mut fn_events) in defs { - let count = numbers[&name].1; - fn_events.insert( - 0, - Event::Html(format!("
  1. ").into()), - ); - // Generate the linkbacks. - for usage in 1..=count { - let nth = if usage == 1 { - String::new() - } else { - usage.to_string() - }; - let backlink = - Event::Html(format!(" ↩{nth}").into()); - if matches!(fn_events.last(), Some(Event::End(TagEnd::Paragraph))) { - // Put the linkback at the end of the last paragraph instead - // of on a line by itself. - fn_events.insert(fn_events.len() - 1, backlink); - } else { - // Not a clear place to put it in this circumstance, so put it - // at the end. - fn_events.push(backlink); - } - } - fn_events.push(Event::Html("
  2. \n".into())); - html::push_html(body, fn_events.into_iter()); - } - - body.push_str("
"); -} - -/// Wraps tables in a `.table-wrapper` class to apply overflow-x rules to. -fn wrap_tables(event: Event<'_>) -> (Option>, Option>) { - match event { - Event::Start(Tag::Table(_)) => ( - Some(Event::Html(r#"
"#.into())), - Some(event), - ), - Event::End(TagEnd::Table) => (Some(event), Some(Event::Html(r#"
"#.into()))), - _ => (Some(event), None), - } -} - -fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> { - match event { - Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => { - let info: String = info - .chars() - .map(|x| match x { - ' ' | '\t' => ',', - _ => x, - }) - .filter(|ch| !ch.is_whitespace()) - .collect(); - - Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::from(info)))) - } - _ => event, - } -} - /// Prints a "backtrace" of some `Error`. pub fn log_backtrace(e: &Error) { error!("Error: {}", e); @@ -424,27 +89,6 @@ pub fn log_backtrace(e: &Error) { } } -/// Escape characters to make it safe for an HTML string. -pub fn special_escape(mut s: &str) -> String { - let mut escaped = String::with_capacity(s.len()); - let needs_escape: &[char] = &['<', '>', '\'', '"', '\\', '&']; - while let Some(next) = s.find(needs_escape) { - escaped.push_str(&s[..next]); - match s.as_bytes()[next] { - b'<' => escaped.push_str("<"), - b'>' => escaped.push_str(">"), - b'\'' => escaped.push_str("'"), - b'"' => escaped.push_str("""), - b'\\' => escaped.push_str("\"), - b'&' => escaped.push_str("&"), - _ => unreachable!(), - } - s = &s[next + 1..]; - } - escaped.push_str(s); - escaped -} - /// Escape `<` and `>` for HTML. pub fn bracket_escape(mut s: &str) -> String { let mut escaped = String::with_capacity(s.len()); @@ -464,143 +108,7 @@ pub fn bracket_escape(mut s: &str) -> String { #[cfg(test)] mod tests { - use super::{bracket_escape, special_escape}; - - mod render_markdown { - use super::super::render_markdown; - - #[test] - fn preserves_external_links() { - assert_eq!( - render_markdown("[example](https://www.rust-lang.org/)", false), - "

example

\n" - ); - } - - #[test] - fn it_can_adjust_markdown_links() { - assert_eq!( - render_markdown("[example](example.md)", false), - "

example

\n" - ); - assert_eq!( - render_markdown("[example_anchor](example.md#anchor)", false), - "

example_anchor

\n" - ); - - // this anchor contains 'md' inside of it - assert_eq!( - render_markdown("[phantom data](foo.html#phantomdata)", false), - "

phantom data

\n" - ); - } - - #[test] - fn it_can_wrap_tables() { - let src = r#" -| Original | Punycode | Punycode + Encoding | -|-----------------|-----------------|---------------------| -| føø | f-5gaa | f_5gaa | -"#; - let out = r#" -
- -
OriginalPunycodePunycode + Encoding
føøf-5gaaf_5gaa
-
-"#.trim(); - assert_eq!(render_markdown(src, false), out); - } - - #[test] - fn it_can_keep_quotes_straight() { - assert_eq!(render_markdown("'one'", false), "

'one'

\n"); - } - - #[test] - fn it_can_make_quotes_curly_except_when_they_are_in_code() { - let input = r#" -'one' -``` -'two' -``` -`'three'` 'four'"#; - let expected = r#"

‘one’

-
'two'
-
-

'three' ‘four’

-"#; - assert_eq!(render_markdown(input, true), expected); - } - - #[test] - fn whitespace_outside_of_codeblock_header_is_preserved() { - let input = r#" -some text with spaces -```rust -fn main() { -// code inside is unchanged -} -``` -more text with spaces -"#; - - let expected = r#"

some text with spaces

-
fn main() {
-// code inside is unchanged
-}
-
-

more text with spaces

-"#; - assert_eq!(render_markdown(input, false), expected); - assert_eq!(render_markdown(input, true), expected); - } - - #[test] - fn rust_code_block_properties_are_passed_as_space_delimited_class() { - let input = r#" -```rust,no_run,should_panic,property_3 -``` -"#; - - let expected = r#"
-"#; - assert_eq!(render_markdown(input, false), expected); - assert_eq!(render_markdown(input, true), expected); - } - - #[test] - fn rust_code_block_properties_with_whitespace_are_passed_as_space_delimited_class() { - let input = r#" -```rust, no_run,,,should_panic , ,property_3 -``` -"#; - - let expected = r#"
-"#; - assert_eq!(render_markdown(input, false), expected); - assert_eq!(render_markdown(input, true), expected); - } - - #[test] - fn rust_code_block_without_properties_has_proper_html_class() { - let input = r#" -```rust -``` -"#; - - let expected = r#"
-"#; - assert_eq!(render_markdown(input, false), expected); - assert_eq!(render_markdown(input, true), expected); - - let input = r#" -```rust -``` -"#; - assert_eq!(render_markdown(input, false), expected); - assert_eq!(render_markdown(input, true), expected); - } - } + use super::bracket_escape; #[allow(deprecated)] mod id_from_content { @@ -690,17 +198,4 @@ more text with spaces assert_eq!(bracket_escape("'"), "'"); assert_eq!(bracket_escape("\\"), "\\"); } - - #[test] - fn escaped_special() { - assert_eq!(special_escape(""), ""); - assert_eq!(special_escape("<"), "<"); - assert_eq!(special_escape(">"), ">"); - assert_eq!(special_escape("<>"), "<>"); - assert_eq!(special_escape(""), "<test>"); - assert_eq!(special_escape("ab"), "a<test>b"); - assert_eq!(special_escape("'"), "'"); - assert_eq!(special_escape("\\"), "\"); - assert_eq!(special_escape("&"), "&"); - } } diff --git a/crates/mdbook-markdown/Cargo.toml b/crates/mdbook-markdown/Cargo.toml new file mode 100644 index 00000000..0dc3e723 --- /dev/null +++ b/crates/mdbook-markdown/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mdbook-markdown" +version = "0.5.0-alpha.1" +description = "Markdown processing used in mdBook" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +log.workspace = true +pulldown-cmark.workspace = true +regex.workspace = true + +[lints] +workspace = true diff --git a/crates/mdbook-markdown/src/lib.rs b/crates/mdbook-markdown/src/lib.rs new file mode 100644 index 00000000..5b506fa7 --- /dev/null +++ b/crates/mdbook-markdown/src/lib.rs @@ -0,0 +1,367 @@ +//! Markdown processing used in mdBook. + +use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd, html}; +use regex::Regex; +use std::borrow::Cow; +use std::collections::HashMap; +use std::fmt::Write; +use std::path::Path; +use std::sync::LazyLock; + +pub use pulldown_cmark; + +#[cfg(test)] +mod tests; + +/// Wrapper around the pulldown-cmark parser for rendering markdown to HTML. +pub fn render_markdown(text: &str, smart_punctuation: bool) -> String { + render_markdown_with_path(text, smart_punctuation, None) +} + +/// Creates a new pulldown-cmark parser of the given text. +pub fn new_cmark_parser(text: &str, smart_punctuation: bool) -> Parser<'_> { + let mut opts = Options::empty(); + opts.insert(Options::ENABLE_TABLES); + opts.insert(Options::ENABLE_FOOTNOTES); + opts.insert(Options::ENABLE_STRIKETHROUGH); + opts.insert(Options::ENABLE_TASKLISTS); + opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); + if smart_punctuation { + opts.insert(Options::ENABLE_SMART_PUNCTUATION); + } + Parser::new_ext(text, opts) +} + +/// Renders markdown to HTML. +/// +/// `path` should only be set if this is being generated for the consolidated +/// print page. It should point to the page being rendered relative to the +/// root of the book. +pub fn render_markdown_with_path( + text: &str, + smart_punctuation: bool, + path: Option<&Path>, +) -> String { + let mut body = String::with_capacity(text.len() * 3 / 2); + + // Based on + // https://github.com/pulldown-cmark/pulldown-cmark/blob/master/pulldown-cmark/examples/footnote-rewrite.rs + + // This handling of footnotes is a two-pass process. This is done to + // support linkbacks, little arrows that allow you to jump back to the + // footnote reference. The first pass collects the footnote definitions. + // The second pass modifies those definitions to include the linkbacks, + // and inserts the definitions back into the `events` list. + + // This is a map of name -> (number, count) + // `name` is the name of the footnote. + // `number` is the footnote number displayed in the output. + // `count` is the number of references to this footnote (used for multiple + // linkbacks, and checking for unused footnotes). + let mut footnote_numbers = HashMap::new(); + // This is a map of name -> Vec + // `name` is the name of the footnote. + // The events list is the list of events needed to build the footnote definition. + let mut footnote_defs = HashMap::new(); + + // The following are used when currently processing a footnote definition. + // + // This is the name of the footnote (escaped). + let mut in_footnote_name = String::new(); + // This is the list of events to build the footnote definition. + let mut in_footnote = Vec::new(); + + let events = new_cmark_parser(text, smart_punctuation) + .map(clean_codeblock_headers) + .map(|event| adjust_links(event, path)) + .flat_map(|event| { + let (a, b) = wrap_tables(event); + a.into_iter().chain(b) + }) + // Footnote rewriting must go last to ensure inner definition contents + // are processed (since they get pulled out of the initial stream). + .filter_map(|event| { + match event { + Event::Start(Tag::FootnoteDefinition(name)) => { + if !in_footnote.is_empty() { + log::warn!("internal bug: nested footnote not expected in {path:?}"); + } + in_footnote_name = special_escape(&name); + None + } + Event::End(TagEnd::FootnoteDefinition) => { + let def_events = std::mem::take(&mut in_footnote); + let name = std::mem::take(&mut in_footnote_name); + + if footnote_defs.contains_key(&name) { + log::warn!( + "footnote `{name}` in {} defined multiple times - \ + not updating to new definition", + path.map_or_else(|| Cow::from(""), |p| p.to_string_lossy()) + ); + } else { + footnote_defs.insert(name, def_events); + } + None + } + Event::FootnoteReference(name) => { + let name = special_escape(&name); + let len = footnote_numbers.len() + 1; + let (n, count) = footnote_numbers.entry(name.clone()).or_insert((len, 0)); + *count += 1; + let html = Event::Html( + format!( + "\ + {n}\ + " + ) + .into(), + ); + if in_footnote_name.is_empty() { + Some(html) + } else { + // While inside a footnote, we need to accumulate. + in_footnote.push(html); + None + } + } + // While inside a footnote, accumulate all events into a local. + _ if !in_footnote_name.is_empty() => { + in_footnote.push(event); + None + } + _ => Some(event), + } + }); + + html::push_html(&mut body, events); + + if !footnote_defs.is_empty() { + add_footnote_defs( + &mut body, + path, + footnote_defs.into_iter().collect(), + &footnote_numbers, + ); + } + + body +} + +/// Adds all footnote definitions into `body`. +fn add_footnote_defs( + body: &mut String, + path: Option<&Path>, + mut defs: Vec<(String, Vec>)>, + numbers: &HashMap, +) { + // Remove unused. + defs.retain(|(name, _)| { + if !numbers.contains_key(name) { + log::warn!( + "footnote `{name}` in `{}` is defined but not referenced", + path.map_or_else(|| Cow::from(""), |p| p.to_string_lossy()) + ); + false + } else { + true + } + }); + + defs.sort_by_cached_key(|(name, _)| numbers[name].0); + + body.push_str( + "
\n\ +
    ", + ); + + // Insert the backrefs to the definition, and put the definitions in the output. + for (name, mut fn_events) in defs { + let count = numbers[&name].1; + fn_events.insert( + 0, + Event::Html(format!("
  1. ").into()), + ); + // Generate the linkbacks. + for usage in 1..=count { + let nth = if usage == 1 { + String::new() + } else { + usage.to_string() + }; + let backlink = + Event::Html(format!(" ↩{nth}").into()); + if matches!(fn_events.last(), Some(Event::End(TagEnd::Paragraph))) { + // Put the linkback at the end of the last paragraph instead + // of on a line by itself. + fn_events.insert(fn_events.len() - 1, backlink); + } else { + // Not a clear place to put it in this circumstance, so put it + // at the end. + fn_events.push(backlink); + } + } + fn_events.push(Event::Html("
  2. \n".into())); + html::push_html(body, fn_events.into_iter()); + } + + body.push_str("
"); +} + +/// Wraps tables in a `.table-wrapper` class to apply overflow-x rules to. +fn wrap_tables(event: Event<'_>) -> (Option>, Option>) { + match event { + Event::Start(Tag::Table(_)) => ( + Some(Event::Html(r#"
"#.into())), + Some(event), + ), + Event::End(TagEnd::Table) => (Some(event), Some(Event::Html(r#"
"#.into()))), + _ => (Some(event), None), + } +} + +fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> { + match event { + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => { + let info: String = info + .chars() + .map(|x| match x { + ' ' | '\t' => ',', + _ => x, + }) + .filter(|ch| !ch.is_whitespace()) + .collect(); + + Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::from(info)))) + } + _ => event, + } +} + +/// Fix links to the correct location. +/// +/// This adjusts links, such as turning `.md` extensions to `.html`. +/// +/// `path` is the path to the page being rendered relative to the root of the +/// book. This is used for the `print.html` page so that links on the print +/// page go to the original location. Normal page rendering sets `path` to +/// None. Ideally, print page links would link to anchors on the print page, +/// but that is very difficult. +fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> { + static SCHEME_LINK: LazyLock = + LazyLock::new(|| Regex::new(r"^[a-z][a-z0-9+.-]*:").unwrap()); + static MD_LINK: LazyLock = + LazyLock::new(|| Regex::new(r"(?P.*)\.md(?P#.*)?").unwrap()); + + fn fix<'a>(dest: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> { + if dest.starts_with('#') { + // Fragment-only link. + if let Some(path) = path { + let mut base = path.display().to_string(); + if base.ends_with(".md") { + base.replace_range(base.len() - 3.., ".html"); + } + return format!("{base}{dest}").into(); + } else { + return dest; + } + } + // Don't modify links with schemes like `https`. + if !SCHEME_LINK.is_match(&dest) { + // This is a relative link, adjust it as necessary. + let mut fixed_link = String::new(); + if let Some(path) = path { + let base = path + .parent() + .expect("path can't be empty") + .to_str() + .expect("utf-8 paths only"); + if !base.is_empty() { + write!(fixed_link, "{base}/").unwrap(); + } + } + + if let Some(caps) = MD_LINK.captures(&dest) { + fixed_link.push_str(&caps["link"]); + fixed_link.push_str(".html"); + if let Some(anchor) = caps.name("anchor") { + fixed_link.push_str(anchor.as_str()); + } + } else { + fixed_link.push_str(&dest); + }; + return CowStr::from(fixed_link); + } + dest + } + + fn fix_html<'a>(html: CowStr<'a>, path: Option<&Path>) -> CowStr<'a> { + // This is a terrible hack, but should be reasonably reliable. Nobody + // should ever parse a tag with a regex. However, there isn't anything + // in Rust that I know of that is suitable for handling partial html + // fragments like those generated by pulldown_cmark. + // + // There are dozens of HTML tags/attributes that contain paths, so + // feel free to add more tags if desired; these are the only ones I + // care about right now. + static HTML_LINK: LazyLock = + LazyLock::new(|| Regex::new(r#"(<(?:a|img) [^>]*?(?:src|href)=")([^"]+?)""#).unwrap()); + + HTML_LINK + .replace_all(&html, |caps: ®ex::Captures<'_>| { + let fixed = fix(caps[2].into(), path); + format!("{}{}\"", &caps[1], fixed) + }) + .into_owned() + .into() + } + + match event { + Event::Start(Tag::Link { + link_type, + dest_url, + title, + id, + }) => Event::Start(Tag::Link { + link_type, + dest_url: fix(dest_url, path), + title, + id, + }), + Event::Start(Tag::Image { + link_type, + dest_url, + title, + id, + }) => Event::Start(Tag::Image { + link_type, + dest_url: fix(dest_url, path), + title, + id, + }), + Event::Html(html) => Event::Html(fix_html(html, path)), + Event::InlineHtml(html) => Event::InlineHtml(fix_html(html, path)), + _ => event, + } +} + +/// Escape characters to make it safe for an HTML string. +pub fn special_escape(mut s: &str) -> String { + let mut escaped = String::with_capacity(s.len()); + let needs_escape: &[char] = &['<', '>', '\'', '"', '\\', '&']; + while let Some(next) = s.find(needs_escape) { + escaped.push_str(&s[..next]); + match s.as_bytes()[next] { + b'<' => escaped.push_str("<"), + b'>' => escaped.push_str(">"), + b'\'' => escaped.push_str("'"), + b'"' => escaped.push_str("""), + b'\\' => escaped.push_str("\"), + b'&' => escaped.push_str("&"), + _ => unreachable!(), + } + s = &s[next + 1..]; + } + escaped.push_str(s); + escaped +} diff --git a/crates/mdbook-markdown/src/tests.rs b/crates/mdbook-markdown/src/tests.rs new file mode 100644 index 00000000..7f2aea27 --- /dev/null +++ b/crates/mdbook-markdown/src/tests.rs @@ -0,0 +1,147 @@ +use super::render_markdown; +use super::*; + +#[test] +fn escaped_special() { + assert_eq!(special_escape(""), ""); + assert_eq!(special_escape("<"), "<"); + assert_eq!(special_escape(">"), ">"); + assert_eq!(special_escape("<>"), "<>"); + assert_eq!(special_escape(""), "<test>"); + assert_eq!(special_escape("ab"), "a<test>b"); + assert_eq!(special_escape("'"), "'"); + assert_eq!(special_escape("\\"), "\"); + assert_eq!(special_escape("&"), "&"); +} + +#[test] +fn preserves_external_links() { + assert_eq!( + render_markdown("[example](https://www.rust-lang.org/)", false), + "

example

\n" + ); +} + +#[test] +fn it_can_adjust_markdown_links() { + assert_eq!( + render_markdown("[example](example.md)", false), + "

example

\n" + ); + assert_eq!( + render_markdown("[example_anchor](example.md#anchor)", false), + "

example_anchor

\n" + ); + + // this anchor contains 'md' inside of it + assert_eq!( + render_markdown("[phantom data](foo.html#phantomdata)", false), + "

phantom data

\n" + ); +} + +#[test] +fn it_can_wrap_tables() { + let src = r#" +| Original | Punycode | Punycode + Encoding | +|-----------------|-----------------|---------------------| +| føø | f-5gaa | f_5gaa | +"#; + let out = r#" +
+ +
OriginalPunycodePunycode + Encoding
føøf-5gaaf_5gaa
+
+"#.trim(); + assert_eq!(render_markdown(src, false), out); +} + +#[test] +fn it_can_keep_quotes_straight() { + assert_eq!(render_markdown("'one'", false), "

'one'

\n"); +} + +#[test] +fn it_can_make_quotes_curly_except_when_they_are_in_code() { + let input = r#" +'one' +``` +'two' +``` +`'three'` 'four'"#; + let expected = r#"

‘one’

+
'two'
+
+

'three' ‘four’

+"#; + assert_eq!(render_markdown(input, true), expected); +} + +#[test] +fn whitespace_outside_of_codeblock_header_is_preserved() { + let input = r#" +some text with spaces +```rust +fn main() { +// code inside is unchanged +} +``` +more text with spaces +"#; + + let expected = r#"

some text with spaces

+
fn main() {
+// code inside is unchanged
+}
+
+

more text with spaces

+"#; + assert_eq!(render_markdown(input, false), expected); + assert_eq!(render_markdown(input, true), expected); +} + +#[test] +fn rust_code_block_properties_are_passed_as_space_delimited_class() { + let input = r#" +```rust,no_run,should_panic,property_3 +``` +"#; + + let expected = r#"
+"#; + assert_eq!(render_markdown(input, false), expected); + assert_eq!(render_markdown(input, true), expected); +} + +#[test] +fn rust_code_block_properties_with_whitespace_are_passed_as_space_delimited_class() { + let input = r#" +```rust, no_run,,,should_panic , ,property_3 +``` +"#; + + let expected = r#"
+"#; + assert_eq!(render_markdown(input, false), expected); + assert_eq!(render_markdown(input, true), expected); +} + +#[test] +fn rust_code_block_without_properties_has_proper_html_class() { + let input = r#" +```rust +``` +"#; + + let expected = r#"
+"#; + assert_eq!(render_markdown(input, false), expected); + assert_eq!(render_markdown(input, true), expected); + + let input = r#" +```rust +``` +"#; + assert_eq!(render_markdown(input, false), expected); + assert_eq!(render_markdown(input, true), expected); +} diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index e601c82b..1012e907 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -17,6 +17,8 @@ use log::{debug, info, trace, warn}; use mdbook_core::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; use mdbook_core::utils; use mdbook_core::utils::fs::get_404_output_file; +use mdbook_markdown::{render_markdown, render_markdown_with_path}; + use regex::{Captures, Regex}; use serde_json::json; @@ -57,13 +59,10 @@ impl HtmlHandlebars { .insert("git_repository_edit_url".to_owned(), json!(edit_url)); } - let content = utils::render_markdown(&ch.content, ctx.html_config.smart_punctuation()); + let content = render_markdown(&ch.content, ctx.html_config.smart_punctuation()); - let fixed_content = utils::render_markdown_with_path( - &ch.content, - ctx.html_config.smart_punctuation(), - Some(path), - ); + let fixed_content = + render_markdown_with_path(&ch.content, ctx.html_config.smart_punctuation(), Some(path)); if !ctx.is_index && ctx.html_config.print.page_break { // Add page break between chapters // See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before @@ -178,8 +177,7 @@ impl HtmlHandlebars { .to_string() } }; - let html_content_404 = - utils::render_markdown(&content_404, html_config.smart_punctuation()); + let html_content_404 = render_markdown(&content_404, html_config.smart_punctuation()); let mut data_404 = data.clone(); let base_url = if let Some(site_url) = &html_config.site_url { diff --git a/src/renderer/html_handlebars/helpers/toc.rs b/src/renderer/html_handlebars/helpers/toc.rs index 9528a355..daf2dc39 100644 --- a/src/renderer/html_handlebars/helpers/toc.rs +++ b/src/renderer/html_handlebars/helpers/toc.rs @@ -1,11 +1,10 @@ use std::path::Path; use std::{cmp::Ordering, collections::BTreeMap}; -use mdbook_core::utils::special_escape; - use handlebars::{ Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError, RenderErrorReason, }; +use mdbook_markdown::special_escape; // Handlebars helper to construct TOC #[derive(Clone, Copy)] diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 054e16d9..02a24b2c 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -8,6 +8,7 @@ use elasticlunr::{Index, IndexBuilder}; use log::{debug, warn}; use mdbook_core::config::{Search, SearchChapterSettings}; use mdbook_core::utils; +use mdbook_markdown::new_cmark_parser; use pulldown_cmark::*; use serde::Serialize; @@ -134,7 +135,7 @@ fn render_item( .with_context(|| "Could not convert HTML path to str")?; let anchor_base = utils::fs::normalize_path(filepath); - let mut p = utils::new_cmark_parser(&chapter.content, false).peekable(); + let mut p = new_cmark_parser(&chapter.content, false).peekable(); let mut in_heading = false; let max_section_depth = u32::from(search_config.heading_split_level); diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs index 3fc08425..d9851559 100644 --- a/tests/testsuite/markdown.rs +++ b/tests/testsuite/markdown.rs @@ -22,10 +22,10 @@ fn footnotes() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `multiple-definitions` in defined multiple times - not updating to new definition -[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `unused` in `` is defined but not referenced -[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition -[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `unused` in `footnotes.md` is defined but not referenced +[TIMESTAMP] [WARN] (mdbook_markdown): footnote `multiple-definitions` in defined multiple times - not updating to new definition +[TIMESTAMP] [WARN] (mdbook_markdown): footnote `unused` in `` is defined but not referenced +[TIMESTAMP] [WARN] (mdbook_markdown): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition +[TIMESTAMP] [WARN] (mdbook_markdown): footnote `unused` in `footnotes.md` is defined but not referenced [TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); From 6805740e20ff2d46b966c3c8c789c94f8d7db049 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 18:06:49 -0700 Subject: [PATCH 23/44] Add mdbook-html This new crate will hold the HTML renderer and related front-end parts. --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/mdbook-html/Cargo.toml | 13 +++++++++++++ crates/mdbook-html/src/lib.rs | 1 + 4 files changed, 19 insertions(+) create mode 100644 crates/mdbook-html/Cargo.toml create mode 100644 crates/mdbook-html/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2a5589f0..c75a93ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1312,6 +1312,10 @@ dependencies = [ "toml", ] +[[package]] +name = "mdbook-html" +version = "0.5.0-alpha.1" + [[package]] name = "mdbook-markdown" version = "0.5.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index b4cc16fd..6d26730a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow anyhow = "1.0.98" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } +mdbook-html = { path = "crates/mdbook-html" } mdbook-markdown = { path = "crates/mdbook-markdown" } mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } mdbook-renderer = { path = "crates/mdbook-renderer" } diff --git a/crates/mdbook-html/Cargo.toml b/crates/mdbook-html/Cargo.toml new file mode 100644 index 00000000..16dec7bf --- /dev/null +++ b/crates/mdbook-html/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mdbook-html" +version = "0.5.0-alpha.1" +description = "mdBook HTML renderer" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/mdbook-html/src/lib.rs b/crates/mdbook-html/src/lib.rs new file mode 100644 index 00000000..08c7aef0 --- /dev/null +++ b/crates/mdbook-html/src/lib.rs @@ -0,0 +1 @@ +//! mdBook HTML renderer. From 308768655929ec0d5ab440ddb71cae8a0e14fccb Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 18:07:42 -0700 Subject: [PATCH 24/44] Move theme to mdbook-html This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-html. Additional commits will refactor/move/remove items. --- .../mdbook-html}/front-end/css/ayu-highlight.css | 0 .../mdbook-html}/front-end/css/chrome.css | 0 .../mdbook-html}/front-end/css/font-awesome.min.css | 0 .../mdbook-html}/front-end/css/general.css | 0 .../mdbook-html}/front-end/css/highlight.css | 0 {src => crates/mdbook-html}/front-end/css/print.css | 0 .../mdbook-html}/front-end/css/tomorrow-night.css | 0 .../mdbook-html}/front-end/css/variables.css | 0 .../mdbook-html}/front-end/fonts/FontAwesome.otf | Bin .../front-end/fonts/OPEN-SANS-LICENSE.txt | 0 .../front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt | 0 .../front-end/fonts/fontawesome-webfont.eot | Bin .../front-end/fonts/fontawesome-webfont.svg | 0 .../front-end/fonts/fontawesome-webfont.ttf | Bin .../front-end/fonts/fontawesome-webfont.woff | Bin .../front-end/fonts/fontawesome-webfont.woff2 | Bin .../mdbook-html}/front-end/fonts/fonts.css | 0 .../fonts/open-sans-v17-all-charsets-300.woff2 | Bin .../open-sans-v17-all-charsets-300italic.woff2 | Bin .../fonts/open-sans-v17-all-charsets-600.woff2 | Bin .../open-sans-v17-all-charsets-600italic.woff2 | Bin .../fonts/open-sans-v17-all-charsets-700.woff2 | Bin .../open-sans-v17-all-charsets-700italic.woff2 | Bin .../fonts/open-sans-v17-all-charsets-800.woff2 | Bin .../open-sans-v17-all-charsets-800italic.woff2 | Bin .../fonts/open-sans-v17-all-charsets-italic.woff2 | Bin .../fonts/open-sans-v17-all-charsets-regular.woff2 | Bin .../source-code-pro-v11-all-charsets-500.woff2 | Bin .../mdbook-html}/front-end/images/favicon.png | Bin .../mdbook-html}/front-end/images/favicon.svg | 0 {src => crates/mdbook-html}/front-end/js/book.js | 0 .../mdbook-html}/front-end/js/clipboard.min.js | 0 .../mdbook-html}/front-end/js/highlight.js | 0 .../mdbook-html}/front-end/playground_editor/ace.js | 0 .../front-end/playground_editor/editor.js | 0 .../front-end/playground_editor/mode-rust.js | 0 .../front-end/playground_editor/theme-dawn.js | 0 .../playground_editor/theme-tomorrow_night.js | 0 .../front-end/searcher/elasticlunr.min.js | 0 .../mdbook-html}/front-end/searcher/mark.min.js | 0 .../mdbook-html}/front-end/searcher/searcher.js | 0 .../mdbook-html}/front-end/templates/head.hbs | 0 .../mdbook-html}/front-end/templates/header.hbs | 0 .../mdbook-html}/front-end/templates/index.hbs | 0 .../mdbook-html}/front-end/templates/redirect.hbs | 0 .../mdbook-html}/front-end/templates/toc.html.hbs | 0 .../mdbook-html}/front-end/templates/toc.js.hbs | 0 .../mod.rs => crates/mdbook-html/src/theme/fonts.rs | 0 .../mdbook-html/src/theme}/mod.rs | 0 .../mdbook-html/src/theme/playground_editor.rs | 0 .../mdbook-html/src/theme/searcher.rs | 0 package.json | 4 ++-- 52 files changed, 2 insertions(+), 2 deletions(-) rename {src => crates/mdbook-html}/front-end/css/ayu-highlight.css (100%) rename {src => crates/mdbook-html}/front-end/css/chrome.css (100%) rename {src => crates/mdbook-html}/front-end/css/font-awesome.min.css (100%) rename {src => crates/mdbook-html}/front-end/css/general.css (100%) rename {src => crates/mdbook-html}/front-end/css/highlight.css (100%) rename {src => crates/mdbook-html}/front-end/css/print.css (100%) rename {src => crates/mdbook-html}/front-end/css/tomorrow-night.css (100%) rename {src => crates/mdbook-html}/front-end/css/variables.css (100%) rename {src => crates/mdbook-html}/front-end/fonts/FontAwesome.otf (100%) rename {src => crates/mdbook-html}/front-end/fonts/OPEN-SANS-LICENSE.txt (100%) rename {src => crates/mdbook-html}/front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt (100%) rename {src => crates/mdbook-html}/front-end/fonts/fontawesome-webfont.eot (100%) rename {src => crates/mdbook-html}/front-end/fonts/fontawesome-webfont.svg (100%) rename {src => crates/mdbook-html}/front-end/fonts/fontawesome-webfont.ttf (100%) rename {src => crates/mdbook-html}/front-end/fonts/fontawesome-webfont.woff (100%) rename {src => crates/mdbook-html}/front-end/fonts/fontawesome-webfont.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/fonts.css (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-300.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-300italic.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-600.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-600italic.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-700.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-700italic.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-800.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-800italic.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-italic.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/open-sans-v17-all-charsets-regular.woff2 (100%) rename {src => crates/mdbook-html}/front-end/fonts/source-code-pro-v11-all-charsets-500.woff2 (100%) rename {src => crates/mdbook-html}/front-end/images/favicon.png (100%) rename {src => crates/mdbook-html}/front-end/images/favicon.svg (100%) rename {src => crates/mdbook-html}/front-end/js/book.js (100%) rename {src => crates/mdbook-html}/front-end/js/clipboard.min.js (100%) rename {src => crates/mdbook-html}/front-end/js/highlight.js (100%) rename {src => crates/mdbook-html}/front-end/playground_editor/ace.js (100%) rename {src => crates/mdbook-html}/front-end/playground_editor/editor.js (100%) rename {src => crates/mdbook-html}/front-end/playground_editor/mode-rust.js (100%) rename {src => crates/mdbook-html}/front-end/playground_editor/theme-dawn.js (100%) rename {src => crates/mdbook-html}/front-end/playground_editor/theme-tomorrow_night.js (100%) rename {src => crates/mdbook-html}/front-end/searcher/elasticlunr.min.js (100%) rename {src => crates/mdbook-html}/front-end/searcher/mark.min.js (100%) rename {src => crates/mdbook-html}/front-end/searcher/searcher.js (100%) rename {src => crates/mdbook-html}/front-end/templates/head.hbs (100%) rename {src => crates/mdbook-html}/front-end/templates/header.hbs (100%) rename {src => crates/mdbook-html}/front-end/templates/index.hbs (100%) rename {src => crates/mdbook-html}/front-end/templates/redirect.hbs (100%) rename {src => crates/mdbook-html}/front-end/templates/toc.html.hbs (100%) rename {src => crates/mdbook-html}/front-end/templates/toc.js.hbs (100%) rename src/front-end/fonts/mod.rs => crates/mdbook-html/src/theme/fonts.rs (100%) rename {src/front-end => crates/mdbook-html/src/theme}/mod.rs (100%) rename src/front-end/playground_editor/mod.rs => crates/mdbook-html/src/theme/playground_editor.rs (100%) rename src/front-end/searcher/mod.rs => crates/mdbook-html/src/theme/searcher.rs (100%) diff --git a/src/front-end/css/ayu-highlight.css b/crates/mdbook-html/front-end/css/ayu-highlight.css similarity index 100% rename from src/front-end/css/ayu-highlight.css rename to crates/mdbook-html/front-end/css/ayu-highlight.css diff --git a/src/front-end/css/chrome.css b/crates/mdbook-html/front-end/css/chrome.css similarity index 100% rename from src/front-end/css/chrome.css rename to crates/mdbook-html/front-end/css/chrome.css diff --git a/src/front-end/css/font-awesome.min.css b/crates/mdbook-html/front-end/css/font-awesome.min.css similarity index 100% rename from src/front-end/css/font-awesome.min.css rename to crates/mdbook-html/front-end/css/font-awesome.min.css diff --git a/src/front-end/css/general.css b/crates/mdbook-html/front-end/css/general.css similarity index 100% rename from src/front-end/css/general.css rename to crates/mdbook-html/front-end/css/general.css diff --git a/src/front-end/css/highlight.css b/crates/mdbook-html/front-end/css/highlight.css similarity index 100% rename from src/front-end/css/highlight.css rename to crates/mdbook-html/front-end/css/highlight.css diff --git a/src/front-end/css/print.css b/crates/mdbook-html/front-end/css/print.css similarity index 100% rename from src/front-end/css/print.css rename to crates/mdbook-html/front-end/css/print.css diff --git a/src/front-end/css/tomorrow-night.css b/crates/mdbook-html/front-end/css/tomorrow-night.css similarity index 100% rename from src/front-end/css/tomorrow-night.css rename to crates/mdbook-html/front-end/css/tomorrow-night.css diff --git a/src/front-end/css/variables.css b/crates/mdbook-html/front-end/css/variables.css similarity index 100% rename from src/front-end/css/variables.css rename to crates/mdbook-html/front-end/css/variables.css diff --git a/src/front-end/fonts/FontAwesome.otf b/crates/mdbook-html/front-end/fonts/FontAwesome.otf similarity index 100% rename from src/front-end/fonts/FontAwesome.otf rename to crates/mdbook-html/front-end/fonts/FontAwesome.otf diff --git a/src/front-end/fonts/OPEN-SANS-LICENSE.txt b/crates/mdbook-html/front-end/fonts/OPEN-SANS-LICENSE.txt similarity index 100% rename from src/front-end/fonts/OPEN-SANS-LICENSE.txt rename to crates/mdbook-html/front-end/fonts/OPEN-SANS-LICENSE.txt diff --git a/src/front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt b/crates/mdbook-html/front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt similarity index 100% rename from src/front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt rename to crates/mdbook-html/front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt diff --git a/src/front-end/fonts/fontawesome-webfont.eot b/crates/mdbook-html/front-end/fonts/fontawesome-webfont.eot similarity index 100% rename from src/front-end/fonts/fontawesome-webfont.eot rename to crates/mdbook-html/front-end/fonts/fontawesome-webfont.eot diff --git a/src/front-end/fonts/fontawesome-webfont.svg b/crates/mdbook-html/front-end/fonts/fontawesome-webfont.svg similarity index 100% rename from src/front-end/fonts/fontawesome-webfont.svg rename to crates/mdbook-html/front-end/fonts/fontawesome-webfont.svg diff --git a/src/front-end/fonts/fontawesome-webfont.ttf b/crates/mdbook-html/front-end/fonts/fontawesome-webfont.ttf similarity index 100% rename from src/front-end/fonts/fontawesome-webfont.ttf rename to crates/mdbook-html/front-end/fonts/fontawesome-webfont.ttf diff --git a/src/front-end/fonts/fontawesome-webfont.woff b/crates/mdbook-html/front-end/fonts/fontawesome-webfont.woff similarity index 100% rename from src/front-end/fonts/fontawesome-webfont.woff rename to crates/mdbook-html/front-end/fonts/fontawesome-webfont.woff diff --git a/src/front-end/fonts/fontawesome-webfont.woff2 b/crates/mdbook-html/front-end/fonts/fontawesome-webfont.woff2 similarity index 100% rename from src/front-end/fonts/fontawesome-webfont.woff2 rename to crates/mdbook-html/front-end/fonts/fontawesome-webfont.woff2 diff --git a/src/front-end/fonts/fonts.css b/crates/mdbook-html/front-end/fonts/fonts.css similarity index 100% rename from src/front-end/fonts/fonts.css rename to crates/mdbook-html/front-end/fonts/fonts.css diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-300.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-300.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-300.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-300.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-300italic.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-300italic.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-300italic.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-300italic.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-600.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-600.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-600.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-600.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-600italic.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-600italic.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-600italic.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-600italic.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-700.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-700.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-700.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-700.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-700italic.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-700italic.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-700italic.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-700italic.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-800.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-800.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-800.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-800.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-800italic.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-800italic.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-800italic.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-800italic.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-italic.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-italic.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-italic.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-italic.woff2 diff --git a/src/front-end/fonts/open-sans-v17-all-charsets-regular.woff2 b/crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-regular.woff2 similarity index 100% rename from src/front-end/fonts/open-sans-v17-all-charsets-regular.woff2 rename to crates/mdbook-html/front-end/fonts/open-sans-v17-all-charsets-regular.woff2 diff --git a/src/front-end/fonts/source-code-pro-v11-all-charsets-500.woff2 b/crates/mdbook-html/front-end/fonts/source-code-pro-v11-all-charsets-500.woff2 similarity index 100% rename from src/front-end/fonts/source-code-pro-v11-all-charsets-500.woff2 rename to crates/mdbook-html/front-end/fonts/source-code-pro-v11-all-charsets-500.woff2 diff --git a/src/front-end/images/favicon.png b/crates/mdbook-html/front-end/images/favicon.png similarity index 100% rename from src/front-end/images/favicon.png rename to crates/mdbook-html/front-end/images/favicon.png diff --git a/src/front-end/images/favicon.svg b/crates/mdbook-html/front-end/images/favicon.svg similarity index 100% rename from src/front-end/images/favicon.svg rename to crates/mdbook-html/front-end/images/favicon.svg diff --git a/src/front-end/js/book.js b/crates/mdbook-html/front-end/js/book.js similarity index 100% rename from src/front-end/js/book.js rename to crates/mdbook-html/front-end/js/book.js diff --git a/src/front-end/js/clipboard.min.js b/crates/mdbook-html/front-end/js/clipboard.min.js similarity index 100% rename from src/front-end/js/clipboard.min.js rename to crates/mdbook-html/front-end/js/clipboard.min.js diff --git a/src/front-end/js/highlight.js b/crates/mdbook-html/front-end/js/highlight.js similarity index 100% rename from src/front-end/js/highlight.js rename to crates/mdbook-html/front-end/js/highlight.js diff --git a/src/front-end/playground_editor/ace.js b/crates/mdbook-html/front-end/playground_editor/ace.js similarity index 100% rename from src/front-end/playground_editor/ace.js rename to crates/mdbook-html/front-end/playground_editor/ace.js diff --git a/src/front-end/playground_editor/editor.js b/crates/mdbook-html/front-end/playground_editor/editor.js similarity index 100% rename from src/front-end/playground_editor/editor.js rename to crates/mdbook-html/front-end/playground_editor/editor.js diff --git a/src/front-end/playground_editor/mode-rust.js b/crates/mdbook-html/front-end/playground_editor/mode-rust.js similarity index 100% rename from src/front-end/playground_editor/mode-rust.js rename to crates/mdbook-html/front-end/playground_editor/mode-rust.js diff --git a/src/front-end/playground_editor/theme-dawn.js b/crates/mdbook-html/front-end/playground_editor/theme-dawn.js similarity index 100% rename from src/front-end/playground_editor/theme-dawn.js rename to crates/mdbook-html/front-end/playground_editor/theme-dawn.js diff --git a/src/front-end/playground_editor/theme-tomorrow_night.js b/crates/mdbook-html/front-end/playground_editor/theme-tomorrow_night.js similarity index 100% rename from src/front-end/playground_editor/theme-tomorrow_night.js rename to crates/mdbook-html/front-end/playground_editor/theme-tomorrow_night.js diff --git a/src/front-end/searcher/elasticlunr.min.js b/crates/mdbook-html/front-end/searcher/elasticlunr.min.js similarity index 100% rename from src/front-end/searcher/elasticlunr.min.js rename to crates/mdbook-html/front-end/searcher/elasticlunr.min.js diff --git a/src/front-end/searcher/mark.min.js b/crates/mdbook-html/front-end/searcher/mark.min.js similarity index 100% rename from src/front-end/searcher/mark.min.js rename to crates/mdbook-html/front-end/searcher/mark.min.js diff --git a/src/front-end/searcher/searcher.js b/crates/mdbook-html/front-end/searcher/searcher.js similarity index 100% rename from src/front-end/searcher/searcher.js rename to crates/mdbook-html/front-end/searcher/searcher.js diff --git a/src/front-end/templates/head.hbs b/crates/mdbook-html/front-end/templates/head.hbs similarity index 100% rename from src/front-end/templates/head.hbs rename to crates/mdbook-html/front-end/templates/head.hbs diff --git a/src/front-end/templates/header.hbs b/crates/mdbook-html/front-end/templates/header.hbs similarity index 100% rename from src/front-end/templates/header.hbs rename to crates/mdbook-html/front-end/templates/header.hbs diff --git a/src/front-end/templates/index.hbs b/crates/mdbook-html/front-end/templates/index.hbs similarity index 100% rename from src/front-end/templates/index.hbs rename to crates/mdbook-html/front-end/templates/index.hbs diff --git a/src/front-end/templates/redirect.hbs b/crates/mdbook-html/front-end/templates/redirect.hbs similarity index 100% rename from src/front-end/templates/redirect.hbs rename to crates/mdbook-html/front-end/templates/redirect.hbs diff --git a/src/front-end/templates/toc.html.hbs b/crates/mdbook-html/front-end/templates/toc.html.hbs similarity index 100% rename from src/front-end/templates/toc.html.hbs rename to crates/mdbook-html/front-end/templates/toc.html.hbs diff --git a/src/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs similarity index 100% rename from src/front-end/templates/toc.js.hbs rename to crates/mdbook-html/front-end/templates/toc.js.hbs diff --git a/src/front-end/fonts/mod.rs b/crates/mdbook-html/src/theme/fonts.rs similarity index 100% rename from src/front-end/fonts/mod.rs rename to crates/mdbook-html/src/theme/fonts.rs diff --git a/src/front-end/mod.rs b/crates/mdbook-html/src/theme/mod.rs similarity index 100% rename from src/front-end/mod.rs rename to crates/mdbook-html/src/theme/mod.rs diff --git a/src/front-end/playground_editor/mod.rs b/crates/mdbook-html/src/theme/playground_editor.rs similarity index 100% rename from src/front-end/playground_editor/mod.rs rename to crates/mdbook-html/src/theme/playground_editor.rs diff --git a/src/front-end/searcher/mod.rs b/crates/mdbook-html/src/theme/searcher.rs similarity index 100% rename from src/front-end/searcher/mod.rs rename to crates/mdbook-html/src/theme/searcher.rs diff --git a/package.json b/package.json index dcb25296..48e72718 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "eslint": "^8.57.1" }, "scripts": { - "lint": "eslint src/front-end/*js src/front-end/**/*js", - "lint-fix": "eslint --fix src/front-end/*js src/front-end/**/*js" + "lint": "eslint crates/mdbook-html/front-end/*js crates/mdbook-html/front-end/**/*js", + "lint-fix": "eslint --fix crates/mdbook-html/front-end/*js crates/mdbook-html/front-end/**/*js" } } From 753780f653f9e3b15867bccf24549d612b3ec0f1 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 18:32:36 -0700 Subject: [PATCH 25/44] Finish move of theme to mdbook-html This updates everything for the move of theme to mdbook-html. There will be followup commits that will be doing more cleanup here. --- Cargo.lock | 6 ++ Cargo.toml | 3 +- crates/mdbook-html/Cargo.toml | 8 +++ crates/mdbook-html/src/lib.rs | 2 + crates/mdbook-html/src/theme/fonts.rs | 28 +++++----- crates/mdbook-html/src/theme/mod.rs | 55 ++++++++++--------- .../src/theme/playground_editor.rs | 11 ++-- crates/mdbook-html/src/theme/searcher.rs | 6 +- src/book/init.rs | 2 +- src/lib.rs | 2 - src/renderer/html_handlebars/hbs_renderer.rs | 4 +- src/renderer/html_handlebars/search.rs | 2 +- src/renderer/html_handlebars/static_files.rs | 4 +- 13 files changed, 77 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c75a93ba..dd1e8982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,7 @@ dependencies = [ "ignore", "log", "mdbook-core", + "mdbook-html", "mdbook-markdown", "mdbook-preprocessor", "mdbook-renderer", @@ -1315,6 +1316,11 @@ dependencies = [ [[package]] name = "mdbook-html" version = "0.5.0-alpha.1" +dependencies = [ + "anyhow", + "log", + "tempfile", +] [[package]] name = "mdbook-markdown" diff --git a/Cargo.toml b/Cargo.toml index 6d26730a..9505fe67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ handlebars = "6.0" hex = "0.4.3" log.workspace = true mdbook-core.workspace = true +mdbook-html.workspace = true mdbook-markdown.workspace = true mdbook-preprocessor.workspace = true mdbook-renderer.workspace = true @@ -109,7 +110,7 @@ walkdir = "2.3.3" default = ["watch", "serve", "search"] watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff", "dep:walkdir"] serve = ["dep:futures-util", "dep:tokio", "dep:axum", "dep:tower-http"] -search = ["dep:elasticlunr-rs", "dep:ammonia"] +search = ["dep:elasticlunr-rs", "dep:ammonia", "mdbook-html/search"] [[bin]] doc = false diff --git a/crates/mdbook-html/Cargo.toml b/crates/mdbook-html/Cargo.toml index 16dec7bf..c02aca64 100644 --- a/crates/mdbook-html/Cargo.toml +++ b/crates/mdbook-html/Cargo.toml @@ -8,6 +8,14 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow.workspace = true +log.workspace = true + +[dev-dependencies] +tempfile.workspace = true [lints] workspace = true + +[features] +search = [] diff --git a/crates/mdbook-html/src/lib.rs b/crates/mdbook-html/src/lib.rs index 08c7aef0..e32ac6bf 100644 --- a/crates/mdbook-html/src/lib.rs +++ b/crates/mdbook-html/src/lib.rs @@ -1 +1,3 @@ //! mdBook HTML renderer. + +pub mod theme; diff --git a/crates/mdbook-html/src/theme/fonts.rs b/crates/mdbook-html/src/theme/fonts.rs index 5d2e29cb..dc8c8c08 100644 --- a/crates/mdbook-html/src/theme/fonts.rs +++ b/crates/mdbook-html/src/theme/fonts.rs @@ -1,61 +1,61 @@ -pub static CSS: &[u8] = include_bytes!("fonts.css"); +pub static CSS: &[u8] = include_bytes!("../../front-end/fonts/fonts.css"); // An array of (file_name, file_contents) pairs pub static LICENSES: [(&str, &[u8]); 2] = [ ( "fonts/OPEN-SANS-LICENSE.txt", - include_bytes!("OPEN-SANS-LICENSE.txt"), + include_bytes!("../../front-end/fonts/OPEN-SANS-LICENSE.txt"), ), ( "fonts/SOURCE-CODE-PRO-LICENSE.txt", - include_bytes!("SOURCE-CODE-PRO-LICENSE.txt"), + include_bytes!("../../front-end/fonts/SOURCE-CODE-PRO-LICENSE.txt"), ), ]; // An array of (file_name, file_contents) pairs pub static OPEN_SANS: [(&str, &[u8]); 10] = [ ( "fonts/open-sans-v17-all-charsets-300.woff2", - include_bytes!("open-sans-v17-all-charsets-300.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-300.woff2"), ), ( "fonts/open-sans-v17-all-charsets-300italic.woff2", - include_bytes!("open-sans-v17-all-charsets-300italic.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-300italic.woff2"), ), ( "fonts/open-sans-v17-all-charsets-regular.woff2", - include_bytes!("open-sans-v17-all-charsets-regular.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-regular.woff2"), ), ( "fonts/open-sans-v17-all-charsets-italic.woff2", - include_bytes!("open-sans-v17-all-charsets-italic.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-italic.woff2"), ), ( "fonts/open-sans-v17-all-charsets-600.woff2", - include_bytes!("open-sans-v17-all-charsets-600.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-600.woff2"), ), ( "fonts/open-sans-v17-all-charsets-600italic.woff2", - include_bytes!("open-sans-v17-all-charsets-600italic.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-600italic.woff2"), ), ( "fonts/open-sans-v17-all-charsets-700.woff2", - include_bytes!("open-sans-v17-all-charsets-700.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-700.woff2"), ), ( "fonts/open-sans-v17-all-charsets-700italic.woff2", - include_bytes!("open-sans-v17-all-charsets-700italic.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-700italic.woff2"), ), ( "fonts/open-sans-v17-all-charsets-800.woff2", - include_bytes!("open-sans-v17-all-charsets-800.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-800.woff2"), ), ( "fonts/open-sans-v17-all-charsets-800italic.woff2", - include_bytes!("open-sans-v17-all-charsets-800italic.woff2"), + include_bytes!("../../front-end/fonts/open-sans-v17-all-charsets-800italic.woff2"), ), ]; // A (file_name, file_contents) pair pub static SOURCE_CODE_PRO: (&str, &[u8]) = ( "fonts/source-code-pro-v11-all-charsets-500.woff2", - include_bytes!("source-code-pro-v11-all-charsets-500.woff2"), + include_bytes!("../../front-end/fonts/source-code-pro-v11-all-charsets-500.woff2"), ); diff --git a/crates/mdbook-html/src/theme/mod.rs b/crates/mdbook-html/src/theme/mod.rs index fe7b4a2f..2547d3f9 100644 --- a/crates/mdbook-html/src/theme/mod.rs +++ b/crates/mdbook-html/src/theme/mod.rs @@ -11,31 +11,36 @@ pub mod playground_editor; #[cfg(feature = "search")] pub mod searcher; -pub static INDEX: &[u8] = include_bytes!("templates/index.hbs"); -pub static HEAD: &[u8] = include_bytes!("templates/head.hbs"); -pub static REDIRECT: &[u8] = include_bytes!("templates/redirect.hbs"); -pub static HEADER: &[u8] = include_bytes!("templates/header.hbs"); -pub static TOC_JS: &[u8] = include_bytes!("templates/toc.js.hbs"); -pub static TOC_HTML: &[u8] = include_bytes!("templates/toc.html.hbs"); -pub static CHROME_CSS: &[u8] = include_bytes!("css/chrome.css"); -pub static GENERAL_CSS: &[u8] = include_bytes!("css/general.css"); -pub static PRINT_CSS: &[u8] = include_bytes!("css/print.css"); -pub static VARIABLES_CSS: &[u8] = include_bytes!("css/variables.css"); -pub static FAVICON_PNG: &[u8] = include_bytes!("images/favicon.png"); -pub static FAVICON_SVG: &[u8] = include_bytes!("images/favicon.svg"); -pub static JS: &[u8] = include_bytes!("js/book.js"); -pub static HIGHLIGHT_JS: &[u8] = include_bytes!("js/highlight.js"); -pub static TOMORROW_NIGHT_CSS: &[u8] = include_bytes!("css/tomorrow-night.css"); -pub static HIGHLIGHT_CSS: &[u8] = include_bytes!("css/highlight.css"); -pub static AYU_HIGHLIGHT_CSS: &[u8] = include_bytes!("css/ayu-highlight.css"); -pub static CLIPBOARD_JS: &[u8] = include_bytes!("js/clipboard.min.js"); -pub static FONT_AWESOME: &[u8] = include_bytes!("css/font-awesome.min.css"); -pub static FONT_AWESOME_EOT: &[u8] = include_bytes!("fonts/fontawesome-webfont.eot"); -pub static FONT_AWESOME_SVG: &[u8] = include_bytes!("fonts/fontawesome-webfont.svg"); -pub static FONT_AWESOME_TTF: &[u8] = include_bytes!("fonts/fontawesome-webfont.ttf"); -pub static FONT_AWESOME_WOFF: &[u8] = include_bytes!("fonts/fontawesome-webfont.woff"); -pub static FONT_AWESOME_WOFF2: &[u8] = include_bytes!("fonts/fontawesome-webfont.woff2"); -pub static FONT_AWESOME_OTF: &[u8] = include_bytes!("fonts/FontAwesome.otf"); +pub static INDEX: &[u8] = include_bytes!("../../front-end/templates/index.hbs"); +pub static HEAD: &[u8] = include_bytes!("../../front-end/templates/head.hbs"); +pub static REDIRECT: &[u8] = include_bytes!("../../front-end/templates/redirect.hbs"); +pub static HEADER: &[u8] = include_bytes!("../../front-end/templates/header.hbs"); +pub static TOC_JS: &[u8] = include_bytes!("../../front-end/templates/toc.js.hbs"); +pub static TOC_HTML: &[u8] = include_bytes!("../../front-end/templates/toc.html.hbs"); +pub static CHROME_CSS: &[u8] = include_bytes!("../../front-end/css/chrome.css"); +pub static GENERAL_CSS: &[u8] = include_bytes!("../../front-end/css/general.css"); +pub static PRINT_CSS: &[u8] = include_bytes!("../../front-end/css/print.css"); +pub static VARIABLES_CSS: &[u8] = include_bytes!("../../front-end/css/variables.css"); +pub static FAVICON_PNG: &[u8] = include_bytes!("../../front-end/images/favicon.png"); +pub static FAVICON_SVG: &[u8] = include_bytes!("../../front-end/images/favicon.svg"); +pub static JS: &[u8] = include_bytes!("../../front-end/js/book.js"); +pub static HIGHLIGHT_JS: &[u8] = include_bytes!("../../front-end/js/highlight.js"); +pub static TOMORROW_NIGHT_CSS: &[u8] = include_bytes!("../../front-end/css/tomorrow-night.css"); +pub static HIGHLIGHT_CSS: &[u8] = include_bytes!("../../front-end/css/highlight.css"); +pub static AYU_HIGHLIGHT_CSS: &[u8] = include_bytes!("../../front-end/css/ayu-highlight.css"); +pub static CLIPBOARD_JS: &[u8] = include_bytes!("../../front-end/js/clipboard.min.js"); +pub static FONT_AWESOME: &[u8] = include_bytes!("../../front-end/css/font-awesome.min.css"); +pub static FONT_AWESOME_EOT: &[u8] = + include_bytes!("../../front-end/fonts/fontawesome-webfont.eot"); +pub static FONT_AWESOME_SVG: &[u8] = + include_bytes!("../../front-end/fonts/fontawesome-webfont.svg"); +pub static FONT_AWESOME_TTF: &[u8] = + include_bytes!("../../front-end/fonts/fontawesome-webfont.ttf"); +pub static FONT_AWESOME_WOFF: &[u8] = + include_bytes!("../../front-end/fonts/fontawesome-webfont.woff"); +pub static FONT_AWESOME_WOFF2: &[u8] = + include_bytes!("../../front-end/fonts/fontawesome-webfont.woff2"); +pub static FONT_AWESOME_OTF: &[u8] = include_bytes!("../../front-end/fonts/FontAwesome.otf"); /// The `Theme` struct should be used instead of the static variables because /// the `new()` method will look if the user has a theme directory in their diff --git a/crates/mdbook-html/src/theme/playground_editor.rs b/crates/mdbook-html/src/theme/playground_editor.rs index 19dbf4bc..c2462e2f 100644 --- a/crates/mdbook-html/src/theme/playground_editor.rs +++ b/crates/mdbook-html/src/theme/playground_editor.rs @@ -1,7 +1,8 @@ //! Theme dependencies for the playground editor. -pub static JS: &[u8] = include_bytes!("editor.js"); -pub static ACE_JS: &[u8] = include_bytes!("ace.js"); -pub static MODE_RUST_JS: &[u8] = include_bytes!("mode-rust.js"); -pub static THEME_DAWN_JS: &[u8] = include_bytes!("theme-dawn.js"); -pub static THEME_TOMORROW_NIGHT_JS: &[u8] = include_bytes!("theme-tomorrow_night.js"); +pub static JS: &[u8] = include_bytes!("../../front-end/playground_editor/editor.js"); +pub static ACE_JS: &[u8] = include_bytes!("../../front-end/playground_editor/ace.js"); +pub static MODE_RUST_JS: &[u8] = include_bytes!("../../front-end/playground_editor/mode-rust.js"); +pub static THEME_DAWN_JS: &[u8] = include_bytes!("../../front-end/playground_editor/theme-dawn.js"); +pub static THEME_TOMORROW_NIGHT_JS: &[u8] = + include_bytes!("../../front-end/playground_editor/theme-tomorrow_night.js"); diff --git a/crates/mdbook-html/src/theme/searcher.rs b/crates/mdbook-html/src/theme/searcher.rs index d5029db1..f1a839b2 100644 --- a/crates/mdbook-html/src/theme/searcher.rs +++ b/crates/mdbook-html/src/theme/searcher.rs @@ -1,6 +1,6 @@ //! Theme dependencies for in-browser search. Not included in mdbook when //! the "search" cargo feature is disabled. -pub static JS: &[u8] = include_bytes!("searcher.js"); -pub static MARK_JS: &[u8] = include_bytes!("mark.min.js"); -pub static ELASTICLUNR_JS: &[u8] = include_bytes!("elasticlunr.min.js"); +pub static JS: &[u8] = include_bytes!("../../front-end/searcher/searcher.js"); +pub static MARK_JS: &[u8] = include_bytes!("../../front-end/searcher/mark.min.js"); +pub static ELASTICLUNR_JS: &[u8] = include_bytes!("../../front-end/searcher/elasticlunr.min.js"); diff --git a/src/book/init.rs b/src/book/init.rs index 13e0d687..a179c63b 100644 --- a/src/book/init.rs +++ b/src/book/init.rs @@ -3,11 +3,11 @@ use std::io::Write; use std::path::PathBuf; use super::MDBook; -use crate::theme; use anyhow::{Context, Result}; use log::{debug, error, info, trace}; use mdbook_core::config::Config; use mdbook_core::utils::fs::write_file; +use mdbook_html::theme; /// A helper for setting up a new book and its directory structure. #[derive(Debug, Clone, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 7a8c6e00..e152fcc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,8 +83,6 @@ pub mod book; pub mod preprocess; pub mod renderer; -#[path = "front-end/mod.rs"] -pub mod theme; pub use crate::book::BookItem; pub use crate::book::MDBook; diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 1012e907..2e4844e3 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -2,7 +2,6 @@ use crate::book::{Book, BookItem}; use crate::renderer::html_handlebars::StaticFiles; use crate::renderer::html_handlebars::helpers; use crate::renderer::{RenderContext, Renderer}; -use crate::theme::{self, Theme}; use std::borrow::Cow; use std::collections::BTreeMap; @@ -17,6 +16,7 @@ use log::{debug, info, trace, warn}; use mdbook_core::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; use mdbook_core::utils; use mdbook_core::utils::fs::get_404_output_file; +use mdbook_html::theme::Theme; use mdbook_markdown::{render_markdown, render_markdown_with_path}; use regex::{Captures, Regex}; @@ -362,7 +362,7 @@ impl Renderer for HtmlHandlebars { None => ctx.root.join("theme"), }; - let theme = theme::Theme::new(theme_dir); + let theme = Theme::new(theme_dir); debug!("Register the index handlebars template"); handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?; diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 02a24b2c..9f63ba74 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -8,13 +8,13 @@ use elasticlunr::{Index, IndexBuilder}; use log::{debug, warn}; use mdbook_core::config::{Search, SearchChapterSettings}; use mdbook_core::utils; +use mdbook_html::theme::searcher; use mdbook_markdown::new_cmark_parser; use pulldown_cmark::*; use serde::Serialize; use crate::book::{Book, BookItem, Chapter}; use crate::renderer::html_handlebars::StaticFiles; -use crate::theme::searcher; const MAX_WORD_LENGTH_TO_INDEX: usize = 80; diff --git a/src/renderer/html_handlebars/static_files.rs b/src/renderer/html_handlebars/static_files.rs index de03585e..2436d763 100644 --- a/src/renderer/html_handlebars/static_files.rs +++ b/src/renderer/html_handlebars/static_files.rs @@ -4,9 +4,9 @@ use anyhow::{Context, Result}; use log::{debug, warn}; use mdbook_core::config::HtmlConfig; use mdbook_core::utils; +use mdbook_html::theme::{self, Theme, playground_editor}; use crate::renderer::html_handlebars::helpers::resources::ResourceHelper; -use crate::theme::{self, Theme, playground_editor}; use std::borrow::Cow; use std::collections::HashMap; @@ -300,9 +300,9 @@ impl StaticFiles { #[cfg(test)] mod tests { use super::*; - use crate::theme::Theme; use mdbook_core::config::HtmlConfig; use mdbook_core::utils::fs::write_file; + use mdbook_html::theme::Theme; use tempfile::TempDir; #[test] From 06324d8b2430a9c27ddd19443b9c14073bb2055b Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 20:17:11 -0700 Subject: [PATCH 26/44] Move hbs_renderer to mdbook-html This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-html. Additional commits will refactor/move/remove items. --- .../mdbook-html/src}/html_handlebars/hbs_renderer.rs | 0 .../mdbook-html/src}/html_handlebars/helpers/mod.rs | 0 .../mdbook-html/src}/html_handlebars/helpers/navigation.rs | 0 .../mdbook-html/src}/html_handlebars/helpers/resources.rs | 0 .../mdbook-html/src}/html_handlebars/helpers/theme.rs | 0 .../mdbook-html/src}/html_handlebars/helpers/toc.rs | 0 {src/renderer => crates/mdbook-html/src}/html_handlebars/mod.rs | 0 .../renderer => crates/mdbook-html/src}/html_handlebars/search.rs | 0 .../mdbook-html/src}/html_handlebars/static_files.rs | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/hbs_renderer.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/helpers/mod.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/helpers/navigation.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/helpers/resources.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/helpers/theme.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/helpers/toc.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/mod.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/search.rs (100%) rename {src/renderer => crates/mdbook-html/src}/html_handlebars/static_files.rs (100%) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs similarity index 100% rename from src/renderer/html_handlebars/hbs_renderer.rs rename to crates/mdbook-html/src/html_handlebars/hbs_renderer.rs diff --git a/src/renderer/html_handlebars/helpers/mod.rs b/crates/mdbook-html/src/html_handlebars/helpers/mod.rs similarity index 100% rename from src/renderer/html_handlebars/helpers/mod.rs rename to crates/mdbook-html/src/html_handlebars/helpers/mod.rs diff --git a/src/renderer/html_handlebars/helpers/navigation.rs b/crates/mdbook-html/src/html_handlebars/helpers/navigation.rs similarity index 100% rename from src/renderer/html_handlebars/helpers/navigation.rs rename to crates/mdbook-html/src/html_handlebars/helpers/navigation.rs diff --git a/src/renderer/html_handlebars/helpers/resources.rs b/crates/mdbook-html/src/html_handlebars/helpers/resources.rs similarity index 100% rename from src/renderer/html_handlebars/helpers/resources.rs rename to crates/mdbook-html/src/html_handlebars/helpers/resources.rs diff --git a/src/renderer/html_handlebars/helpers/theme.rs b/crates/mdbook-html/src/html_handlebars/helpers/theme.rs similarity index 100% rename from src/renderer/html_handlebars/helpers/theme.rs rename to crates/mdbook-html/src/html_handlebars/helpers/theme.rs diff --git a/src/renderer/html_handlebars/helpers/toc.rs b/crates/mdbook-html/src/html_handlebars/helpers/toc.rs similarity index 100% rename from src/renderer/html_handlebars/helpers/toc.rs rename to crates/mdbook-html/src/html_handlebars/helpers/toc.rs diff --git a/src/renderer/html_handlebars/mod.rs b/crates/mdbook-html/src/html_handlebars/mod.rs similarity index 100% rename from src/renderer/html_handlebars/mod.rs rename to crates/mdbook-html/src/html_handlebars/mod.rs diff --git a/src/renderer/html_handlebars/search.rs b/crates/mdbook-html/src/html_handlebars/search.rs similarity index 100% rename from src/renderer/html_handlebars/search.rs rename to crates/mdbook-html/src/html_handlebars/search.rs diff --git a/src/renderer/html_handlebars/static_files.rs b/crates/mdbook-html/src/html_handlebars/static_files.rs similarity index 100% rename from src/renderer/html_handlebars/static_files.rs rename to crates/mdbook-html/src/html_handlebars/static_files.rs From 7eccd1d556a21816c44e72af5f505d50ad3fe022 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 20:45:14 -0700 Subject: [PATCH 27/44] Finish move of hbs_renderer to mdbook-html This updates everything for the move of hbs_renderer to mdbook-html. --- Cargo.lock | 20 ++++++++---- Cargo.toml | 16 +++++----- crates/mdbook-html/Cargo.toml | 16 +++++++++- .../src/html_handlebars/hbs_renderer.rs | 31 +++++++++---------- crates/mdbook-html/src/html_handlebars/mod.rs | 8 ++--- .../mdbook-html/src/html_handlebars/search.rs | 26 +++++++--------- .../src/html_handlebars/static_files.rs | 8 ++--- crates/mdbook-html/src/lib.rs | 3 ++ src/book/mod.rs | 6 ++-- src/renderer/mod.rs | 2 -- tests/testsuite/build.rs | 2 +- tests/testsuite/includes.rs | 2 +- tests/testsuite/markdown.rs | 2 +- tests/testsuite/preprocessor.rs | 2 +- tests/testsuite/theme.rs | 10 +++--- 15 files changed, 83 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd1e8982..15b80779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1257,17 +1257,13 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" name = "mdbook" version = "0.5.0-alpha.1" dependencies = [ - "ammonia", "anyhow", "axum", "chrono", "clap", "clap_complete", - "elasticlunr-rs", "env_logger", "futures-util", - "handlebars", - "hex", "ignore", "log", "mdbook-core", @@ -1281,14 +1277,12 @@ dependencies = [ "notify-debouncer-mini", "opener", "pathdiff", - "pretty_assertions", "pulldown-cmark 0.10.3", "regex", "select", "semver", "serde", "serde_json", - "sha2", "shlex", "snapbox", "tempfile", @@ -1317,9 +1311,23 @@ dependencies = [ name = "mdbook-html" version = "0.5.0-alpha.1" dependencies = [ + "ammonia", "anyhow", + "elasticlunr-rs", + "handlebars", + "hex", "log", + "mdbook-core", + "mdbook-markdown", + "mdbook-renderer", + "pretty_assertions", + "pulldown-cmark 0.10.3", + "regex", + "serde", + "serde_json", + "sha2", "tempfile", + "toml", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9505fe67..88852bdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,11 @@ repository = "https://github.com/rust-lang/mdBook" rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflows/main.yml [workspace.dependencies] +ammonia = "4.1.1" anyhow = "1.0.98" +elasticlunr-rs = "3.0.2" +handlebars = "6.3.2" +hex = "0.4.3" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } mdbook-html = { path = "crates/mdbook-html" } @@ -30,10 +34,12 @@ mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } mdbook-renderer = { path = "crates/mdbook-renderer" } mdbook-summary = { path = "crates/mdbook-summary" } memchr = "2.7.5" +pretty_assertions = "1.4.1" pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" +sha2 = "0.10.9" tempfile = "3.20.0" toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037 @@ -61,8 +67,6 @@ chrono = { version = "0.4.24", default-features = false, features = ["clock"] } clap = { version = "4.3.12", features = ["cargo", "wrap_help"] } clap_complete = "4.3.2" env_logger = "0.11.1" -handlebars = "6.0" -hex = "0.4.3" log.workspace = true mdbook-core.workspace = true mdbook-html.workspace = true @@ -76,7 +80,6 @@ pulldown-cmark.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true -sha2 = "0.10.8" shlex = "1.3.0" tempfile.workspace = true toml.workspace = true @@ -95,22 +98,17 @@ tokio = { version = "1.43.1", features = ["macros", "rt-multi-thread"], optional axum = { version = "0.8.0", features = ["ws"], optional = true } tower-http = { version = "0.6.0", features = ["fs", "trace"], optional = true } -# Search feature -elasticlunr-rs = { version = "3.0.2", optional = true } -ammonia = { version = "4.0.0", optional = true } - [dev-dependencies] select = "0.6.0" semver = "1.0.17" snapbox = { version = "0.6.21", features = ["diff", "dir", "term-svg", "regex", "json"] } -pretty_assertions = "1.3.0" walkdir = "2.3.3" [features] default = ["watch", "serve", "search"] watch = ["dep:notify", "dep:notify-debouncer-mini", "dep:ignore", "dep:pathdiff", "dep:walkdir"] serve = ["dep:futures-util", "dep:tokio", "dep:axum", "dep:tower-http"] -search = ["dep:elasticlunr-rs", "dep:ammonia", "mdbook-html/search"] +search = ["mdbook-html/search"] [[bin]] doc = false diff --git a/crates/mdbook-html/Cargo.toml b/crates/mdbook-html/Cargo.toml index c02aca64..fafdd2e6 100644 --- a/crates/mdbook-html/Cargo.toml +++ b/crates/mdbook-html/Cargo.toml @@ -8,14 +8,28 @@ repository.workspace = true rust-version.workspace = true [dependencies] +ammonia = { workspace = true, optional = true } anyhow.workspace = true +elasticlunr-rs = { workspace = true, optional = true } +handlebars.workspace = true +hex.workspace = true log.workspace = true +mdbook-core.workspace = true +mdbook-markdown.workspace = true +mdbook-renderer.workspace = true +pulldown-cmark.workspace = true +regex.workspace = true +serde.workspace = true +serde_json.workspace = true +sha2.workspace = true [dev-dependencies] +pretty_assertions.workspace = true tempfile.workspace = true +toml.workspace = true [lints] workspace = true [features] -search = [] +search = ["dep:ammonia", "dep:elasticlunr-rs"] diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index 2e4844e3..e3fc55e8 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -1,8 +1,17 @@ -use crate::book::{Book, BookItem}; -use crate::renderer::html_handlebars::StaticFiles; -use crate::renderer::html_handlebars::helpers; -use crate::renderer::{RenderContext, Renderer}; - +use super::helpers; +use super::static_files::StaticFiles; +use crate::theme::Theme; +use anyhow::{Context, Result, bail}; +use handlebars::Handlebars; +use log::{debug, info, trace, warn}; +use mdbook_core::book::{Book, BookItem}; +use mdbook_core::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; +use mdbook_core::utils; +use mdbook_core::utils::fs::get_404_output_file; +use mdbook_markdown::{render_markdown, render_markdown_with_path}; +use mdbook_renderer::{RenderContext, Renderer}; +use regex::{Captures, Regex}; +use serde_json::json; use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::HashMap; @@ -10,18 +19,6 @@ use std::fs::{self, File}; use std::path::{Path, PathBuf}; use std::sync::LazyLock; -use anyhow::{Context, Result, bail}; -use handlebars::Handlebars; -use log::{debug, info, trace, warn}; -use mdbook_core::config::{BookConfig, Code, Config, HtmlConfig, Playground, RustEdition}; -use mdbook_core::utils; -use mdbook_core::utils::fs::get_404_output_file; -use mdbook_html::theme::Theme; -use mdbook_markdown::{render_markdown, render_markdown_with_path}; - -use regex::{Captures, Regex}; -use serde_json::json; - /// The HTML renderer for mdBook. #[derive(Default)] pub struct HtmlHandlebars; diff --git a/crates/mdbook-html/src/html_handlebars/mod.rs b/crates/mdbook-html/src/html_handlebars/mod.rs index 6cbdf588..6c3ef04f 100644 --- a/crates/mdbook-html/src/html_handlebars/mod.rs +++ b/crates/mdbook-html/src/html_handlebars/mod.rs @@ -1,9 +1,7 @@ -pub use self::hbs_renderer::HtmlHandlebars; -pub use self::static_files::StaticFiles; - mod hbs_renderer; mod helpers; -mod static_files; - #[cfg(feature = "search")] mod search; +mod static_files; + +pub use self::hbs_renderer::HtmlHandlebars; diff --git a/crates/mdbook-html/src/html_handlebars/search.rs b/crates/mdbook-html/src/html_handlebars/search.rs index 9f63ba74..849af3d1 100644 --- a/crates/mdbook-html/src/html_handlebars/search.rs +++ b/crates/mdbook-html/src/html_handlebars/search.rs @@ -1,21 +1,19 @@ +use super::static_files::StaticFiles; +use crate::theme::searcher; +use anyhow::{Context, Result, bail}; +use elasticlunr::{Index, IndexBuilder}; +use log::{debug, warn}; +use mdbook_core::book::{Book, BookItem, Chapter}; +use mdbook_core::config::{Search, SearchChapterSettings}; +use mdbook_core::utils; +use mdbook_markdown::new_cmark_parser; +use pulldown_cmark::*; +use serde::Serialize; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::LazyLock; -use anyhow::{Context, Result, bail}; -use elasticlunr::{Index, IndexBuilder}; -use log::{debug, warn}; -use mdbook_core::config::{Search, SearchChapterSettings}; -use mdbook_core::utils; -use mdbook_html::theme::searcher; -use mdbook_markdown::new_cmark_parser; -use pulldown_cmark::*; -use serde::Serialize; - -use crate::book::{Book, BookItem, Chapter}; -use crate::renderer::html_handlebars::StaticFiles; - const MAX_WORD_LENGTH_TO_INDEX: usize = 80; /// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens. @@ -394,7 +392,7 @@ fn chapter_settings_priority() { "cli/inner" = { enable = true } "foo" = {} # Just to make sure empty table is allowed. "#; - let cfg: crate::Config = toml::from_str(cfg).unwrap(); + let cfg: mdbook_core::config::Config = toml::from_str(cfg).unwrap(); let html = cfg.html_config().unwrap(); let chapter_configs = sort_search_config(&html.search.unwrap().chapter); for (path, enable) in [ diff --git a/crates/mdbook-html/src/html_handlebars/static_files.rs b/crates/mdbook-html/src/html_handlebars/static_files.rs index 2436d763..a9abc923 100644 --- a/crates/mdbook-html/src/html_handlebars/static_files.rs +++ b/crates/mdbook-html/src/html_handlebars/static_files.rs @@ -1,13 +1,11 @@ //! Support for writing static files. +use super::helpers::resources::ResourceHelper; +use crate::theme::{self, Theme, playground_editor}; use anyhow::{Context, Result}; use log::{debug, warn}; use mdbook_core::config::HtmlConfig; use mdbook_core::utils; -use mdbook_html::theme::{self, Theme, playground_editor}; - -use crate::renderer::html_handlebars::helpers::resources::ResourceHelper; - use std::borrow::Cow; use std::collections::HashMap; use std::fs::{self, File}; @@ -300,9 +298,9 @@ impl StaticFiles { #[cfg(test)] mod tests { use super::*; + use crate::theme::Theme; use mdbook_core::config::HtmlConfig; use mdbook_core::utils::fs::write_file; - use mdbook_html::theme::Theme; use tempfile::TempDir; #[test] diff --git a/crates/mdbook-html/src/lib.rs b/crates/mdbook-html/src/lib.rs index e32ac6bf..62b392a5 100644 --- a/crates/mdbook-html/src/lib.rs +++ b/crates/mdbook-html/src/lib.rs @@ -1,3 +1,6 @@ //! mdBook HTML renderer. +mod html_handlebars; pub mod theme; + +pub use html_handlebars::HtmlHandlebars; diff --git a/src/book/mod.rs b/src/book/mod.rs index 3b8879da..3b323497 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -10,11 +10,14 @@ mod init; pub use self::book::load_book; pub use self::init::BookBuilder; +use crate::preprocess::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor}; +use crate::renderer::{CmdRenderer, MarkdownRenderer}; 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::config::{Config, RustEdition}; use mdbook_core::utils; +use mdbook_html::HtmlHandlebars; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use mdbook_renderer::{RenderContext, Renderer}; pub use mdbook_summary::{Link, Summary, SummaryItem, parse_summary}; @@ -26,9 +29,6 @@ use tempfile::Builder as TempFileBuilder; use toml::Value; use topological_sort::TopologicalSort; -use crate::preprocess::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor}; -use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer}; - /// The object used to manage and build a book. pub struct MDBook { /// The book's root directory. diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index d639b005..e3503779 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -21,10 +21,8 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use toml::Value; -pub use self::html_handlebars::HtmlHandlebars; pub use self::markdown_renderer::MarkdownRenderer; -mod html_handlebars; mod markdown_renderer; /// A generic renderer which will shell out to an arbitrary executable. diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 98972c4b..c0e563e0 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -12,7 +12,7 @@ fn basic_build() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }); diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs index bc02e513..41b30705 100644 --- a/tests/testsuite/includes.rs +++ b/tests/testsuite/includes.rs @@ -48,7 +48,7 @@ fn recursive_include() { [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [ERROR] (mdbook::preprocess::links): Stack depth exceeded in recursive.md. Check for cyclic includes [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }) diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs index d9851559..47a15379 100644 --- a/tests/testsuite/markdown.rs +++ b/tests/testsuite/markdown.rs @@ -26,7 +26,7 @@ fn footnotes() { [TIMESTAMP] [WARN] (mdbook_markdown): footnote `unused` in `` is defined but not referenced [TIMESTAMP] [WARN] (mdbook_markdown): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition [TIMESTAMP] [WARN] (mdbook_markdown): footnote `unused` in `footnotes.md` is defined but not referenced -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }) diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index b04f4b43..efa3ea0f 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -49,7 +49,7 @@ fn nop_preprocessor() { 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::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }); diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs index 8ebb0c64..da5c19a3 100644 --- a/tests/testsuite/theme.rs +++ b/tests/testsuite/theme.rs @@ -26,7 +26,7 @@ fn empty_theme() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }); @@ -147,10 +147,10 @@ fn copy_fonts_false_no_theme() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [WARN] (mdbook::renderer::html_handlebars::static_files): output.html.copy-fonts is deprecated. +[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. -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }) @@ -166,7 +166,7 @@ fn copy_fonts_false_with_empty_fonts_css() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }) @@ -182,7 +182,7 @@ fn copy_fonts_false_with_fonts_css() { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the html backend -[TIMESTAMP] [INFO] (mdbook::renderer::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` +[TIMESTAMP] [INFO] (mdbook_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` "#]]); }) From c8571f592c5b970e8e6f719ffb4c50b6b1596afb Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 20:47:33 -0700 Subject: [PATCH 28/44] Add mdbook-driver This is intended to hold the high-level MDBook type. --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/mdbook-driver/Cargo.toml | 13 +++++++++++++ crates/mdbook-driver/src/lib.rs | 1 + 4 files changed, 19 insertions(+) create mode 100644 crates/mdbook-driver/Cargo.toml create mode 100644 crates/mdbook-driver/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 15b80779..c4273f28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1307,6 +1307,10 @@ dependencies = [ "toml", ] +[[package]] +name = "mdbook-driver" +version = "0.5.0-alpha.1" + [[package]] name = "mdbook-html" version = "0.5.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 88852bdd..b52a59a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ handlebars = "6.3.2" hex = "0.4.3" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } +mdbook-driver = { path = "crates/mdbook-driver" } mdbook-html = { path = "crates/mdbook-html" } mdbook-markdown = { path = "crates/mdbook-markdown" } mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } diff --git a/crates/mdbook-driver/Cargo.toml b/crates/mdbook-driver/Cargo.toml new file mode 100644 index 00000000..be35be3c --- /dev/null +++ b/crates/mdbook-driver/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mdbook-driver" +version = "0.5.0-alpha.1" +description = "High-level library for running mdBook" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs new file mode 100644 index 00000000..ea725ddb --- /dev/null +++ b/crates/mdbook-driver/src/lib.rs @@ -0,0 +1 @@ +//! High-level library for running mdBook. From 6aac696ee14cfc897c2d2d141adc468293ce7b54 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 20:52:17 -0700 Subject: [PATCH 29/44] Move built-in preprocessors to mdbook-driver This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-driver. --- .../mdbook-driver/src/builtin_preprocessors}/cmd.rs | 0 .../mdbook-driver/src/builtin_preprocessors}/index.rs | 0 .../mdbook-driver/src/builtin_preprocessors}/links.rs | 0 .../mdbook-driver/src/builtin_preprocessors}/mod.rs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {src/preprocess => crates/mdbook-driver/src/builtin_preprocessors}/cmd.rs (100%) rename {src/preprocess => crates/mdbook-driver/src/builtin_preprocessors}/index.rs (100%) rename {src/preprocess => crates/mdbook-driver/src/builtin_preprocessors}/links.rs (100%) rename {src/preprocess => crates/mdbook-driver/src/builtin_preprocessors}/mod.rs (100%) diff --git a/src/preprocess/cmd.rs b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs similarity index 100% rename from src/preprocess/cmd.rs rename to crates/mdbook-driver/src/builtin_preprocessors/cmd.rs diff --git a/src/preprocess/index.rs b/crates/mdbook-driver/src/builtin_preprocessors/index.rs similarity index 100% rename from src/preprocess/index.rs rename to crates/mdbook-driver/src/builtin_preprocessors/index.rs diff --git a/src/preprocess/links.rs b/crates/mdbook-driver/src/builtin_preprocessors/links.rs similarity index 100% rename from src/preprocess/links.rs rename to crates/mdbook-driver/src/builtin_preprocessors/links.rs diff --git a/src/preprocess/mod.rs b/crates/mdbook-driver/src/builtin_preprocessors/mod.rs similarity index 100% rename from src/preprocess/mod.rs rename to crates/mdbook-driver/src/builtin_preprocessors/mod.rs From f5fc54461ab2181790e4f5a9b84ae4cd19d56596 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 21:05:27 -0700 Subject: [PATCH 30/44] Finish moving built-in preprocessors to mdbook-driver --- Cargo.lock | 10 ++++++++++ Cargo.toml | 4 +++- crates/mdbook-driver/Cargo.toml | 7 +++++++ crates/mdbook-driver/src/builtin_preprocessors/cmd.rs | 3 ++- .../mdbook-driver/src/builtin_preprocessors/index.rs | 5 +++-- .../mdbook-driver/src/builtin_preprocessors/links.rs | 5 +++-- crates/mdbook-driver/src/builtin_preprocessors/mod.rs | 2 +- crates/mdbook-driver/src/lib.rs | 2 ++ src/book/mod.rs | 2 +- src/lib.rs | 1 - tests/testsuite/includes.rs | 2 +- tests/testsuite/preprocessor.rs | 2 +- 12 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4273f28..8c5e4328 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1267,6 +1267,7 @@ dependencies = [ "ignore", "log", "mdbook-core", + "mdbook-driver", "mdbook-html", "mdbook-markdown", "mdbook-preprocessor", @@ -1310,6 +1311,15 @@ dependencies = [ [[package]] name = "mdbook-driver" version = "0.5.0-alpha.1" +dependencies = [ + "anyhow", + "log", + "mdbook-core", + "mdbook-preprocessor", + "regex", + "serde_json", + "shlex", +] [[package]] name = "mdbook-html" diff --git a/Cargo.toml b/Cargo.toml index b52a59a8..3ed64c85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" 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 @@ -70,6 +71,7 @@ clap_complete = "4.3.2" env_logger = "0.11.1" log.workspace = true mdbook-core.workspace = true +mdbook-driver.workspace = true mdbook-html.workspace = true mdbook-markdown.workspace = true mdbook-preprocessor.workspace = true @@ -81,7 +83,7 @@ pulldown-cmark.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true -shlex = "1.3.0" +shlex.workspace = true tempfile.workspace = true toml.workspace = true topological-sort = "0.2.2" diff --git a/crates/mdbook-driver/Cargo.toml b/crates/mdbook-driver/Cargo.toml index be35be3c..8417ed15 100644 --- a/crates/mdbook-driver/Cargo.toml +++ b/crates/mdbook-driver/Cargo.toml @@ -8,6 +8,13 @@ repository.workspace = true rust-version.workspace = true [dependencies] +anyhow.workspace = true +log.workspace = true +mdbook-core.workspace = true +mdbook-preprocessor.workspace = true +regex.workspace = true +serde_json.workspace = true +shlex.workspace = true [lints] workspace = true diff --git a/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs index 754ddddc..ebe0275c 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs @@ -1,6 +1,6 @@ -use crate::book::Book; use anyhow::{Context, Result, bail, ensure}; use log::{debug, trace, warn}; +use mdbook_core::book::Book; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use shlex::Shlex; use std::io::{self, Write}; @@ -170,6 +170,7 @@ impl Preprocessor for CmdPreprocessor { } } +#[cfg(false)] // Needs to wait for MDBook transfer #[cfg(test)] mod tests { use super::*; diff --git a/crates/mdbook-driver/src/builtin_preprocessors/index.rs b/crates/mdbook-driver/src/builtin_preprocessors/index.rs index 3b8dfd2a..18ac4407 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/index.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/index.rs @@ -1,6 +1,6 @@ -use crate::book::{Book, BookItem}; use anyhow::Result; use log::warn; +use mdbook_core::book::{Book, BookItem}; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use regex::Regex; use std::{path::Path, sync::LazyLock}; @@ -11,7 +11,8 @@ use std::{path::Path, sync::LazyLock}; pub struct IndexPreprocessor; impl IndexPreprocessor { - pub(crate) const NAME: &'static str = "index"; + /// Name of this preprocessor. + pub const NAME: &'static str = "index"; /// Create a new `IndexPreprocessor`. pub fn new() -> Self { diff --git a/crates/mdbook-driver/src/builtin_preprocessors/links.rs b/crates/mdbook-driver/src/builtin_preprocessors/links.rs index cfefbc8a..127ac3fd 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/links.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/links.rs @@ -1,6 +1,6 @@ -use crate::book::{Book, BookItem}; use anyhow::{Context, Result}; use log::{error, warn}; +use mdbook_core::book::{Book, BookItem}; use mdbook_core::utils::{ take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, @@ -29,7 +29,8 @@ const MAX_LINK_NESTED_DEPTH: usize = 10; pub struct LinkPreprocessor; impl LinkPreprocessor { - pub(crate) const NAME: &'static str = "links"; + /// Name of this preprocessor. + pub const NAME: &'static str = "links"; /// Create a new `LinkPreprocessor`. pub fn new() -> Self { diff --git a/crates/mdbook-driver/src/builtin_preprocessors/mod.rs b/crates/mdbook-driver/src/builtin_preprocessors/mod.rs index d4906651..77b48126 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/mod.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/mod.rs @@ -1,4 +1,4 @@ -//! Book preprocessing. +//! Built-in preprocessors. pub use self::cmd::CmdPreprocessor; pub use self::index::IndexPreprocessor; diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs index ea725ddb..08f7e17b 100644 --- a/crates/mdbook-driver/src/lib.rs +++ b/crates/mdbook-driver/src/lib.rs @@ -1 +1,3 @@ //! High-level library for running mdBook. + +pub mod builtin_preprocessors; diff --git a/src/book/mod.rs b/src/book/mod.rs index 3b323497..c650d4d2 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -10,13 +10,13 @@ mod init; pub use self::book::load_book; pub use self::init::BookBuilder; -use crate::preprocess::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor}; use crate::renderer::{CmdRenderer, MarkdownRenderer}; 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::config::{Config, RustEdition}; use mdbook_core::utils; +use mdbook_driver::builtin_preprocessors::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor}; use mdbook_html::HtmlHandlebars; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use mdbook_renderer::{RenderContext, Renderer}; diff --git a/src/lib.rs b/src/lib.rs index e152fcc0..5b5c7a64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,6 @@ //! [`Config`]: mdbook_core::config::Config pub mod book; -pub mod preprocess; pub mod renderer; pub use crate::book::BookItem; diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs index 41b30705..c242d35b 100644 --- a/tests/testsuite/includes.rs +++ b/tests/testsuite/includes.rs @@ -46,7 +46,7 @@ fn recursive_include() { .run("build", |cmd| { cmd.expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started -[TIMESTAMP] [ERROR] (mdbook::preprocess::links): Stack depth exceeded in recursive.md. Check for cyclic includes +[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_html::html_handlebars::hbs_renderer): HTML book written to `[ROOT]/book` diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index efa3ea0f..1676ce1b 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use anyhow::Result; use mdbook::book::Book; -use mdbook::preprocess::CmdPreprocessor; +use mdbook_driver::builtin_preprocessors::CmdPreprocessor; use mdbook_preprocessor::{Preprocessor, PreprocessorContext}; use std::sync::{Arc, Mutex}; From 9b27b1498592e7569e199f9eb1e581b243b9ca35 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 21:06:46 -0700 Subject: [PATCH 31/44] Move built-in renderers to mdbook-driver This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-driver. --- .../mdbook-driver/src/builtin_renderers}/markdown_renderer.rs | 0 .../mdbook-driver/src/builtin_renderers}/mod.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {src/renderer => crates/mdbook-driver/src/builtin_renderers}/markdown_renderer.rs (100%) rename {src/renderer => crates/mdbook-driver/src/builtin_renderers}/mod.rs (100%) diff --git a/src/renderer/markdown_renderer.rs b/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs similarity index 100% rename from src/renderer/markdown_renderer.rs rename to crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs diff --git a/src/renderer/mod.rs b/crates/mdbook-driver/src/builtin_renderers/mod.rs similarity index 100% rename from src/renderer/mod.rs rename to crates/mdbook-driver/src/builtin_renderers/mod.rs From d7587535515e7dedc93e49ca2cf9356ec315c574 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 21:12:43 -0700 Subject: [PATCH 32/44] Finish moving builtin renderers to mdbook-driver --- Cargo.lock | 2 ++ crates/mdbook-driver/Cargo.toml | 2 ++ .../builtin_renderers/markdown_renderer.rs | 4 ++-- .../src/builtin_renderers/mod.rs | 13 ++--------- crates/mdbook-driver/src/lib.rs | 1 + src/book/mod.rs | 2 +- src/lib.rs | 1 - tests/testsuite/renderer.rs | 22 +++++++++---------- 8 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c5e4328..b66a7116 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,9 +1316,11 @@ dependencies = [ "log", "mdbook-core", "mdbook-preprocessor", + "mdbook-renderer", "regex", "serde_json", "shlex", + "toml", ] [[package]] diff --git a/crates/mdbook-driver/Cargo.toml b/crates/mdbook-driver/Cargo.toml index 8417ed15..a6b788fa 100644 --- a/crates/mdbook-driver/Cargo.toml +++ b/crates/mdbook-driver/Cargo.toml @@ -12,9 +12,11 @@ anyhow.workspace = true log.workspace = true mdbook-core.workspace = true mdbook-preprocessor.workspace = true +mdbook-renderer.workspace = true regex.workspace = true serde_json.workspace = true shlex.workspace = true +toml.workspace = true [lints] workspace = true diff --git a/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs b/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs index 638b21f0..5255bc80 100644 --- a/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs +++ b/crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs @@ -1,8 +1,8 @@ -use crate::book::BookItem; -use crate::renderer::{RenderContext, Renderer}; use anyhow::{Context, Result}; use log::trace; +use mdbook_core::book::BookItem; use mdbook_core::utils; +use mdbook_renderer::{RenderContext, Renderer}; use std::fs; #[derive(Default)] diff --git a/crates/mdbook-driver/src/builtin_renderers/mod.rs b/crates/mdbook-driver/src/builtin_renderers/mod.rs index e3503779..0218a62d 100644 --- a/crates/mdbook-driver/src/builtin_renderers/mod.rs +++ b/crates/mdbook-driver/src/builtin_renderers/mod.rs @@ -1,15 +1,6 @@ -//! `mdbook`'s low level rendering interface. +//! Built-in renderers. //! -//! # Note -//! -//! You usually don't need to work with this module directly. If you want to -//! implement your own backend, then check out the [For Developers] section of -//! the user guide. -//! -//! The definition for [RenderContext] may be useful though. -//! -//! [For Developers]: https://rust-lang.github.io/mdBook/for_developers/index.html -//! [RenderContext]: struct.RenderContext.html +//! The HTML renderer can be found in the [`mdbook_html`] crate. use anyhow::{Context, Result, bail}; use log::{error, info, trace, warn}; diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs index 08f7e17b..b359b576 100644 --- a/crates/mdbook-driver/src/lib.rs +++ b/crates/mdbook-driver/src/lib.rs @@ -1,3 +1,4 @@ //! High-level library for running mdBook. pub mod builtin_preprocessors; +pub mod builtin_renderers; diff --git a/src/book/mod.rs b/src/book/mod.rs index c650d4d2..df9b8fac 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -10,13 +10,13 @@ mod init; pub use self::book::load_book; pub use self::init::BookBuilder; -use crate::renderer::{CmdRenderer, MarkdownRenderer}; 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::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}; diff --git a/src/lib.rs b/src/lib.rs index 5b5c7a64..0d534e8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,6 @@ //! [`Config`]: mdbook_core::config::Config pub mod book; -pub mod renderer; pub use crate::book::BookItem; pub use crate::book::MDBook; diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index abf45502..5b49d759 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -66,8 +66,8 @@ fn failing_command() { .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the failing backend -[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "failing" renderer -[TIMESTAMP] [ERROR] (mdbook::renderer): Renderer exited with non-zero return code. +[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 [TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: The "failing" renderer failed @@ -84,8 +84,8 @@ fn missing_renderer() { .expect_stderr(str![[r#" [TIMESTAMP] [INFO] (mdbook::book): Book building has started [TIMESTAMP] [INFO] (mdbook::book): Running the missing backend -[TIMESTAMP] [INFO] (mdbook::renderer): Invoking the "missing" renderer -[TIMESTAMP] [ERROR] (mdbook::renderer): 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] [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 [TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: Unable to start the backend [TIMESTAMP] [ERROR] (mdbook_core::utils): [TAB]Caused By: [NOT_FOUND] @@ -101,8 +101,8 @@ fn missing_optional_not_fatal() { 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::renderer): Invoking the "missing" renderer -[TIMESTAMP] [WARN] (mdbook::renderer): The command `trduyvbhijnorgevfuhn` for backend `missing` was not found, but was marked as optional. +[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. "#]]); }); @@ -133,7 +133,7 @@ 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::renderer): Invoking the "arguments" renderer +[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "arguments" renderer "#]]); }); @@ -158,7 +158,7 @@ fn backends_receive_render_context_via_stdin() { 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::renderer): Invoking the "cat-to-file" renderer +[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "cat-to-file" renderer "#]]); }) @@ -236,7 +236,7 @@ fn legacy_relative_command_path() { 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::renderer): Invoking the "myrenderer" renderer +[TIMESTAMP] [INFO] (mdbook_driver::builtin_renderers): Invoking the "myrenderer" renderer "#]]); }) @@ -255,8 +255,8 @@ fn legacy_relative_command_path() { 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::renderer): Invoking the "myrenderer" renderer -[TIMESTAMP] [WARN] (mdbook::renderer): 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. +[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. "#]]); }) From 5a31947eb7e708afbc24fb959d834ba0435a469d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 21:16:07 -0700 Subject: [PATCH 33/44] Move MDBook to mdbook-driver This is a pure git rename in order to make sure that git can follow history. The next commit will integrate these into mdbook-driver. --- {src/book => crates/mdbook-driver/src}/init.rs | 0 src/book/book.rs => crates/mdbook-driver/src/load.rs | 0 src/book/mod.rs => crates/mdbook-driver/src/mdbook.rs | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {src/book => crates/mdbook-driver/src}/init.rs (100%) rename src/book/book.rs => crates/mdbook-driver/src/load.rs (100%) rename src/book/mod.rs => crates/mdbook-driver/src/mdbook.rs (100%) diff --git a/src/book/init.rs b/crates/mdbook-driver/src/init.rs similarity index 100% rename from src/book/init.rs rename to crates/mdbook-driver/src/init.rs diff --git a/src/book/book.rs b/crates/mdbook-driver/src/load.rs similarity index 100% rename from src/book/book.rs rename to crates/mdbook-driver/src/load.rs diff --git a/src/book/mod.rs b/crates/mdbook-driver/src/mdbook.rs similarity index 100% rename from src/book/mod.rs rename to crates/mdbook-driver/src/mdbook.rs From 40745600a32430a5ce49f095d45ad41cff5c9d7a Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 21 Jul 2025 21:42:58 -0700 Subject: [PATCH 34/44] Finish move of MDBook to mdbook-driver --- Cargo.lock | 5 +- Cargo.toml | 2 +- crates/mdbook-driver/Cargo.toml | 4 + .../src/builtin_preprocessors/cmd.rs | 3 +- crates/mdbook-driver/src/init.rs | 2 + crates/mdbook-driver/src/lib.rs | 5 + crates/mdbook-driver/src/mdbook.rs | 312 +----------------- crates/mdbook-driver/src/mdbook/tests.rs | 275 +++++++++++++++ examples/nop-preprocessor.rs | 4 +- examples/remove-emphasis/test.rs | 2 +- src/cmd/build.rs | 2 +- src/cmd/clean.rs | 2 +- src/cmd/init.rs | 2 +- src/cmd/serve.rs | 2 +- src/cmd/test.rs | 2 +- src/cmd/watch.rs | 2 +- src/cmd/watch/native.rs | 2 +- src/cmd/watch/poller.rs | 2 +- src/lib.rs | 11 +- tests/testsuite/book_test.rs | 7 +- tests/testsuite/build.rs | 8 +- tests/testsuite/includes.rs | 4 +- tests/testsuite/init.rs | 9 +- tests/testsuite/markdown.rs | 4 +- tests/testsuite/preprocessor.rs | 8 +- tests/testsuite/redirects.rs | 8 +- tests/testsuite/renderer.rs | 28 +- tests/testsuite/search.rs | 7 +- tests/testsuite/test.rs | 18 +- tests/testsuite/theme.rs | 20 +- 30 files changed, 380 insertions(+), 382 deletions(-) create mode 100644 crates/mdbook-driver/src/mdbook/tests.rs diff --git a/Cargo.lock b/Cargo.lock index b66a7116..73168a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/Cargo.toml b/Cargo.toml index 3ed64c85..8f916ae4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/crates/mdbook-driver/Cargo.toml b/crates/mdbook-driver/Cargo.toml index a6b788fa..1e2e1a6e 100644 --- a/crates/mdbook-driver/Cargo.toml +++ b/crates/mdbook-driver/Cargo.toml @@ -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 diff --git a/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs index ebe0275c..e97c19e2 100644 --- a/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs +++ b/crates/mdbook-driver/src/builtin_preprocessors/cmd.rs @@ -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() } diff --git a/crates/mdbook-driver/src/init.rs b/crates/mdbook-driver/src/init.rs index a179c63b..ec62ce27 100644 --- a/crates/mdbook-driver/src/init.rs +++ b/crates/mdbook-driver/src/init.rs @@ -1,3 +1,5 @@ +//! Support for initializing a new book. + use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs index b359b576..12586811 100644 --- a/crates/mdbook-driver/src/lib.rs +++ b/crates/mdbook-driver/src/lib.rs @@ -2,3 +2,8 @@ pub mod builtin_preprocessors; pub mod builtin_renderers; +pub mod init; +mod load; +mod mdbook; + +pub use mdbook::MDBook; diff --git a/crates/mdbook-driver/src/mdbook.rs b/crates/mdbook-driver/src/mdbook.rs index df9b8fac..4619f1d8 100644 --- a/crates/mdbook-driver/src/mdbook.rs +++ b/crates/mdbook-driver/src/mdbook.rs @@ -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 { - 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); - } -} diff --git a/crates/mdbook-driver/src/mdbook/tests.rs b/crates/mdbook-driver/src/mdbook/tests.rs new file mode 100644 index 00000000..8040217d --- /dev/null +++ b/crates/mdbook-driver/src/mdbook/tests.rs @@ -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 { + 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); +} diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index b2e0fb40..944f757b 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -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 ); } diff --git a/examples/remove-emphasis/test.rs b/examples/remove-emphasis/test.rs index e29d7d0f..4349cdb2 100644 --- a/examples/remove-emphasis/test.rs +++ b/examples/remove-emphasis/test.rs @@ -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.")); diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 05b0bfd2..a04adc5f 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -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 diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index 5a06bc6f..a07bb2ae 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -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}; diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 386ea4aa..9b0c35bc 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -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; diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 1d3ab6b9..d1f5d0e4 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -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; diff --git a/src/cmd/test.rs b/src/cmd/test.rs index fecbc8b0..36988c47 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -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 diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index 8050079e..4f637505 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -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; diff --git a/src/cmd/watch/native.rs b/src/cmd/watch/native.rs index fad8d7ce..0eb04e0a 100644 --- a/src/cmd/watch/native.rs +++ b/src/cmd/watch/native.rs @@ -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; diff --git a/src/cmd/watch/poller.rs b/src/cmd/watch/poller.rs index 5e1d1497..65a51188 100644 --- a/src/cmd/watch/poller.rs +++ b/src/cmd/watch/poller.rs @@ -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; diff --git a/src/lib.rs b/src/lib.rs index 0d534e8d..987b5a9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/tests/testsuite/book_test.rs b/tests/testsuite/book_test.rs index 57b5f3f3..e40ca63c 100644 --- a/tests/testsuite/book_test.rs +++ b/tests/testsuite/book_test.rs @@ -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)(?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(); diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index c0e563e0..6bbf061d 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -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 diff --git a/tests/testsuite/includes.rs b/tests/testsuite/includes.rs index c242d35b..300df28e 100644 --- a/tests/testsuite/includes.rs +++ b/tests/testsuite/includes.rs @@ -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` "#]]); diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs index e5989eb6..946d9f23 100644 --- a/tests/testsuite/init.rs +++ b/tests/testsuite/init.rs @@ -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"]); diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs index 47a15379..e8366a48 100644 --- a/tests/testsuite/markdown.rs +++ b/tests/testsuite/markdown.rs @@ -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 defined multiple times - not updating to new definition [TIMESTAMP] [WARN] (mdbook_markdown): footnote `unused` in `` is defined but not referenced [TIMESTAMP] [WARN] (mdbook_markdown): footnote `multiple-definitions` in footnotes.md defined multiple times - not updating to new definition diff --git a/tests/testsuite/preprocessor.rs b/tests/testsuite/preprocessor.rs index 1676ce1b..428c9d08 100644 --- a/tests/testsuite/preprocessor.rs +++ b/tests/testsuite/preprocessor.rs @@ -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 diff --git a/tests/testsuite/redirects.rs b/tests/testsuite/redirects.rs index 93fd9113..c73703ca 100644 --- a/tests/testsuite/redirects.rs +++ b/tests/testsuite/redirects.rs @@ -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. diff --git a/tests/testsuite/renderer.rs b/tests/testsuite/renderer.rs index 5b49d759..9d74f9ba 100644 --- a/tests/testsuite/renderer.rs +++ b/tests/testsuite/renderer.rs @@ -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. diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs index a8f05120..c3fa0085 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -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 diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index 5fd2cc7e..c116bc67 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -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" "#]]); }); diff --git a/tests/testsuite/theme.rs b/tests/testsuite/theme.rs index da5c19a3..6ef67c23 100644 --- a/tests/testsuite/theme.rs +++ b/tests/testsuite/theme.rs @@ -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` "#]]); From fdebbfdce2e64eb38d858871f6f976281366eb7e Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 22 Jul 2025 11:14:43 -0700 Subject: [PATCH 35/44] Remove pulldown-cmark from mdbook-core This dependency is no longer directly used. --- Cargo.lock | 1 - crates/mdbook-core/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73168a57..65e740fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1299,7 +1299,6 @@ version = "0.5.0-alpha.1" dependencies = [ "anyhow", "log", - "pulldown-cmark 0.10.3", "regex", "serde", "serde_json", diff --git a/crates/mdbook-core/Cargo.toml b/crates/mdbook-core/Cargo.toml index 80e9fcb0..6841704b 100644 --- a/crates/mdbook-core/Cargo.toml +++ b/crates/mdbook-core/Cargo.toml @@ -10,7 +10,6 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true log.workspace = true -pulldown-cmark.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true From ae6c4522bbe34061cc6ae79d03129dec62a4ff4a Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 22 Jul 2025 11:16:21 -0700 Subject: [PATCH 36/44] Publicly re-export mdbook-core modules from mdbook-driver The intent here is to make mdbook-core a private dependency that the user shouldn't need. --- crates/mdbook-driver/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs index 12586811..a8a3d3d5 100644 --- a/crates/mdbook-driver/src/lib.rs +++ b/crates/mdbook-driver/src/lib.rs @@ -7,3 +7,4 @@ mod load; mod mdbook; pub use mdbook::MDBook; +pub use mdbook_core::{book, config, errors}; From 9229e80499a309c815c7bc9dc87b7a9c12826ae1 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 22 Jul 2025 11:17:07 -0700 Subject: [PATCH 37/44] Clean up some remaining uses of mdbook_core These should be using the appropriate high-level crate. --- crates/mdbook-driver/src/mdbook.rs | 2 +- examples/nop-preprocessor.rs | 4 ++-- src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/mdbook-driver/src/mdbook.rs b/crates/mdbook-driver/src/mdbook.rs index 4619f1d8..ab8fa087 100644 --- a/crates/mdbook-driver/src/mdbook.rs +++ b/crates/mdbook-driver/src/mdbook.rs @@ -140,7 +140,7 @@ impl MDBook { /// /// ```no_run /// # use mdbook_driver::MDBook; - /// # use mdbook_core::book::BookItem; + /// # use mdbook_driver::book::BookItem; /// # let book = MDBook::load("mybook").unwrap(); /// for item in book.iter() { /// match *item { diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index 944f757b..ac30b0f6 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -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_core::MDBOOK_VERSION)?; + let version_req = VersionReq::parse(mdbook_preprocessor::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_core::MDBOOK_VERSION, + mdbook_preprocessor::MDBOOK_VERSION, ctx.mdbook_version ); } diff --git a/src/lib.rs b/src/lib.rs index 987b5a9b..122eb565 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ //! //! ```rust,no_run //! use mdbook_driver::MDBook; -//! use mdbook_core::config::Config; +//! use mdbook_driver::config::Config; //! //! let root_dir = "/path/to/book/root"; //! @@ -78,4 +78,4 @@ //! [user guide]: https://rust-lang.github.io/mdBook/ //! [`RenderContext`]: mdbook_renderer::RenderContext //! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html -//! [`Config`]: mdbook_core::config::Config +//! [`Config`]: mdbook_driver::config::Config From b8a7b6e84686ade4587377d7137a82da79d45027 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 22 Jul 2025 11:22:31 -0700 Subject: [PATCH 38/44] Simplify `MDBook::iter` doc The original was a little awkward, and I'm not sure what the tuple syntax was intending to convey. --- crates/mdbook-driver/src/mdbook.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/mdbook-driver/src/mdbook.rs b/crates/mdbook-driver/src/mdbook.rs index ab8fa087..701a0161 100644 --- a/crates/mdbook-driver/src/mdbook.rs +++ b/crates/mdbook-driver/src/mdbook.rs @@ -134,9 +134,7 @@ impl MDBook { }) } - /// Returns a flat depth-first iterator over the elements of the book, - /// it returns a [`BookItem`] enum: - /// `(section: String, bookitem: &BookItem)` + /// Returns a flat depth-first iterator over the [`BookItem`]s of the book. /// /// ```no_run /// # use mdbook_driver::MDBook; From 12fc0ff5c372256a8b6c9d5fb3e3c61e5d705584 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 22 Jul 2025 11:27:11 -0700 Subject: [PATCH 39/44] Clean up dependencies of `mdbook` These are no longer used, or are dev-dependencies only. --- Cargo.lock | 4 ---- Cargo.toml | 10 +++------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65e740fd..ed9155de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1273,18 +1273,14 @@ dependencies = [ "mdbook-preprocessor", "mdbook-renderer", "mdbook-summary", - "memchr", "notify", "notify-debouncer-mini", "opener", "pathdiff", - "pulldown-cmark 0.10.3", "regex", "select", "semver", - "serde", "serde_json", - "shlex", "snapbox", "tempfile", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 8f916ae4..c4ff164b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,14 +78,7 @@ mdbook-markdown.workspace = true mdbook-preprocessor.workspace = true mdbook-renderer.workspace = true mdbook-summary.workspace = true -memchr.workspace = true opener = "0.8.1" -pulldown-cmark.workspace = true -regex.workspace = true -serde.workspace = true -serde_json.workspace = true -shlex.workspace = true -tempfile.workspace = true toml.workspace = true # Watch feature @@ -102,9 +95,12 @@ axum = { version = "0.8.0", features = ["ws"], optional = true } tower-http = { version = "0.6.0", features = ["fs", "trace"], optional = true } [dev-dependencies] +regex.workspace = true select = "0.6.0" semver = "1.0.17" +serde_json.workspace = true snapbox = { version = "0.6.21", features = ["diff", "dir", "term-svg", "regex", "json"] } +tempfile.workspace = true walkdir = "2.3.3" [features] From 6e6518a7ae1b791808d7599781a6f63c9ccb8805 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 23 Jul 2025 14:28:15 -0700 Subject: [PATCH 40/44] Move the remaining dependencies to the workspace table This is intended to have all dependencies only defined in the workspace table, and crates can then refer to it. --- Cargo.toml | 53 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4ff164b..5a615801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,16 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow [workspace.dependencies] ammonia = "4.1.1" anyhow = "1.0.98" +axum = "0.8.4" +chrono = { version = "0.4.41", default-features = false, features = ["clock"] } +clap = { version = "4.5.41", features = ["cargo", "wrap_help"] } +clap_complete = "4.5.55" elasticlunr-rs = "3.0.2" +env_logger = "0.11.8" +futures-util = "0.3.31" handlebars = "6.3.2" hex = "0.4.3" +ignore = "0.4.23" log = "0.4.27" mdbook-core = { path = "crates/mdbook-core" } mdbook-driver = { path = "crates/mdbook-driver" } @@ -35,16 +42,26 @@ mdbook-preprocessor = { path = "crates/mdbook-preprocessor" } mdbook-renderer = { path = "crates/mdbook-renderer" } mdbook-summary = { path = "crates/mdbook-summary" } memchr = "2.7.5" +notify = "8.1.0" +notify-debouncer-mini = "0.6.0" +opener = "0.8.2" +pathdiff = "0.2.3" pretty_assertions = "1.4.1" pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] } # Do not update, part of the public api. regex = "1.11.1" +select = "0.6.1" +semver = "1.0.26" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" sha2 = "0.10.9" shlex = "1.3.0" +snapbox = "0.6.21" tempfile = "3.20.0" +tokio = "1.46.1" toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/2037 topological-sort = "0.2.2" +tower-http = "0.6.6" +walkdir = "2.5.0" [package] name = "mdbook" @@ -66,10 +83,10 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true -chrono = { version = "0.4.24", default-features = false, features = ["clock"] } -clap = { version = "4.3.12", features = ["cargo", "wrap_help"] } -clap_complete = "4.3.2" -env_logger = "0.11.1" +chrono.workspace = true +clap.workspace = true +clap_complete.workspace = true +env_logger.workspace = true log.workspace = true mdbook-core.workspace = true mdbook-driver.workspace = true @@ -78,30 +95,30 @@ mdbook-markdown.workspace = true mdbook-preprocessor.workspace = true mdbook-renderer.workspace = true mdbook-summary.workspace = true -opener = "0.8.1" +opener.workspace = true toml.workspace = true # Watch feature -notify = { version = "8.0.0", optional = true } -notify-debouncer-mini = { version = "0.6.0", optional = true } -ignore = { version = "0.4.20", optional = true } -pathdiff = { version = "0.2.1", optional = true } -walkdir = { version = "2.3.3", optional = true } +ignore = { workspace = true, optional = true } +notify = { workspace = true, optional = true } +notify-debouncer-mini = { workspace = true, optional = true } +pathdiff = { workspace = true, optional = true } +walkdir = { workspace = true, optional = true } # Serve feature -futures-util = { version = "0.3.28", optional = true } -tokio = { version = "1.43.1", features = ["macros", "rt-multi-thread"], optional = true } -axum = { version = "0.8.0", features = ["ws"], optional = true } -tower-http = { version = "0.6.0", features = ["fs", "trace"], optional = true } +axum = { workspace = true, features = ["ws"], optional = true } +futures-util = { workspace = true, optional = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"], optional = true } +tower-http = { workspace = true, features = ["fs", "trace"], optional = true } [dev-dependencies] regex.workspace = true -select = "0.6.0" -semver = "1.0.17" +select.workspace = true +semver.workspace = true serde_json.workspace = true -snapbox = { version = "0.6.21", features = ["diff", "dir", "term-svg", "regex", "json"] } +snapbox = { workspace = true, features = ["diff", "dir", "term-svg", "regex", "json"] } tempfile.workspace = true -walkdir = "2.3.3" +walkdir.workspace = true [features] default = ["watch", "serve", "search"] From e0a4fb1ea96db2501dfc04142dec9223bbe1b5ff Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 23 Jul 2025 15:17:11 -0700 Subject: [PATCH 41/44] Add READMEs for all new crates --- crates/mdbook-core/README.md | 13 +++++++++++++ crates/mdbook-driver/README.md | 13 +++++++++++++ crates/mdbook-html/README.md | 13 +++++++++++++ crates/mdbook-markdown/README.md | 13 +++++++++++++ crates/mdbook-preprocessor/README.md | 13 +++++++++++++ crates/mdbook-renderer/README.md | 13 +++++++++++++ crates/mdbook-summary/README.md | 13 +++++++++++++ 7 files changed, 91 insertions(+) create mode 100644 crates/mdbook-core/README.md create mode 100644 crates/mdbook-driver/README.md create mode 100644 crates/mdbook-html/README.md create mode 100644 crates/mdbook-markdown/README.md create mode 100644 crates/mdbook-preprocessor/README.md create mode 100644 crates/mdbook-renderer/README.md create mode 100644 crates/mdbook-summary/README.md diff --git a/crates/mdbook-core/README.md b/crates/mdbook-core/README.md new file mode 100644 index 00000000..9d541479 --- /dev/null +++ b/crates/mdbook-core/README.md @@ -0,0 +1,13 @@ +# mdbook-core + +[![Documentation](https://img.shields.io/docsrs/mdbook-core)](https://docs.rs/mdbook-core) +[![crates.io](https://img.shields.io/crates/v/mdbook-core.svg)](https://crates.io/crates/mdbook-core) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the base support library for [mdBook](https://rust-lang.github.io/mdBook/). It is intended for internal use only. Other mdBook crates depend on this for any types that are shared across the crates. + +> This crate is maintained by the mdBook team, primarily for use by mdBook and not intended for external use (except as a transitive dependency). This crate may make major changes to its APIs or be deprecated without warning. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) diff --git a/crates/mdbook-driver/README.md b/crates/mdbook-driver/README.md new file mode 100644 index 00000000..21cf8d63 --- /dev/null +++ b/crates/mdbook-driver/README.md @@ -0,0 +1,13 @@ +# mdbook-driver + +[![Documentation](https://img.shields.io/docsrs/mdbook-driver)](https://docs.rs/mdbook-driver) +[![crates.io](https://img.shields.io/crates/v/mdbook-driver.svg)](https://crates.io/crates/mdbook-driver) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the high-level Rust library for running [mdBook](https://rust-lang.github.io/mdBook/). New books can be created using [`BookBuilder`](https://docs.rs/mdbook-driver/latest/mdbook_driver/init/struct.BookBuilder.html). The primary type [`MDBook`](https://docs.rs/mdbook-driver/latest/mdbook_driver/struct.MDBook.html) can be used to manage and render books. + +> This crate is maintained by the mdBook team for use by the wider ecosystem. This crate follows [semver compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for its APIs. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) diff --git a/crates/mdbook-html/README.md b/crates/mdbook-html/README.md new file mode 100644 index 00000000..ad11a4e4 --- /dev/null +++ b/crates/mdbook-html/README.md @@ -0,0 +1,13 @@ +# mdbook-html + +[![Documentation](https://img.shields.io/docsrs/mdbook-html)](https://docs.rs/mdbook-html) +[![crates.io](https://img.shields.io/crates/v/mdbook-html.svg)](https://crates.io/crates/mdbook-html) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the HTML renderer for [mdBook](https://rust-lang.github.io/mdBook/). This is intended for internal use only. It is automatically included by [`mdbook-driver`](https://crates.io/crates/mdbook-driver) to render books to HTML. + +> This crate is maintained by the mdBook team, primarily for use by mdBook and not intended for external use (except as a transitive dependency). This crate may make major changes to its APIs or be deprecated without warning. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) diff --git a/crates/mdbook-markdown/README.md b/crates/mdbook-markdown/README.md new file mode 100644 index 00000000..53544f23 --- /dev/null +++ b/crates/mdbook-markdown/README.md @@ -0,0 +1,13 @@ +# mdbook-markdown + +[![Documentation](https://img.shields.io/docsrs/mdbook-markdown)](https://docs.rs/mdbook-markdown) +[![crates.io](https://img.shields.io/crates/v/mdbook-markdown.svg)](https://crates.io/crates/mdbook-markdown) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the Markdown support library for [mdBook](https://rust-lang.github.io/mdBook/). Rust crates (such as preprocessors) can use this library to process Markdown in the same way as mdBook. + +> This crate is maintained by the mdBook team for use by the wider ecosystem. This crate follows [semver compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for its APIs. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) diff --git a/crates/mdbook-preprocessor/README.md b/crates/mdbook-preprocessor/README.md new file mode 100644 index 00000000..1063d3fb --- /dev/null +++ b/crates/mdbook-preprocessor/README.md @@ -0,0 +1,13 @@ +# mdbook-preprocessor + +[![Documentation](https://img.shields.io/docsrs/mdbook-preprocessor)](https://docs.rs/mdbook-preprocessor) +[![crates.io](https://img.shields.io/crates/v/mdbook-preprocessor.svg)](https://crates.io/crates/mdbook-preprocessor) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the Rust library to implement a [preprocessor](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html) for [mdBook](https://rust-lang.github.io/mdBook/). + +> This crate is maintained by the mdBook team for use by the wider ecosystem. This crate follows [semver compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for its APIs. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) diff --git a/crates/mdbook-renderer/README.md b/crates/mdbook-renderer/README.md new file mode 100644 index 00000000..11e9c821 --- /dev/null +++ b/crates/mdbook-renderer/README.md @@ -0,0 +1,13 @@ +# mdbook-renderer + +[![Documentation](https://img.shields.io/docsrs/mdbook-renderer)](https://docs.rs/mdbook-renderer) +[![crates.io](https://img.shields.io/crates/v/mdbook-renderer.svg)](https://crates.io/crates/mdbook-renderer) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the Rust library to implement a [renderer](https://rust-lang.github.io/mdBook/for_developers/backends.html) for [mdBook](https://rust-lang.github.io/mdBook/). + +> This crate is maintained by the mdBook team for use by the wider ecosystem. This crate follows [semver compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for its APIs. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) diff --git a/crates/mdbook-summary/README.md b/crates/mdbook-summary/README.md new file mode 100644 index 00000000..543335f3 --- /dev/null +++ b/crates/mdbook-summary/README.md @@ -0,0 +1,13 @@ +# mdbook-summary + +[![Documentation](https://img.shields.io/docsrs/mdbook-summary)](https://docs.rs/mdbook-summary) +[![crates.io](https://img.shields.io/crates/v/mdbook-summary.svg)](https://crates.io/crates/mdbook-summary) +[![Changelog](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md) + +This is the Rust library used to parse the [`SUMMARY.md`](https://rust-lang.github.io/mdBook/format/summary.html) file structure for [mdBook](https://rust-lang.github.io/mdBook/). + +> This crate is maintained by the mdBook team for use by the wider ecosystem. This crate follows [semver compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) for its APIs. + +## License + +[Mozilla Public License, version 2.0](https://github.com/rust-lang/mdBook/blob/master/LICENSE) From dcfb527342b4ba113bc01ce19eae3cea28e1824b Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 23 Jul 2025 16:31:21 -0700 Subject: [PATCH 42/44] Update crate docs This updates the crate-level docs to add a little more detail for each crate. This also drops the `mdbook` library crate, as it is no longer needed. --- Cargo.lock | 1 + crates/mdbook-driver/Cargo.toml | 1 + crates/mdbook-driver/src/lib.rs | 58 +++++++++++++++++++ crates/mdbook-markdown/src/lib.rs | 10 ++++ crates/mdbook-preprocessor/src/lib.rs | 10 ++++ crates/mdbook-renderer/src/lib.rs | 10 ++++ crates/mdbook-summary/src/lib.rs | 4 ++ src/lib.rs | 81 --------------------------- 8 files changed, 94 insertions(+), 81 deletions(-) delete mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ed9155de..10ec141e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1310,6 +1310,7 @@ dependencies = [ "log", "mdbook-core", "mdbook-html", + "mdbook-markdown", "mdbook-preprocessor", "mdbook-renderer", "mdbook-summary", diff --git a/crates/mdbook-driver/Cargo.toml b/crates/mdbook-driver/Cargo.toml index 1e2e1a6e..5e9b348e 100644 --- a/crates/mdbook-driver/Cargo.toml +++ b/crates/mdbook-driver/Cargo.toml @@ -12,6 +12,7 @@ anyhow.workspace = true log.workspace = true mdbook-core.workspace = true mdbook-html.workspace = true +mdbook-markdown.workspace = true mdbook-preprocessor.workspace = true mdbook-renderer.workspace = true mdbook-summary.workspace = true diff --git a/crates/mdbook-driver/src/lib.rs b/crates/mdbook-driver/src/lib.rs index a8a3d3d5..470fa039 100644 --- a/crates/mdbook-driver/src/lib.rs +++ b/crates/mdbook-driver/src/lib.rs @@ -1,4 +1,62 @@ //! High-level library for running mdBook. +//! +//! This is the high-level library for running +//! [mdBook](https://rust-lang.github.io/mdBook/). There are several +//! reasons for using the programmatic API (over the CLI): +//! +//! - Integrate mdBook in a current project. +//! - Extend the capabilities of mdBook. +//! - Do some processing or test before building your book. +//! - Accessing the public API to help create a new Renderer. +//! +//! ## Additional crates +//! +//! In addition to `mdbook-driver`, there are several other crates available +//! for using and extending mdBook: +//! +//! - [`mdbook_preprocessor`]: Provides support for implementing preprocessors. +//! - [`mdbook_renderer`]: Provides support for implementing renderers. +//! - [`mdbook_markdown`]: The Markdown renderer. +//! - [`mdbook_summary`]: The `SUMMARY.md` parser. +//! - [`mdbook_html`]: The HTML renderer. +//! - [`mdbook_core`]: An internal library that is used by the other crates +//! for shared types. Types from this crate are rexported from the other +//! crates as appropriate. +//! +//! ## Examples +//! +//! If creating a new book from scratch, you'll want to get a [`init::BookBuilder`] via +//! the [`MDBook::init()`] method. +//! +//! ```rust,no_run +//! use mdbook_driver::MDBook; +//! use mdbook_driver::config::Config; +//! +//! let root_dir = "/path/to/book/root"; +//! +//! // create a default config and change a couple things +//! let mut cfg = Config::default(); +//! cfg.book.title = Some("My Book".to_string()); +//! cfg.book.authors.push("Michael-F-Bryan".to_string()); +//! +//! MDBook::init(root_dir) +//! .create_gitignore(true) +//! .with_config(cfg) +//! .build() +//! .expect("Book generation failed"); +//! ``` +//! +//! You can also load an existing book and build it. +//! +//! ```rust,no_run +//! use mdbook_driver::MDBook; +//! +//! let root_dir = "/path/to/book/root"; +//! +//! let mut md = MDBook::load(root_dir) +//! .expect("Unable to load the book"); +//! md.build().expect("Building failed"); +//! ``` pub mod builtin_preprocessors; pub mod builtin_renderers; diff --git a/crates/mdbook-markdown/src/lib.rs b/crates/mdbook-markdown/src/lib.rs index 5b506fa7..a014ca6b 100644 --- a/crates/mdbook-markdown/src/lib.rs +++ b/crates/mdbook-markdown/src/lib.rs @@ -1,4 +1,13 @@ //! Markdown processing used in mdBook. +//! +//! This crate provides functions for processing Markdown in the same way as +//! [mdBook](https://rust-lang.github.io/mdBook/). The [`pulldown_cmark`] +//! crate is used as the underlying parser. This crate re-exports +//! [`pulldown_cmark`] so that you can access its types. +//! +//! The parser in this library adds several modifications to the +//! [`pulldown_cmark`] event stream. For example, it adjusts some links, +//! modifies the behavior of footnotes, and adds various HTML wrappers. use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag, TagEnd, html}; use regex::Regex; @@ -8,6 +17,7 @@ use std::fmt::Write; use std::path::Path; use std::sync::LazyLock; +#[doc(inline)] pub use pulldown_cmark; #[cfg(test)] diff --git a/crates/mdbook-preprocessor/src/lib.rs b/crates/mdbook-preprocessor/src/lib.rs index b1d77420..898d19a3 100644 --- a/crates/mdbook-preprocessor/src/lib.rs +++ b/crates/mdbook-preprocessor/src/lib.rs @@ -1,4 +1,9 @@ //! Library to assist implementing an mdbook preprocessor. +//! +//! This library is used to implement a +//! [preprocessor](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html) +//! for [mdBook](https://rust-lang.github.io/mdBook/). See the linked chapter +//! for more information on how to implement a preprocessor. use anyhow::Context; use mdbook_core::book::Book; @@ -17,6 +22,11 @@ pub use mdbook_core::errors; /// An operation which is run immediately after loading a book into memory and /// before it gets rendered. +/// +/// Types that implement the `Preprocessor` trait can be used with +/// [`MDBook::with_preprocessor`] to programmatically add preprocessors. +/// +/// [`MDBook::with_preprocessor`]: https://docs.rs/mdbook-driver/latest/mdbook_driver/struct.MDBook.html#method.with_preprocessor pub trait Preprocessor { /// Get the `Preprocessor`'s name. fn name(&self) -> &str; diff --git a/crates/mdbook-renderer/src/lib.rs b/crates/mdbook-renderer/src/lib.rs index 56365420..12b0aab4 100644 --- a/crates/mdbook-renderer/src/lib.rs +++ b/crates/mdbook-renderer/src/lib.rs @@ -1,4 +1,9 @@ //! Library to assist implementing an mdbook renderer. +//! +//! This library is used to implement a +//! [renderer](https://rust-lang.github.io/mdBook/for_developers/backends.html) +//! for [mdBook](https://rust-lang.github.io/mdBook/). See the linked chapter +//! for more information on how to implement a renderer. use anyhow::Context; use mdbook_core::book::Book; @@ -15,6 +20,11 @@ pub use mdbook_core::config; pub use mdbook_core::errors; /// An mdbook backend. +/// +/// Types that implement the `Renderer` trait can be used with +/// [`MDBook::with_renderer`] to programmatically add renderers. +/// +/// [`MDBook::with_renderer`]: https://docs.rs/mdbook-driver/latest/mdbook_driver/struct.MDBook.html#method.with_renderer pub trait Renderer { /// The `Renderer`'s name. fn name(&self) -> &str; diff --git a/crates/mdbook-summary/src/lib.rs b/crates/mdbook-summary/src/lib.rs index ee1f74b3..f0aad211 100644 --- a/crates/mdbook-summary/src/lib.rs +++ b/crates/mdbook-summary/src/lib.rs @@ -1,4 +1,8 @@ //! Summary parser for mdBook. +//! +//! This is used to parse the +//! [`SUMMARY.md`](https://rust-lang.github.io/mdBook/format/summary.html) +//! file structure for [mdBook](https://rust-lang.github.io/mdBook/). use anyhow::{Context, Error, Result, bail}; use log::{debug, trace, warn}; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 122eb565..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! # mdBook -//! -//! **mdBook** is a tool for rendering a collection of markdown documents into -//! a form more suitable for end users like HTML or EPUB. It offers a command -//! line interface, but this crate can be used if more control is required. -//! -//! This is the API doc, the [user guide] is also available if you want -//! information about the command line tool, format, structure etc. It is also -//! rendered with mdBook to showcase the features and default theme. -//! -//! Some reasons why you would want to use the crate (over the cli): -//! -//! - Integrate mdbook in a current project -//! - Extend the capabilities of mdBook -//! - Do some processing or test before building your book -//! - Accessing the public API to help create a new Renderer -//! - ... -//! -//! > **Note:** While we try to ensure `mdbook`'s command-line interface and -//! > behaviour are backwards compatible, the tool's internals are still -//! > evolving and being iterated on. If you wish to prevent accidental -//! > breakages it is recommended to pin any tools building on top of the -//! > `mdbook` crate to a specific release. -//! -//! # Examples -//! -//! If creating a new book from scratch, you'll want to get a `BookBuilder` via -//! the `MDBook::init()` method. -//! -//! ```rust,no_run -//! use mdbook_driver::MDBook; -//! use mdbook_driver::config::Config; -//! -//! let root_dir = "/path/to/book/root"; -//! -//! // create a default config and change a couple things -//! let mut cfg = Config::default(); -//! cfg.book.title = Some("My Book".to_string()); -//! cfg.book.authors.push("Michael-F-Bryan".to_string()); -//! -//! MDBook::init(root_dir) -//! .create_gitignore(true) -//! .with_config(cfg) -//! .build() -//! .expect("Book generation failed"); -//! ``` -//! -//! You can also load an existing book and build it. -//! -//! ```rust,no_run -//! use mdbook_driver::MDBook; -//! -//! let root_dir = "/path/to/book/root"; -//! -//! let mut md = MDBook::load(root_dir) -//! .expect("Unable to load the book"); -//! md.build().expect("Building failed"); -//! ``` -//! -//! ## Implementing a new Backend -//! -//! `mdbook` has a fairly flexible mechanism for creating additional backends -//! for your book. The general idea is you'll add an extra table in the book's -//! `book.toml` which specifies an executable to be invoked by `mdbook`. This -//! executable will then be called during a build, with an in-memory -//! representation ([`RenderContext`]) of the book being passed to the -//! subprocess via `stdin`. -//! -//! The [`RenderContext`] gives the backend access to the contents of -//! `book.toml` and lets it know which directory all generated artefacts should -//! be placed in. For a much more in-depth explanation, consult the [relevant -//! chapter] in the *For Developers* section of the user guide. -//! -//! To make creating a backend easier, the `mdbook` crate can be imported -//! directly, making deserializing the `RenderContext` easy and giving you -//! access to the various methods for working with the [`Config`]. -//! -//! [user guide]: https://rust-lang.github.io/mdBook/ -//! [`RenderContext`]: mdbook_renderer::RenderContext -//! [relevant chapter]: https://rust-lang.github.io/mdBook/for_developers/backends.html -//! [`Config`]: mdbook_driver::config::Config From fad53f720f3ecc703e1c5f6b01ba63f5fe05c798 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 23 Jul 2025 16:32:49 -0700 Subject: [PATCH 43/44] Update guide to accommodate crate split This updates the docs now that the crate has been split into multiple crates. --- guide/src/for_developers/README.md | 21 ++++++-------- guide/src/for_developers/backends.md | 35 ++++++++++------------- guide/src/for_developers/preprocessors.md | 16 +++++------ 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/guide/src/for_developers/README.md b/guide/src/for_developers/README.md index d8b97709..ea9ed116 100644 --- a/guide/src/for_developers/README.md +++ b/guide/src/for_developers/README.md @@ -1,7 +1,7 @@ # For Developers While `mdbook` is mainly used as a command line tool, you can also import the -underlying library directly and use that to manage a book. It also has a fairly +underlying libraries directly and use those to manage a book. It also has a fairly flexible plugin mechanism, allowing you to create your own custom tooling and consumers (often referred to as *backends*) if you need to do some analysis of the book or render it in a different format. @@ -14,7 +14,6 @@ The two main ways a developer can hook into the book's build process is via, - [Preprocessors](preprocessors.md) - [Alternative Backends](backends.md) - ## The Build Process The process of rendering a book project goes through several steps. @@ -28,20 +27,18 @@ The process of rendering a book project goes through several steps. 1. Run all the preprocessors. 2. Call the backend to render the processed result. - ## Using `mdbook` as a Library -The `mdbook` binary is just a wrapper around the `mdbook` crate, exposing its -functionality as a command-line program. As such it is quite easy to create your -own programs which use `mdbook` internally, adding your own functionality (e.g. -a custom preprocessor) or tweaking the build process. +The `mdbook` binary is just a wrapper around the underlying mdBook crates, +exposing their functionality as a command-line program. If you want to +programmatically drive mdBook, you can use the [`mdbook-driver`] crate. +This can be used to add your own functionality or tweak the build process. -The easiest way to find out how to use the `mdbook` crate is by looking at the +The easiest way to find out how to use the `mdbook-driver` crate is by looking at the [API Docs]. The top level documentation explains how one would use the [`MDBook`] type to load and build a book, while the [config] module gives a good explanation on the configuration system. - -[`MDBook`]: https://docs.rs/mdbook/*/mdbook/book/struct.MDBook.html -[API Docs]: https://docs.rs/mdbook/*/mdbook/ -[config]: https://docs.rs/mdbook/*/mdbook/config/index.html +[`MDBook`]: https://docs.rs/mdbook-driver/latest/mdbook_driver/struct.MDBook.html +[API Docs]: https://docs.rs/mdbook-driver/latest/mdbook_driver/ +[config]: https://docs.rs/mdbook-driver/latest/mdbook_driver/config/index.html diff --git a/guide/src/for_developers/backends.md b/guide/src/for_developers/backends.md index 72f8263e..c49381d9 100644 --- a/guide/src/for_developers/backends.md +++ b/guide/src/for_developers/backends.md @@ -16,13 +16,13 @@ This page will step you through creating your own alternative backend in the for of a simple word counting program. Although it will be written in Rust, there's no reason why it couldn't be accomplished using something like Python or Ruby. -First you'll want to create a new binary program and add `mdbook` as a +First you'll want to create a new binary program and add `mdbook-renderer` as a dependency. ```shell $ cargo new --bin mdbook-wordcount $ cd mdbook-wordcount -$ cargo add mdbook +$ cargo add mdbook-renderer ``` When our `mdbook-wordcount` plugin is invoked, `mdbook` will send it a JSON @@ -33,10 +33,8 @@ This is all the boilerplate necessary for our backend to load the book. ```rust // src/main.rs -extern crate mdbook; - use std::io; -use mdbook::renderer::RenderContext; +use mdbook_renderer::RenderContext; fn main() { let mut stdin = io::stdin(); @@ -45,13 +43,12 @@ fn main() { ``` > **Note:** The `RenderContext` contains a `version` field. This lets backends - figure out whether they are compatible with the version of `mdbook` it's being - called by. This `version` comes directly from the corresponding field in - `mdbook`'s `Cargo.toml`. - - It is recommended that backends use the [`semver`] crate to inspect this field - and emit a warning if there may be a compatibility issue. - +> figure out whether they are compatible with the version of `mdbook` it's being +> called by. This `version` comes directly from the corresponding field in +> `mdbook`'s `Cargo.toml`. +> +> It is recommended that backends use the [`semver`] crate to inspect this field +> and emit a warning if there may be a compatibility issue. ## Inspecting the Book @@ -183,9 +180,7 @@ $ cargo add serde serde_derive And then you can create the config struct, ```rust -extern crate serde; -#[macro_use] -extern crate serde_derive; +use serde_derive::{Serialize, Deserialize}; ... @@ -337,10 +332,10 @@ the source code or ask questions. [Third Party Plugins]: https://github.com/rust-lang/mdBook/wiki/Third-party-plugins -[`RenderContext`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html -[`RenderContext::from_json()`]: https://docs.rs/mdbook/*/mdbook/renderer/struct.RenderContext.html#method.from_json +[`RenderContext`]: https://docs.rs/mdbook-renderer/latest/mdbook_renderer/struct.RenderContext.html +[`RenderContext::from_json()`]: https://docs.rs/mdbook-renderer/latest/mdbook_renderer/struct.RenderContext.html#method.from_json [`semver`]: https://crates.io/crates/semver -[`Book`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html -[`Book::iter()`]: https://docs.rs/mdbook/*/mdbook/book/struct.Book.html#method.iter -[`Config`]: https://docs.rs/mdbook/*/mdbook/config/struct.Config.html +[`Book`]: https://docs.rs/mdbook-renderer/latest/mdbook_renderer/book/struct.Book.html +[`Book::iter()`]: https://docs.rs/mdbook-renderer/latest/mdbook_renderer/book/struct.Book.html#method.iter +[`Config`]: https://docs.rs/mdbook-renderer/latest/mdbook_renderer/config/struct.Config.html [issue tracker]: https://github.com/rust-lang/mdBook/issues diff --git a/guide/src/for_developers/preprocessors.md b/guide/src/for_developers/preprocessors.md index 1455aceb..1abd39cc 100644 --- a/guide/src/for_developers/preprocessors.md +++ b/guide/src/for_developers/preprocessors.md @@ -45,7 +45,7 @@ be adapted for other preprocessors. ## Hints For Implementing A Preprocessor -By pulling in `mdbook` as a library, preprocessors can have access to the +By pulling in `mdbook-preprocessor` as a library, preprocessors can have access to the existing infrastructure for dealing with books. For example, a custom preprocessor could use the @@ -60,9 +60,7 @@ chapters) or via the `Book::for_each_mut()` convenience method. The `chapter.content` is just a string which happens to be markdown. While it's entirely possible to use regular expressions or do a manual find & replace, you'll probably want to process the input into something more computer-friendly. -The [`pulldown-cmark`][pc] crate implements a production-quality event-based -Markdown parser, with the [`pulldown-cmark-to-cmark`][pctc] crate allowing you to -translate events back into markdown text. +The [`mdbook-markdown`] crate exposes the [`pulldown-cmark`][pc] crate used by mdBook to parse Markdown. The [`pulldown-cmark-to-cmark`][pctc] crate can be used to translate events back into markdown text. The following code block shows how to remove all emphasis from markdown, without accidentally breaking the document. @@ -100,11 +98,11 @@ if __name__ == '__main__': [emphasis-example]: https://github.com/rust-lang/mdBook/tree/master/examples/remove-emphasis/ -[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html [pc]: https://crates.io/crates/pulldown-cmark [pctc]: https://crates.io/crates/pulldown-cmark-to-cmark [an example no-op preprocessor]: https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs -[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html#method.parse_input -[`Book::for_each_mut()`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html#method.for_each_mut -[`PreprocessorContext`]: https://docs.rs/mdbook/latest/mdbook/preprocess/struct.PreprocessorContext.html -[`Book`]: https://docs.rs/mdbook/latest/mdbook/book/struct.Book.html +[`CmdPreprocessor::parse_input()`]: https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/trait.Preprocessor.html#method.parse_input +[`Book::for_each_mut()`]: https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html#method.for_each_mut +[`PreprocessorContext`]: https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/struct.PreprocessorContext.html +[`Book`]: https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html +[`mdbook-markdown`]: https://docs.rs/mdbook-markdown/latest/mdbook_markdown/ From a397f64356f88566376545fb5e02d56b107eda51 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 23 Jul 2025 16:40:25 -0700 Subject: [PATCH 44/44] Add a section on how to run tests --- CONTRIBUTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 29f47f9a..2c230fe3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,6 +123,18 @@ Please consider the following when making a change: * Check out the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) for guidelines on designing the API. +## Tests + +The main test harness is described in the [testsuite documentation](tests/testsuite/README.md). There are several different commands to run different kinds of tests: + +- `cargo test --workspace` — This runs all of the unit and integration tests, except for the GUI tests. +- `cargo test --test gui` — This runs the [GUI test harness](#browser-compatibility-and-testing). This does not get run automatically due to its extra requirements. +- `npm run lint` — [Checks the `.js` files](#checking-changes-in-js-files) +- `cargo test --workspace --no-default-features` — Testing without default features helps check that all feature checks are implemented correctly. +- `cargo clippy --workspace --all-targets --no-deps -- -D warnings` — This makes sure that there are no clippy warnings. +- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --document-private-items --no-deps` — This verifies that there aren't any rustdoc warnings. +- `cargo fmt --check` — Verifies that everything is formatted correctly. + ## Making a pull-request When you feel comfortable that your changes could be integrated into mdBook, you can create a pull-request on GitHub.