]> git.proxmox.com Git - rustc.git/blob - src/vendor/mdbook/src/utils/mod.rs
New upstream version 1.22.1+dfsg1
[rustc.git] / src / vendor / mdbook / src / utils / mod.rs
1 pub mod fs;
2
3 use pulldown_cmark::{Parser, Event, Tag, html, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
4 use std::borrow::Cow;
5
6
7 ///
8 ///
9 /// Wrapper around the pulldown-cmark parser and renderer to render markdown
10
11 pub fn render_markdown(text: &str, curly_quotes: bool) -> String {
12 let mut s = String::with_capacity(text.len() * 3 / 2);
13
14 let mut opts = Options::empty();
15 opts.insert(OPTION_ENABLE_TABLES);
16 opts.insert(OPTION_ENABLE_FOOTNOTES);
17
18 let p = Parser::new_ext(text, opts);
19 let mut converter = EventQuoteConverter::new(curly_quotes);
20 let events = p.map(clean_codeblock_headers).map(|event| converter.convert(event));
21
22 html::push_html(&mut s, events);
23 s
24 }
25
26 struct EventQuoteConverter {
27 enabled: bool,
28 convert_text: bool,
29 }
30
31 impl EventQuoteConverter {
32 fn new(enabled: bool) -> Self {
33 EventQuoteConverter { enabled: enabled, convert_text: true }
34 }
35
36 fn convert<'a>(&mut self, event: Event<'a>) -> Event<'a> {
37 if !self.enabled {
38 return event;
39 }
40
41 match event {
42 Event::Start(Tag::CodeBlock(_)) |
43 Event::Start(Tag::Code) => {
44 self.convert_text = false;
45 event
46 },
47 Event::End(Tag::CodeBlock(_)) |
48 Event::End(Tag::Code) => {
49 self.convert_text = true;
50 event
51 },
52 Event::Text(ref text) if self.convert_text => Event::Text(Cow::from(convert_quotes_to_curly(text))),
53 _ => event,
54 }
55 }
56 }
57
58 fn clean_codeblock_headers(event: Event) -> Event {
59 match event {
60 Event::Start(Tag::CodeBlock(ref info)) => {
61 let info: String = info
62 .chars()
63 .filter(|ch| !ch.is_whitespace())
64 .collect();
65
66 Event::Start(Tag::CodeBlock(Cow::from(info)))
67 },
68 _ => event,
69 }
70 }
71
72
73 fn convert_quotes_to_curly(original_text: &str) -> String {
74 // We'll consider the start to be "whitespace".
75 let mut preceded_by_whitespace = true;
76
77 original_text
78 .chars()
79 .map(|original_char| {
80 let converted_char = match original_char {
81 '\'' => if preceded_by_whitespace { '‘' } else { '’' },
82 '"' => if preceded_by_whitespace { '“' } else { '”' },
83 _ => original_char,
84 };
85
86 preceded_by_whitespace = original_char.is_whitespace();
87
88 converted_char
89 })
90 .collect()
91 }
92
93 #[cfg(test)]
94 mod tests {
95 mod render_markdown {
96 use super::super::render_markdown;
97
98 #[test]
99 fn it_can_keep_quotes_straight() {
100 assert_eq!(render_markdown("'one'", false), "<p>'one'</p>\n");
101 }
102
103 #[test]
104 fn it_can_make_quotes_curly_except_when_they_are_in_code() {
105 let input = r#"
106 'one'
107 ```
108 'two'
109 ```
110 `'three'` 'four'"#;
111 let expected = r#"<p>‘one’</p>
112 <pre><code>'two'
113 </code></pre>
114 <p><code>'three'</code> ‘four’</p>
115 "#;
116 assert_eq!(render_markdown(input, true), expected);
117 }
118
119 #[test]
120 fn whitespace_outside_of_codeblock_header_is_preserved() {
121 let input = r#"
122 some text with spaces
123 ```rust
124 fn main() {
125 // code inside is unchanged
126 }
127 ```
128 more text with spaces
129 "#;
130
131 let expected = r#"<p>some text with spaces</p>
132 <pre><code class="language-rust">fn main() {
133 // code inside is unchanged
134 }
135 </code></pre>
136 <p>more text with spaces</p>
137 "#;
138 assert_eq!(render_markdown(input, false), expected);
139 assert_eq!(render_markdown(input, true), expected);
140 }
141
142 #[test]
143 fn rust_code_block_properties_are_passed_as_space_delimited_class() {
144 let input = r#"
145 ```rust,no_run,should_panic,property_3
146 ```
147 "#;
148
149 let expected = r#"<pre><code class="language-rust,no_run,should_panic,property_3"></code></pre>
150 "#;
151 assert_eq!(render_markdown(input, false), expected);
152 assert_eq!(render_markdown(input, true), expected);
153 }
154
155 #[test]
156 fn rust_code_block_properties_with_whitespace_are_passed_as_space_delimited_class() {
157 let input = r#"
158 ```rust, no_run,,,should_panic , ,property_3
159 ```
160 "#;
161
162 let expected = r#"<pre><code class="language-rust,no_run,,,should_panic,,property_3"></code></pre>
163 "#;
164 assert_eq!(render_markdown(input, false), expected);
165 assert_eq!(render_markdown(input, true), expected);
166 }
167
168 #[test]
169 fn rust_code_block_without_properties_has_proper_html_class() {
170 let input = r#"
171 ```rust
172 ```
173 "#;
174
175 let expected = r#"<pre><code class="language-rust"></code></pre>
176 "#;
177 assert_eq!(render_markdown(input, false), expected);
178 assert_eq!(render_markdown(input, true), expected);
179
180 let input = r#"
181 ```rust
182 ```
183 "#;
184 assert_eq!(render_markdown(input, false), expected);
185 assert_eq!(render_markdown(input, true), expected);
186
187 }
188 }
189
190 mod convert_quotes_to_curly {
191 use super::super::convert_quotes_to_curly;
192
193 #[test]
194 fn it_converts_single_quotes() {
195 assert_eq!(convert_quotes_to_curly("'one', 'two'"), "‘one’, ‘two’");
196 }
197
198 #[test]
199 fn it_converts_double_quotes() {
200 assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”, “two”");
201 }
202
203 #[test]
204 fn it_treats_tab_as_whitespace() {
205 assert_eq!(convert_quotes_to_curly("\t'one'"), "\t‘one’");
206 }
207 }
208 }