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.
This commit is contained in:
Eric Huss 2025-07-21 15:30:51 -07:00
parent 12285f505d
commit 3278f84373
8 changed files with 93 additions and 79 deletions

4
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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<PathBuf, String>,
#[serde(skip)]
__non_exhaustive: (),
}
impl RenderContext {
/// Create a new `RenderContext`.
pub fn new<P, Q>(root: P, book: Book, config: Config, destination: Q) -> RenderContext
where
P: Into<PathBuf>,
Q: Into<PathBuf>,
{
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<R: Read>(reader: R) -> Result<RenderContext> {
serde_json::from_reader(reader).with_context(|| "Unable to deserialize the `RenderContext`")
}
}

View file

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

View file

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

View file

@ -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<PathBuf, String>,
#[serde(skip)]
__non_exhaustive: (),
}
impl RenderContext {
/// Create a new `RenderContext`.
pub fn new<P, Q>(root: P, book: Book, config: Config, destination: Q) -> RenderContext
where
P: Into<PathBuf>,
Q: Into<PathBuf>,
{
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<R: Read>(reader: R) -> Result<RenderContext> {
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

View file

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