mdbook-blush 0.1.0

An mdBook small-caps preprocessor
Documentation
use anyhow::Result;
use mdbook::book::{Book, Chapter};
use mdbook::errors::Result as MdbookResult;
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use mdbook::BookItem;
use pulldown_cmark::{Event, Options, Parser};

pub struct BlushPreprocessor;

impl BlushPreprocessor {
    pub fn new() -> Self {
        Self
    }

    fn preprocess_bookitem(&self, item: &mut BookItem) -> Result<()> {
        match item {
            BookItem::Chapter(chapter) => self.preprocess_chapter(chapter),
            BookItem::Separator | BookItem::PartTitle(_) => Ok(()),
        }
    }

    fn preprocess_chapter(&self, chapter: &mut Chapter) -> Result<()> {
        let parser = Parser::new_ext(&chapter.content, Options::all()).map(|event| match event {
            Event::Text(text) => Event::Text(self.preprocess_text(&text).into()),
            _ => event,
        });
        let new_content_capacity = (chapter.content.len() as f64 * 1.05) as usize;
        let mut new_content = String::with_capacity(new_content_capacity);
        pulldown_cmark_to_cmark::cmark(parser, &mut new_content)?;
        chapter.content = new_content;

        for sub_item in &mut chapter.sub_items {
            self.preprocess_bookitem(sub_item)?;
        }

        Ok(())
    }

    fn preprocess_text(&self, text: &str) -> String {
        let mut fragments = Vec::with_capacity(8);
        let mut cursor = 0;
        let mut char_indices = text
            .char_indices()
            .filter(|(_, chr)| *chr == '=' || *chr == ' ' || *chr == '\t');
        while let Some((index, chr)) = char_indices.next() {
            if chr != '=' {
                continue;
            }

            fragments.push(&text[cursor..index]);
            cursor = index;

            if let Some((end_index, chr)) = char_indices.next() {
                if chr != '=' || end_index == 1 + index {
                    fragments.push(&text[cursor..end_index]);
                    cursor = end_index;
                    continue;
                }

                fragments.push(r#"<span class="small-caps">"#);
                fragments.push(&text[(cursor + 1)..end_index]);
                fragments.push(r#"</span>"#);
                cursor = end_index + 1;
            }
        }

        fragments.push(&text[cursor..]);
        fragments.concat()
    }
}

impl Preprocessor for BlushPreprocessor {
    fn name(&self) -> &str {
        "mdbook-blush"
    }

    fn supports_renderer(&self, renderer: &str) -> bool {
        renderer == "html"
    }

    fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> MdbookResult<Book> {
        book.sections
            .iter_mut()
            .try_for_each(|section| self.preprocess_bookitem(section))?;
        Ok(book)
    }
}