Skip to content

Commit 12285f5

Browse files
committed
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.
1 parent e123879 commit 12285f5

File tree

13 files changed

+111
-99
lines changed

13 files changed

+111
-99
lines changed

Cargo.lock

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ rust-version = "1.85.0" # Keep in sync with installation.md and .github/workflow
2424
anyhow = "1.0.98"
2525
log = "0.4.27"
2626
mdbook-core = { path = "crates/mdbook-core" }
27+
mdbook-preprocessor = { path = "crates/mdbook-preprocessor" }
2728
mdbook-summary = { path = "crates/mdbook-summary" }
2829
memchr = "2.7.5"
2930
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"
6162
hex = "0.4.3"
6263
log.workspace = true
6364
mdbook-core.workspace = true
65+
mdbook-preprocessor.workspace = true
6466
mdbook-summary.workspace = true
6567
memchr.workspace = true
6668
opener = "0.8.1"

crates/mdbook-preprocessor/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ repository.workspace = true
88
rust-version.workspace = true
99

1010
[dependencies]
11+
anyhow.workspace = true
1112
mdbook-core.workspace = true
13+
serde.workspace = true
14+
serde_json.workspace = true
1215

1316
[lints]
1417
workspace = true

crates/mdbook-preprocessor/src/lib.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,76 @@
11
//! Library to assist implementing an mdbook preprocessor.
22
3+
use anyhow::Context;
4+
use mdbook_core::book::Book;
5+
use mdbook_core::config::Config;
6+
use mdbook_core::errors::Result;
7+
use serde::{Deserialize, Serialize};
8+
use std::cell::RefCell;
9+
use std::collections::HashMap;
10+
use std::io::Read;
11+
use std::path::PathBuf;
12+
313
pub use mdbook_core::MDBOOK_VERSION;
14+
pub use mdbook_core::book;
15+
pub use mdbook_core::config;
16+
pub use mdbook_core::errors;
17+
18+
/// An operation which is run immediately after loading a book into memory and
19+
/// before it gets rendered.
20+
pub trait Preprocessor {
21+
/// Get the `Preprocessor`'s name.
22+
fn name(&self) -> &str;
23+
24+
/// Run this `Preprocessor`, allowing it to update the book before it is
25+
/// given to a renderer.
26+
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book>;
27+
28+
/// A hint to `MDBook` whether this preprocessor is compatible with a
29+
/// particular renderer.
30+
///
31+
/// By default, always returns `true`.
32+
fn supports_renderer(&self, _renderer: &str) -> bool {
33+
true
34+
}
35+
}
36+
37+
/// Extra information for a `Preprocessor` to give them more context when
38+
/// processing a book.
39+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40+
pub struct PreprocessorContext {
41+
/// The location of the book directory on disk.
42+
pub root: PathBuf,
43+
/// The book configuration (`book.toml`).
44+
pub config: Config,
45+
/// The `Renderer` this preprocessor is being used with.
46+
pub renderer: String,
47+
/// The calling `mdbook` version.
48+
pub mdbook_version: String,
49+
/// Internal mapping of chapter titles.
50+
///
51+
/// This is used internally by mdbook to compute custom chapter titles.
52+
/// This should not be used outside of mdbook's internals.
53+
#[serde(skip)]
54+
pub chapter_titles: RefCell<HashMap<PathBuf, String>>,
55+
#[serde(skip)]
56+
__non_exhaustive: (),
57+
}
58+
59+
impl PreprocessorContext {
60+
/// Create a new `PreprocessorContext`.
61+
pub fn new(root: PathBuf, config: Config, renderer: String) -> Self {
62+
PreprocessorContext {
63+
root,
64+
config,
65+
renderer,
66+
mdbook_version: crate::MDBOOK_VERSION.to_string(),
67+
chapter_titles: RefCell::new(HashMap::new()),
68+
__non_exhaustive: (),
69+
}
70+
}
71+
}
72+
73+
/// Parses the input given to a preprocessor.
74+
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
75+
serde_json::from_reader(reader).with_context(|| "Unable to parse the input")
76+
}

examples/nop-preprocessor.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! A basic example of a preprocessor that does nothing.
22
33
use crate::nop_lib::Nop;
4-
use anyhow::Error;
54
use clap::{Arg, ArgMatches, Command};
6-
use mdbook::book::Book;
7-
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
5+
use mdbook_preprocessor::book::Book;
6+
use mdbook_preprocessor::errors::Result;
7+
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
88
use semver::{Version, VersionReq};
99
use std::io;
1010
use std::process;
@@ -33,8 +33,8 @@ fn main() {
3333
}
3434
}
3535

36-
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
37-
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
36+
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> {
37+
let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;
3838

3939
let book_version = Version::parse(&ctx.mdbook_version)?;
4040
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
@@ -88,7 +88,7 @@ mod nop_lib {
8888
"nop-preprocessor"
8989
}
9090

91-
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, Error> {
91+
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
9292
// In testing we want to tell the preprocessor to blow up by setting a
9393
// particular config value
9494
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
@@ -149,7 +149,7 @@ mod nop_lib {
149149
]"##;
150150
let input_json = input_json.as_bytes();
151151

152-
let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
152+
let (ctx, book) = mdbook_preprocessor::parse_input(input_json).unwrap();
153153
let expected_book = book.clone();
154154
let result = Nop::new().run(&ctx, book);
155155
assert!(result.is_ok());

examples/remove-emphasis/mdbook-remove-emphasis/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ version = "0.1.0"
44
edition.workspace = true
55

66
[dependencies]
7-
anyhow.workspace = true
8-
mdbook = { path = "../../.." }
7+
mdbook-preprocessor.workspace = true
98
pulldown-cmark = { version = "0.12.2", default-features = false }
109
pulldown-cmark-to-cmark = "18.0.0"
1110
serde_json = "1.0.132"

examples/remove-emphasis/mdbook-remove-emphasis/src/main.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
//! This is a demonstration of an mdBook preprocessor which parses markdown
22
//! and removes any instances of emphasis.
33
4-
use anyhow::Error;
5-
use mdbook::BookItem;
6-
use mdbook::book::{Book, Chapter};
7-
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
4+
use mdbook_preprocessor::book::{Book, BookItem, Chapter};
5+
use mdbook_preprocessor::errors::Result;
6+
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
87
use pulldown_cmark::{Event, Parser, Tag, TagEnd};
98
use std::io;
109

@@ -35,7 +34,7 @@ impl Preprocessor for RemoveEmphasis {
3534
"remove-emphasis"
3635
}
3736

38-
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
37+
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
3938
let mut total = 0;
4039
book.for_each_mut(|item| {
4140
let BookItem::Chapter(ch) = item else {
@@ -55,7 +54,7 @@ impl Preprocessor for RemoveEmphasis {
5554
}
5655

5756
// ANCHOR: remove_emphasis
58-
fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result<String, Error> {
57+
fn remove_emphasis(num_removed_items: &mut usize, chapter: &mut Chapter) -> Result<String> {
5958
let mut buf = String::with_capacity(chapter.content.len());
6059

6160
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
7170
}
7271
// ANCHOR_END: remove_emphasis
7372

74-
pub fn handle_preprocessing() -> Result<(), Error> {
73+
pub fn handle_preprocessing() -> Result<()> {
7574
let pre = RemoveEmphasis;
76-
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
75+
let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;
7776

7877
let processed_book = pre.run(&ctx, book)?;
7978
serde_json::to_writer(io::stdout(), &processed_book)?;

src/book/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use log::{debug, error, info, log_enabled, trace, warn};
1515
pub use mdbook_core::book::{Book, BookItem, BookItems, Chapter, SectionNumber};
1616
use mdbook_core::config::{Config, RustEdition};
1717
use mdbook_core::utils;
18+
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
1819
pub use mdbook_summary::{Link, Summary, SummaryItem, parse_summary};
1920
use std::ffi::OsString;
2021
use std::io::{IsTerminal, Write};
@@ -24,9 +25,7 @@ use tempfile::Builder as TempFileBuilder;
2425
use toml::Value;
2526
use topological_sort::TopologicalSort;
2627

27-
use crate::preprocess::{
28-
CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext,
29-
};
28+
use crate::preprocess::{CmdPreprocessor, IndexPreprocessor, LinkPreprocessor};
3029
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
3130

3231
/// The object used to manage and build a book.

src/preprocess/cmd.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use super::{Preprocessor, PreprocessorContext};
21
use crate::book::Book;
32
use anyhow::{Context, Result, bail, ensure};
43
use log::{debug, trace, warn};
4+
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
55
use shlex::Shlex;
6-
use std::io::{self, Read, Write};
6+
use std::io::{self, Write};
77
use std::process::{Child, Command, Stdio};
88

99
/// A custom preprocessor which will shell out to a 3rd-party program.
@@ -41,12 +41,6 @@ impl CmdPreprocessor {
4141
CmdPreprocessor { name, cmd }
4242
}
4343

44-
/// A convenience function custom preprocessors can use to parse the input
45-
/// written to `stdin` by a `CmdRenderer`.
46-
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
47-
serde_json::from_reader(reader).with_context(|| "Unable to parse the input")
48-
}
49-
5044
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
5145
let stdin = child.stdin.take().expect("Child has stdin");
5246

@@ -200,7 +194,7 @@ mod tests {
200194
let mut buffer = Vec::new();
201195
cmd.write_input(&mut buffer, &md.book, &ctx).unwrap();
202196

203-
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
197+
let (got_ctx, got_book) = mdbook_preprocessor::parse_input(buffer.as_slice()).unwrap();
204198

205199
assert_eq!(got_book, md.book);
206200
assert_eq!(got_ctx, ctx);

src/preprocess/index.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use regex::Regex;
2-
use std::{path::Path, sync::LazyLock};
3-
4-
use super::{Preprocessor, PreprocessorContext};
51
use crate::book::{Book, BookItem};
62
use anyhow::Result;
73
use log::warn;
4+
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
5+
use regex::Regex;
6+
use std::{path::Path, sync::LazyLock};
87

98
/// A preprocessor for converting file name `README.md` to `index.md` since
109
/// `README.md` is the de facto index file in markdown-based documentation.

0 commit comments

Comments
 (0)