mdbook_merjong/
preprocessor.rs

1use mdbook::{
2    book::{Book, BookItem},
3    errors::Result as MdbookResult,
4    preprocess::{Preprocessor, PreprocessorContext},
5};
6use pulldown_cmark::{CodeBlockKind::*, Event, Options, Parser, Tag, TagEnd};
7
8pub struct Merjong;
9
10impl Preprocessor for Merjong {
11    fn name(&self) -> &str {
12        "merjong"
13    }
14    fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> MdbookResult<Book> {
15        let mut res = None;
16        book.for_each_mut(|item: &mut BookItem| {
17            if let Some(Err(_)) = res {
18                return;
19            }
20            if let BookItem::Chapter(ref mut chapter) = *item {
21                res = Some(preprocess(&chapter.content).map(|md| {
22                    chapter.content = md;
23                }));
24            }
25        });
26        res.unwrap_or(Ok(())).map(|_| book)
27    }
28    fn supports_renderer(&self, _renderer: &str) -> bool {
29        true
30    }
31}
32
33fn preprocess(content: &str) -> MdbookResult<String> {
34    let mut merjong_content = String::new();
35    let mut in_merjong_block = false;
36
37    let options = Options::empty();
38
39    let mut code_span = 0..0;
40    let mut start_new_code_span = true;
41
42    let mut merjong_blocks = vec![];
43
44    let events = Parser::new_ext(content, options);
45    for (event, span) in events.into_offset_iter() {
46        if let Event::Start(Tag::CodeBlock(Fenced(code))) = &event {
47            if code.as_ref() == "merjong" {
48                in_merjong_block = true;
49                merjong_content.clear();
50            }
51        }
52
53        if !in_merjong_block {
54            continue;
55        }
56
57        if let Event::Text(_) = event {
58            if start_new_code_span {
59                code_span = span;
60                start_new_code_span = false;
61            } else {
62                code_span = code_span.start..span.end;
63            }
64
65            continue;
66        }
67
68        if let Event::End(TagEnd::CodeBlock) = &event {
69            in_merjong_block = false;
70
71            let merjong_content = &content[code_span.clone()]
72                .replace("\r\n", "\n")
73                .trim_end()
74                .to_string();
75            let merjong_code = format!("<pre class=\"merjong\">{}</pre>", merjong_content);
76            merjong_blocks.push((span, merjong_code));
77            start_new_code_span = true;
78        }
79    }
80
81    let mut new_content = content.to_string();
82
83    for (span, block) in merjong_blocks.iter().rev() {
84        let pre_content = &new_content[..span.start];
85        let post_content = &new_content[span.end..];
86        new_content = format!("{}{}{}", pre_content, block, post_content);
87    }
88
89    Ok(new_content)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    #[test]
96
97    fn test_preprocess() {
98        let content = r#"
99# Chapter 1
100
101```merjong
102234m-234m-234m-222p-5m-5m
103````
104
105"#;
106
107        let result = preprocess(content).expect("preprocess failed");
108
109        println!("test_preprocess' s Result: {}", result);
110    }
111}