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).
This commit is contained in:
Eric Huss
2025-07-21 12:20:21 -07:00
parent a224bfd7d7
commit fc76a47d6e
31 changed files with 84 additions and 56 deletions

5
Cargo.lock generated
View File

@@ -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]]

View File

@@ -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

View File

@@ -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

View File

@@ -10,3 +10,4 @@ pub const MDBOOK_VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod errors {
pub use anyhow::{Error, Result};
}
pub mod utils;

View File

@@ -29,7 +29,7 @@ pub fn write_file<P: AsRef<Path>>(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), "../../");
/// ```

View File

@@ -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) {

View File

@@ -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<Value>;
}

View File

@@ -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.

View File

@@ -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)]

View File

@@ -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};

View File

@@ -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;

View File

@@ -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#"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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};

View File

@@ -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;

View File

@@ -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<String, String>;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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<ResourceHelper> {
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]

View File

@@ -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)]

View File

@@ -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,

View File

@@ -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
"#]]);
});

View File

@@ -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 <unknown> defined multiple times - not updating to new definition
[TIMESTAMP] [WARN] (mdbook::utils): footnote `unused` in `<unknown>` 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 <unknown> defined multiple times - not updating to new definition
[TIMESTAMP] [WARN] (mdbook_core::utils): footnote `unused` in `<unknown>` 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`
"#]]);

View File

@@ -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
"#]]);
});

View File

@@ -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.
"#]]);

View File

@@ -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]
"#]]);
});

View File

@@ -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
"#]]);
});

View File

@@ -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
"#]]);
});

View File

@@ -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
"#]]);
});