Skip to main content

mdbook_blush/
lib.rs

1use anyhow::Result;
2use mdbook::book::{Book, Chapter};
3use mdbook::errors::Result as MdbookResult;
4use mdbook::preprocess::{Preprocessor, PreprocessorContext};
5use mdbook::BookItem;
6use pulldown_cmark::{Event, Options, Parser};
7
8pub struct BlushPreprocessor;
9
10impl BlushPreprocessor {
11    pub fn new() -> Self {
12        Self
13    }
14
15    fn preprocess_bookitem(&self, item: &mut BookItem) -> Result<()> {
16        match item {
17            BookItem::Chapter(chapter) => self.preprocess_chapter(chapter),
18            BookItem::Separator | BookItem::PartTitle(_) => Ok(()),
19        }
20    }
21
22    fn preprocess_chapter(&self, chapter: &mut Chapter) -> Result<()> {
23        let parser = Parser::new_ext(&chapter.content, Options::all()).map(|event| match event {
24            Event::Text(text) => Event::Text(self.preprocess_text(&text).into()),
25            _ => event,
26        });
27        let new_content_capacity = (chapter.content.len() as f64 * 1.05) as usize;
28        let mut new_content = String::with_capacity(new_content_capacity);
29        pulldown_cmark_to_cmark::cmark(parser, &mut new_content)?;
30        chapter.content = new_content;
31
32        for sub_item in &mut chapter.sub_items {
33            self.preprocess_bookitem(sub_item)?;
34        }
35
36        Ok(())
37    }
38
39    fn preprocess_text(&self, text: &str) -> String {
40        let mut fragments = Vec::with_capacity(8);
41        let mut cursor = 0;
42        let mut char_indices = text
43            .char_indices()
44            .filter(|(_, chr)| *chr == '=' || *chr == ' ' || *chr == '\t');
45        while let Some((index, chr)) = char_indices.next() {
46            if chr != '=' {
47                continue;
48            }
49
50            fragments.push(&text[cursor..index]);
51            cursor = index;
52
53            if let Some((end_index, chr)) = char_indices.next() {
54                if chr != '=' || end_index == 1 + index {
55                    fragments.push(&text[cursor..end_index]);
56                    cursor = end_index;
57                    continue;
58                }
59
60                fragments.push(r#"<span class="small-caps">"#);
61                fragments.push(&text[(cursor + 1)..end_index]);
62                fragments.push(r#"</span>"#);
63                cursor = end_index + 1;
64            }
65        }
66
67        fragments.push(&text[cursor..]);
68        fragments.concat()
69    }
70}
71
72impl Preprocessor for BlushPreprocessor {
73    fn name(&self) -> &str {
74        "mdbook-blush"
75    }
76
77    fn supports_renderer(&self, renderer: &str) -> bool {
78        renderer == "html"
79    }
80
81    fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> MdbookResult<Book> {
82        book.sections
83            .iter_mut()
84            .try_for_each(|section| self.preprocess_bookitem(section))?;
85        Ok(book)
86    }
87}