1use mdbook::{
2 BookItem,
3 book::{Book, Chapter},
4 errors::Error,
5 preprocess::{Preprocessor, PreprocessorContext},
6};
7use pulldown_cmark::{CodeBlockKind, CowStr, Event, Tag, TagEnd};
8
9pub struct BadAscii;
11
12fn create_svg_html(formal_mode: bool, s: &str) -> String {
13 let tb = badascii::TextBuffer::with_text(s);
14 let job = if !formal_mode {
15 badascii::RenderJob::rough(tb)
16 } else {
17 badascii::RenderJob::formal(tb)
18 };
19 let svg = badascii::svg::render(&job, "currentColor", "none");
21 format!("\n\n<pre>{svg}</pre>\n")
22}
23impl BadAscii {
24 fn process_chapter(formal_mode: bool, chapter: &mut Chapter) {
25 let mut options = pulldown_cmark::Options::empty();
26 options.insert(pulldown_cmark::Options::ENABLE_TABLES);
27 let parser = pulldown_cmark::Parser::new_ext(&chapter.content, options);
28 let mut buf = String::with_capacity(chapter.content.len() + 128);
29 let mut in_block = false;
32 let mut diagram = String::new();
33 let events = parser.filter_map(|event| match (&event, in_block) {
34 (
35 Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("badascii")))),
36 false,
37 ) => {
38 in_block = true;
39 diagram.clear();
40 None
41 }
42 (Event::Text(content), true) => {
43 diagram.push_str(content);
44 None
45 }
46 (Event::End(TagEnd::CodeBlock), true) => {
47 in_block = false;
48 Some(Event::Html(create_svg_html(formal_mode, &diagram).into()))
49 }
50 _ => Some(event),
51 });
52 pulldown_cmark_to_cmark::cmark(events, &mut buf).unwrap();
53 chapter.content = buf;
54 }
55}
56
57impl Preprocessor for BadAscii {
58 fn name(&self) -> &str {
59 "badascii"
60 }
61
62 fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
63 let formal_mode = if let Some(nop_cfg) = &ctx.config.get_preprocessor(self.name()) {
66 nop_cfg.contains_key("formal")
67 } else {
68 false
69 };
70 book.for_each_mut(|item| {
71 if let BookItem::Chapter(chapter) = item {
72 Self::process_chapter(formal_mode, chapter);
73 }
74 });
75
76 Ok(book)
78 }
79
80 fn supports_renderer(&self, renderer: &str) -> bool {
81 renderer != "not-supported"
82 }
83}
84
85#[cfg(test)]
86mod test {
87 use super::*;
88
89 #[test]
90 fn conversion_works() {
91 let md = r##"
92# Chapter 1
93
94Here is a diagram of the mascot.
95```badascii
96 +----+
97 | OO|
98 +----+
99```
100 "##;
101 let mut chapter = Chapter {
102 name: "Test".into(),
103 content: md.into(),
104 number: None,
105 sub_items: vec![],
106 path: None,
107 source_path: None,
108 parent_names: vec![],
109 };
110 BadAscii::process_chapter(false, &mut chapter);
111 let expect = expect_test::expect_file!["test.md"];
112 expect.assert_eq(&chapter.content);
113 }
114
115 #[test]
116 fn test_formal_mode_works() {
117 let md = r##"
118# Chapter 1
119
120Here is a diagram of the mascot.
121```badascii
122 +----+
123 | OO|
124 +----+
125```
126 "##;
127 let mut chapter = Chapter {
128 name: "Test".into(),
129 content: md.into(),
130 number: None,
131 sub_items: vec![],
132 path: None,
133 source_path: None,
134 parent_names: vec![],
135 };
136 BadAscii::process_chapter(true, &mut chapter);
137 let expect = expect_test::expect_file!["test_formal.md"];
138 expect.assert_eq(&chapter.content);
139 }
140
141 #[test]
142 fn badascii_preprocessor_run() {
143 let input_json = r##"[
144 {
145 "root": "/path/to/book",
146 "config": {
147 "book": {
148 "authors": ["AUTHOR"],
149 "language": "en",
150 "multilingual": false,
151 "src": "src",
152 "title": "TITLE"
153 },
154 "preprocessor": {
155 "nop": {}
156 }
157 },
158 "renderer": "html",
159 "mdbook_version": "0.4.21"
160 },
161 {
162 "sections": [
163 {
164 "Chapter": {
165 "name": "Chapter 1",
166 "content": "# Chapter 1",
167 "number": [1],
168 "sub_items": [],
169 "path": "chapter_1.md",
170 "source_path": "chapter_1.md",
171 "parent_names": []
172 }
173 }
174 ],
175 "__non_exhaustive": null
176 }
177 ]"##;
178 let input_json = input_json.as_bytes();
179
180 let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
181 let result = BadAscii.run(&ctx, book);
182 assert!(result.is_ok());
183 let expected_book = expect_test::expect_file!["test.txt"];
184 let actual_book = result.unwrap();
185 expected_book.assert_debug_eq(&actual_book);
186 }
187}