Skip to content

Commit bb043ef

Browse files
ByronMichael-F-Bryan
authored andcommitted
Add complete preprocessor example (#629)
* First version of preprocessor example, with quicli It seems it's not worth it right now. * Remove quicli, just to simplify everything * Finish de-emphasise example * Finish preprocessor example in book * Rename preprocessor type * Apply changes requested in review * Update preprocessor docs with latest code [skip CI]
1 parent 82aef1b commit bb043ef

File tree

4 files changed

+173
-4
lines changed

4 files changed

+173
-4
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ error-chain = "0.11"
5757
select = "0.4"
5858
pretty_assertions = "0.4"
5959
walkdir = "2.0"
60+
pulldown-cmark-to-cmark = "1.1.0"
6061

6162
[features]
6263
default = ["output", "watch", "serve"]

book-example/src/for_developers/preprocessors.md

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ book is loaded and before it gets rendered, allowing you to update and mutate
55
the book. Possible use cases are:
66

77
- Creating custom helpers like `\{{#include /path/to/file.md}}`
8-
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
8+
- Updating links so `[some chapter](some_chapter.md)` is automatically changed
99
to `[some chapter](some_chapter.html)` for the HTML renderer
10-
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
10+
- Substituting in latex-style expressions (`$$ \frac{1}{3} $$`) with their
1111
mathjax equivalents
1212

1313

14-
## Implementing a Preprocessor
14+
## Implementing a Preprocessor
1515

1616
A preprocessor is represented by the `Preprocessor` trait.
1717

@@ -29,4 +29,68 @@ pub struct PreprocessorContext {
2929
pub root: PathBuf,
3030
pub config: Config,
3131
}
32-
```
32+
```
33+
34+
## A complete Example
35+
36+
The magic happens within the `run(...)` method of the [`Preprocessor`][preprocessor-docs] trait implementation.
37+
38+
As direct access to the chapters is not possible, you will probably end up iterating
39+
them using `for_each_mut(...)`:
40+
41+
```rust
42+
book.for_each_mut(|item: &mut BookItem| {
43+
if let BookItem::Chapter(ref mut chapter) = *item {
44+
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name);
45+
res = Some(
46+
match Deemphasize::remove_emphasis(&mut num_removed_items, chapter) {
47+
Ok(md) => {
48+
chapter.content = md;
49+
Ok(())
50+
}
51+
Err(err) => Err(err),
52+
},
53+
);
54+
}
55+
});
56+
```
57+
58+
The `chapter.content` is just a markdown formatted string, and you will have to
59+
process it in some way. Even though it's entirely possible to implement some sort of
60+
manual find & replace operation, if that feels too unsafe you can use [`pulldown-cmark`][pc]
61+
to parse the string into events and work on them instead.
62+
63+
Finally you can use [`pulldown-cmark-to-cmark`][pctc] to transform these events back to
64+
a string.
65+
66+
The following code block shows how to remove all emphasis from markdown, and do so
67+
safely.
68+
69+
```rust
70+
fn remove_emphasis(num_removed_items: &mut i32, chapter: &mut Chapter) -> Result<String> {
71+
let mut buf = String::with_capacity(chapter.content.len());
72+
let events = Parser::new(&chapter.content).filter(|e| {
73+
let should_keep = match *e {
74+
Event::Start(Tag::Emphasis)
75+
| Event::Start(Tag::Strong)
76+
| Event::End(Tag::Emphasis)
77+
| Event::End(Tag::Strong) => false,
78+
_ => true,
79+
};
80+
if !should_keep {
81+
*num_removed_items += 1;
82+
}
83+
should_keep
84+
});
85+
cmark(events, &mut buf, None)
86+
.map(|_| buf)
87+
.map_err(|err| Error::from(format!("Markdown serialization failed: {}", err)))
88+
}
89+
```
90+
91+
For everything else, have a look [at the complete example][example].
92+
93+
[preprocessor-docs]: https://docs.rs/mdbook/0.1.3/mdbook/preprocess/trait.Preprocessor.html
94+
[pc]: https://crates.io/crates/pulldown-cmark
95+
[pctc]: https://crates.io/crates/pulldown-cmark-to-cmark
96+
[example]: https://github.com/rust-lang-nursery/mdBook/blob/master/examples/de-emphasize.rs

examples/de-emphasize.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! This program removes all forms of emphasis from the markdown of the book.
2+
extern crate mdbook;
3+
extern crate pulldown_cmark;
4+
extern crate pulldown_cmark_to_cmark;
5+
6+
use mdbook::errors::{Error, Result};
7+
use mdbook::MDBook;
8+
use mdbook::book::{Book, BookItem, Chapter};
9+
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
10+
use pulldown_cmark::{Event, Parser, Tag};
11+
use pulldown_cmark_to_cmark::fmt::cmark;
12+
13+
use std::ffi::OsString;
14+
use std::env::{args, args_os};
15+
use std::process;
16+
17+
struct Deemphasize;
18+
19+
impl Preprocessor for Deemphasize {
20+
fn name(&self) -> &str {
21+
"md-links-to-html-links"
22+
}
23+
24+
fn run(&self, _ctx: &PreprocessorContext, book: &mut Book) -> Result<()> {
25+
eprintln!("Running '{}' preprocessor", self.name());
26+
let mut res: Option<_> = None;
27+
let mut num_removed_items = 0;
28+
book.for_each_mut(|item: &mut BookItem| {
29+
if let Some(Err(_)) = res {
30+
return;
31+
}
32+
if let BookItem::Chapter(ref mut chapter) = *item {
33+
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name);
34+
res = Some(
35+
match Deemphasize::remove_emphasis(&mut num_removed_items, chapter) {
36+
Ok(md) => {
37+
chapter.content = md;
38+
Ok(())
39+
}
40+
Err(err) => Err(err),
41+
},
42+
);
43+
}
44+
});
45+
eprintln!(
46+
"{}: removed {} events from markdown stream.",
47+
self.name(),
48+
num_removed_items
49+
);
50+
match res {
51+
Some(res) => res,
52+
None => Ok(()),
53+
}
54+
}
55+
}
56+
57+
fn do_it(book: OsString) -> Result<()> {
58+
let mut book = MDBook::load(book)?;
59+
book.with_preprecessor(Deemphasize);
60+
book.build()
61+
}
62+
63+
fn main() {
64+
if args_os().count() != 2 {
65+
eprintln!("USAGE: {} <book>", args().next().expect("executable"));
66+
return;
67+
}
68+
if let Err(e) = do_it(args_os().skip(1).next().expect("one argument")) {
69+
eprintln!("{}", e);
70+
process::exit(1);
71+
}
72+
}
73+
74+
impl Deemphasize {
75+
fn remove_emphasis(num_removed_items: &mut i32, chapter: &mut Chapter) -> Result<String> {
76+
let mut buf = String::with_capacity(chapter.content.len());
77+
let events = Parser::new(&chapter.content).filter(|e| {
78+
let should_keep = match *e {
79+
Event::Start(Tag::Emphasis)
80+
| Event::Start(Tag::Strong)
81+
| Event::End(Tag::Emphasis)
82+
| Event::End(Tag::Strong) => false,
83+
_ => true,
84+
};
85+
if !should_keep {
86+
*num_removed_items += 1;
87+
}
88+
should_keep
89+
});
90+
cmark(events, &mut buf, None)
91+
.map(|_| buf)
92+
.map_err(|err| Error::from(format!("Markdown serialization failed: {}", err)))
93+
}
94+
}

0 commit comments

Comments
 (0)