2018-01-21 22:35:11 +08:00
|
|
|
# Preprocessors
|
|
|
|
|
|
|
|
A *preprocessor* is simply a bit of code which gets run immediately after the
|
|
|
|
book is loaded and before it gets rendered, allowing you to update and mutate
|
|
|
|
the book. Possible use cases are:
|
|
|
|
|
2018-01-23 21:10:52 +08:00
|
|
|
- Creating custom helpers like `\{{#include /path/to/file.md}}`
|
2018-02-24 18:14:52 +08:00
|
|
|
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
|
2018-01-21 22:35:11 +08:00
|
|
|
to `[some chapter](some_chapter.html)` for the HTML renderer
|
2018-02-24 18:14:52 +08:00
|
|
|
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
|
2018-01-21 22:35:11 +08:00
|
|
|
mathjax equivalents
|
|
|
|
|
|
|
|
|
2018-02-24 18:14:52 +08:00
|
|
|
## Implementing a Preprocessor
|
2018-01-21 22:35:11 +08:00
|
|
|
|
|
|
|
A preprocessor is represented by the `Preprocessor` trait.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
pub trait Preprocessor {
|
|
|
|
fn name(&self) -> &str;
|
2018-09-10 18:58:55 +08:00
|
|
|
fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book>;
|
|
|
|
fn supports_renderer(&self, _renderer: &str) -> bool {
|
|
|
|
true
|
|
|
|
}
|
2018-01-21 22:35:11 +08:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Where the `PreprocessorContext` is defined as
|
|
|
|
|
|
|
|
```rust
|
|
|
|
pub struct PreprocessorContext {
|
|
|
|
pub root: PathBuf,
|
|
|
|
pub config: Config,
|
2018-09-10 18:58:55 +08:00
|
|
|
/// The `Renderer` this preprocessor is being used with.
|
|
|
|
pub renderer: String,
|
2018-01-21 22:35:11 +08:00
|
|
|
}
|
2018-02-24 18:14:52 +08:00
|
|
|
```
|
|
|
|
|
2018-09-10 18:58:55 +08:00
|
|
|
The `renderer` value allows you react accordingly, for example, PDF or HTML.
|
|
|
|
|
2018-02-24 18:14:52 +08:00
|
|
|
## A complete Example
|
|
|
|
|
2018-08-03 10:34:26 +08:00
|
|
|
The magic happens within the `run(...)` method of the
|
|
|
|
[`Preprocessor`][preprocessor-docs] trait implementation.
|
2018-02-24 18:14:52 +08:00
|
|
|
|
2018-08-03 10:34:26 +08:00
|
|
|
As direct access to the chapters is not possible, you will probably end up
|
|
|
|
iterating them using `for_each_mut(...)`:
|
2018-02-24 18:14:52 +08:00
|
|
|
|
|
|
|
```rust
|
|
|
|
book.for_each_mut(|item: &mut BookItem| {
|
|
|
|
if let BookItem::Chapter(ref mut chapter) = *item {
|
|
|
|
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name);
|
|
|
|
res = Some(
|
|
|
|
match Deemphasize::remove_emphasis(&mut num_removed_items, chapter) {
|
|
|
|
Ok(md) => {
|
|
|
|
chapter.content = md;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(err) => Err(err),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
The `chapter.content` is just a markdown formatted string, and you will have to
|
2018-08-03 10:34:26 +08:00
|
|
|
process it in some way. Even though it's entirely possible to implement some
|
|
|
|
sort of manual find & replace operation, if that feels too unsafe you can use
|
|
|
|
[`pulldown-cmark`][pc] to parse the string into events and work on them instead.
|
2018-02-24 18:14:52 +08:00
|
|
|
|
2018-08-03 10:34:26 +08:00
|
|
|
Finally you can use [`pulldown-cmark-to-cmark`][pctc] to transform these events
|
|
|
|
back to a string.
|
2018-02-24 18:14:52 +08:00
|
|
|
|
2018-08-03 10:34:26 +08:00
|
|
|
The following code block shows how to remove all emphasis from markdown, and do
|
|
|
|
so safely.
|
2018-02-24 18:14:52 +08:00
|
|
|
|
|
|
|
```rust
|
2018-09-10 18:58:55 +08:00
|
|
|
fn remove_emphasis(
|
|
|
|
num_removed_items: &mut usize,
|
|
|
|
chapter: &mut Chapter,
|
|
|
|
) -> Result<String> {
|
2018-02-24 18:14:52 +08:00
|
|
|
let mut buf = String::with_capacity(chapter.content.len());
|
2018-09-10 18:58:55 +08:00
|
|
|
|
2018-02-24 18:14:52 +08:00
|
|
|
let events = Parser::new(&chapter.content).filter(|e| {
|
|
|
|
let should_keep = match *e {
|
|
|
|
Event::Start(Tag::Emphasis)
|
|
|
|
| Event::Start(Tag::Strong)
|
|
|
|
| Event::End(Tag::Emphasis)
|
|
|
|
| Event::End(Tag::Strong) => false,
|
|
|
|
_ => true,
|
|
|
|
};
|
|
|
|
if !should_keep {
|
|
|
|
*num_removed_items += 1;
|
|
|
|
}
|
|
|
|
should_keep
|
|
|
|
});
|
2018-09-10 18:58:55 +08:00
|
|
|
|
|
|
|
cmark(events, &mut buf, None).map(|_| buf).map_err(|err| {
|
|
|
|
Error::from(format!("Markdown serialization failed: {}", err))
|
|
|
|
})
|
2018-02-24 18:14:52 +08:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
For everything else, have a look [at the complete example][example].
|
|
|
|
|
2018-09-10 18:58:55 +08:00
|
|
|
[preprocessor-docs]: https://docs.rs/mdbook/latest/mdbook/preprocess/trait.Preprocessor.html
|
2018-02-24 18:14:52 +08:00
|
|
|
[pc]: https://crates.io/crates/pulldown-cmark
|
|
|
|
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
|
|
|
|
[example]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/de-emphasize.rs
|