]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! Markdown formatting for rustdoc | |
12 | //! | |
13 | //! This module implements markdown formatting through the hoedown C-library | |
14 | //! (bundled into the rust runtime). This module self-contains the C bindings | |
15 | //! and necessary legwork to render markdown, and exposes all of the | |
16 | //! functionality through a unit-struct, `Markdown`, which has an implementation | |
92a42be0 | 17 | //! of `fmt::Display`. Example usage: |
1a4d82fc JJ |
18 | //! |
19 | //! ```rust,ignore | |
20 | //! use rustdoc::html::markdown::Markdown; | |
21 | //! | |
22 | //! let s = "My *markdown* _text_"; | |
23 | //! let html = format!("{}", Markdown(s)); | |
24 | //! // ... something using html | |
25 | //! ``` | |
26 | ||
1a4d82fc JJ |
27 | #![allow(non_camel_case_types)] |
28 | ||
29 | use libc; | |
7453a54e | 30 | use rustc::session::config::get_unstable_features_setting; |
1a4d82fc | 31 | use std::ascii::AsciiExt; |
85aaf69f | 32 | use std::cell::RefCell; |
9346a6ac AL |
33 | use std::default::Default; |
34 | use std::ffi::CString; | |
1a4d82fc JJ |
35 | use std::fmt; |
36 | use std::slice; | |
37 | use std::str; | |
7453a54e | 38 | use syntax::feature_gate::UnstableFeatures; |
1a4d82fc | 39 | |
92a42be0 | 40 | use html::render::derive_id; |
1a4d82fc JJ |
41 | use html::toc::TocBuilder; |
42 | use html::highlight; | |
43 | use html::escape::Escape; | |
44 | use test; | |
45 | ||
92a42be0 | 46 | /// A unit struct which has the `fmt::Display` trait implemented. When |
1a4d82fc JJ |
47 | /// formatted, this struct will emit the HTML corresponding to the rendered |
48 | /// version of the contained markdown string. | |
49 | pub struct Markdown<'a>(pub &'a str); | |
50 | /// A unit struct like `Markdown`, that renders the markdown with a | |
51 | /// table of contents. | |
52 | pub struct MarkdownWithToc<'a>(pub &'a str); | |
53 | ||
54 | const DEF_OUNIT: libc::size_t = 64; | |
e9174d1e | 55 | const HOEDOWN_EXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 11; |
1a4d82fc JJ |
56 | const HOEDOWN_EXT_TABLES: libc::c_uint = 1 << 0; |
57 | const HOEDOWN_EXT_FENCED_CODE: libc::c_uint = 1 << 1; | |
58 | const HOEDOWN_EXT_AUTOLINK: libc::c_uint = 1 << 3; | |
59 | const HOEDOWN_EXT_STRIKETHROUGH: libc::c_uint = 1 << 4; | |
60 | const HOEDOWN_EXT_SUPERSCRIPT: libc::c_uint = 1 << 8; | |
61 | const HOEDOWN_EXT_FOOTNOTES: libc::c_uint = 1 << 2; | |
62 | ||
63 | const HOEDOWN_EXTENSIONS: libc::c_uint = | |
64 | HOEDOWN_EXT_NO_INTRA_EMPHASIS | HOEDOWN_EXT_TABLES | | |
65 | HOEDOWN_EXT_FENCED_CODE | HOEDOWN_EXT_AUTOLINK | | |
66 | HOEDOWN_EXT_STRIKETHROUGH | HOEDOWN_EXT_SUPERSCRIPT | | |
67 | HOEDOWN_EXT_FOOTNOTES; | |
68 | ||
e9174d1e | 69 | enum hoedown_document {} |
1a4d82fc JJ |
70 | |
71 | type blockcodefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, | |
e9174d1e SL |
72 | *const hoedown_buffer, *const hoedown_renderer_data); |
73 | ||
74 | type blockquotefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, | |
75 | *const hoedown_renderer_data); | |
1a4d82fc JJ |
76 | |
77 | type headerfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, | |
e9174d1e SL |
78 | libc::c_int, *const hoedown_renderer_data); |
79 | ||
80 | type blockhtmlfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, | |
81 | *const hoedown_renderer_data); | |
1a4d82fc | 82 | |
9346a6ac | 83 | type codespanfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, |
e9174d1e | 84 | *const hoedown_renderer_data) -> libc::c_int; |
9346a6ac | 85 | |
85aaf69f SL |
86 | type linkfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer, |
87 | *const hoedown_buffer, *const hoedown_buffer, | |
e9174d1e SL |
88 | *const hoedown_renderer_data) -> libc::c_int; |
89 | ||
90 | type entityfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer, | |
91 | *const hoedown_renderer_data); | |
85aaf69f SL |
92 | |
93 | type normaltextfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, | |
e9174d1e SL |
94 | *const hoedown_renderer_data); |
95 | ||
96 | #[repr(C)] | |
97 | struct hoedown_renderer_data { | |
98 | opaque: *mut libc::c_void, | |
99 | } | |
85aaf69f | 100 | |
1a4d82fc JJ |
101 | #[repr(C)] |
102 | struct hoedown_renderer { | |
85aaf69f SL |
103 | opaque: *mut libc::c_void, |
104 | ||
1a4d82fc | 105 | blockcode: Option<blockcodefn>, |
e9174d1e | 106 | blockquote: Option<blockquotefn>, |
1a4d82fc | 107 | header: Option<headerfn>, |
e9174d1e SL |
108 | |
109 | other_block_level_callbacks: [libc::size_t; 11], | |
110 | ||
111 | blockhtml: Option<blockhtmlfn>, | |
85aaf69f SL |
112 | |
113 | /* span level callbacks - NULL or return 0 prints the span verbatim */ | |
9346a6ac AL |
114 | autolink: libc::size_t, // unused |
115 | codespan: Option<codespanfn>, | |
116 | other_span_level_callbacks_1: [libc::size_t; 7], | |
85aaf69f | 117 | link: Option<linkfn>, |
e9174d1e | 118 | other_span_level_callbacks_2: [libc::size_t; 6], |
85aaf69f SL |
119 | |
120 | /* low level callbacks - NULL copies input directly into the output */ | |
e9174d1e | 121 | entity: Option<entityfn>, |
85aaf69f SL |
122 | normal_text: Option<normaltextfn>, |
123 | ||
124 | /* header and footer */ | |
e9174d1e | 125 | other_callbacks: [libc::size_t; 2], |
1a4d82fc JJ |
126 | } |
127 | ||
128 | #[repr(C)] | |
129 | struct hoedown_html_renderer_state { | |
130 | opaque: *mut libc::c_void, | |
131 | toc_data: html_toc_data, | |
132 | flags: libc::c_uint, | |
133 | link_attributes: Option<extern "C" fn(*mut hoedown_buffer, | |
134 | *const hoedown_buffer, | |
e9174d1e | 135 | *const hoedown_renderer_data)>, |
1a4d82fc JJ |
136 | } |
137 | ||
138 | #[repr(C)] | |
139 | struct html_toc_data { | |
140 | header_count: libc::c_int, | |
141 | current_level: libc::c_int, | |
142 | level_offset: libc::c_int, | |
143 | nesting_level: libc::c_int, | |
144 | } | |
145 | ||
146 | struct MyOpaque { | |
147 | dfltblk: extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer, | |
e9174d1e | 148 | *const hoedown_buffer, *const hoedown_renderer_data), |
1a4d82fc JJ |
149 | toc_builder: Option<TocBuilder>, |
150 | } | |
151 | ||
152 | #[repr(C)] | |
153 | struct hoedown_buffer { | |
154 | data: *const u8, | |
155 | size: libc::size_t, | |
156 | asize: libc::size_t, | |
157 | unit: libc::size_t, | |
158 | } | |
159 | ||
160 | // hoedown FFI | |
161 | #[link(name = "hoedown", kind = "static")] | |
7453a54e SL |
162 | #[cfg(not(cargobuild))] |
163 | extern {} | |
164 | ||
1a4d82fc JJ |
165 | extern { |
166 | fn hoedown_html_renderer_new(render_flags: libc::c_uint, | |
167 | nesting_level: libc::c_int) | |
168 | -> *mut hoedown_renderer; | |
169 | fn hoedown_html_renderer_free(renderer: *mut hoedown_renderer); | |
170 | ||
e9174d1e | 171 | fn hoedown_document_new(rndr: *const hoedown_renderer, |
1a4d82fc JJ |
172 | extensions: libc::c_uint, |
173 | max_nesting: libc::size_t) -> *mut hoedown_document; | |
174 | fn hoedown_document_render(doc: *mut hoedown_document, | |
175 | ob: *mut hoedown_buffer, | |
176 | document: *const u8, | |
177 | doc_size: libc::size_t); | |
178 | fn hoedown_document_free(md: *mut hoedown_document); | |
179 | ||
180 | fn hoedown_buffer_new(unit: libc::size_t) -> *mut hoedown_buffer; | |
85aaf69f SL |
181 | fn hoedown_buffer_put(b: *mut hoedown_buffer, c: *const libc::c_char, |
182 | n: libc::size_t); | |
1a4d82fc JJ |
183 | fn hoedown_buffer_puts(b: *mut hoedown_buffer, c: *const libc::c_char); |
184 | fn hoedown_buffer_free(b: *mut hoedown_buffer); | |
185 | ||
186 | } | |
187 | ||
85aaf69f SL |
188 | // hoedown_buffer helpers |
189 | impl hoedown_buffer { | |
190 | fn as_bytes(&self) -> &[u8] { | |
191 | unsafe { slice::from_raw_parts(self.data, self.size as usize) } | |
192 | } | |
193 | } | |
194 | ||
1a4d82fc JJ |
195 | /// Returns Some(code) if `s` is a line that should be stripped from |
196 | /// documentation but used in example code. `code` is the portion of | |
197 | /// `s` that should be used in tests. (None for lines that should be | |
198 | /// left as-is.) | |
199 | fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> { | |
200 | let trimmed = s.trim(); | |
9346a6ac AL |
201 | if trimmed == "#" { |
202 | Some("") | |
203 | } else if trimmed.starts_with("# ") { | |
85aaf69f | 204 | Some(&trimmed[2..]) |
1a4d82fc JJ |
205 | } else { |
206 | None | |
207 | } | |
208 | } | |
209 | ||
9346a6ac AL |
210 | /// Returns a new string with all consecutive whitespace collapsed into |
211 | /// single spaces. | |
212 | /// | |
213 | /// Any leading or trailing whitespace will be trimmed. | |
214 | fn collapse_whitespace(s: &str) -> String { | |
b039eaaf | 215 | s.split_whitespace().collect::<Vec<_>>().join(" ") |
9346a6ac AL |
216 | } |
217 | ||
1a4d82fc JJ |
218 | thread_local!(pub static PLAYGROUND_KRATE: RefCell<Option<Option<String>>> = { |
219 | RefCell::new(None) | |
220 | }); | |
221 | ||
222 | pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result { | |
223 | extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer, | |
e9174d1e | 224 | lang: *const hoedown_buffer, data: *const hoedown_renderer_data) { |
1a4d82fc JJ |
225 | unsafe { |
226 | if orig_text.is_null() { return } | |
227 | ||
e9174d1e | 228 | let opaque = (*data).opaque as *mut hoedown_html_renderer_state; |
1a4d82fc | 229 | let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque); |
85aaf69f | 230 | let text = (*orig_text).as_bytes(); |
1a4d82fc JJ |
231 | let origtext = str::from_utf8(text).unwrap(); |
232 | debug!("docblock: ==============\n{:?}\n=======", text); | |
233 | let rendered = if lang.is_null() { | |
234 | false | |
235 | } else { | |
85aaf69f | 236 | let rlang = (*lang).as_bytes(); |
1a4d82fc JJ |
237 | let rlang = str::from_utf8(rlang).unwrap(); |
238 | if !LangString::parse(rlang).rust { | |
239 | (my_opaque.dfltblk)(ob, orig_text, lang, | |
e9174d1e | 240 | opaque as *const hoedown_renderer_data); |
1a4d82fc JJ |
241 | true |
242 | } else { | |
243 | false | |
244 | } | |
245 | }; | |
246 | ||
247 | let lines = origtext.lines().filter(|l| { | |
248 | stripped_filtered_line(*l).is_none() | |
249 | }); | |
c1a9b12d | 250 | let text = lines.collect::<Vec<&str>>().join("\n"); |
1a4d82fc JJ |
251 | if rendered { return } |
252 | PLAYGROUND_KRATE.with(|krate| { | |
7453a54e SL |
253 | // insert newline to clearly separate it from the |
254 | // previous block so we can shorten the html output | |
255 | let mut s = String::from("\n"); | |
85aaf69f | 256 | krate.borrow().as_ref().map(|krate| { |
1a4d82fc JJ |
257 | let test = origtext.lines().map(|l| { |
258 | stripped_filtered_line(l).unwrap_or(l) | |
c1a9b12d | 259 | }).collect::<Vec<&str>>().join("\n"); |
85aaf69f | 260 | let krate = krate.as_ref().map(|s| &**s); |
9346a6ac AL |
261 | let test = test::maketest(&test, krate, false, |
262 | &Default::default()); | |
85aaf69f | 263 | s.push_str(&format!("<span class='rusttest'>{}</span>", Escape(&test))); |
1a4d82fc | 264 | }); |
54a0048b SL |
265 | s.push_str(&highlight::render_with_highlighting(&text, |
266 | Some("rust-example-rendered"), | |
267 | None)); | |
85aaf69f | 268 | let output = CString::new(s).unwrap(); |
1a4d82fc JJ |
269 | hoedown_buffer_puts(ob, output.as_ptr()); |
270 | }) | |
271 | } | |
272 | } | |
273 | ||
274 | extern fn header(ob: *mut hoedown_buffer, text: *const hoedown_buffer, | |
e9174d1e | 275 | level: libc::c_int, data: *const hoedown_renderer_data) { |
1a4d82fc JJ |
276 | // hoedown does this, we may as well too |
277 | unsafe { hoedown_buffer_puts(ob, "\n\0".as_ptr() as *const _); } | |
278 | ||
279 | // Extract the text provided | |
280 | let s = if text.is_null() { | |
b039eaaf | 281 | "".to_owned() |
1a4d82fc | 282 | } else { |
85aaf69f | 283 | let s = unsafe { (*text).as_bytes() }; |
b039eaaf | 284 | str::from_utf8(&s).unwrap().to_owned() |
1a4d82fc JJ |
285 | }; |
286 | ||
b039eaaf SL |
287 | // Discard '<em>', '<code>' tags and some escaped characters, |
288 | // transform the contents of the header into a hyphenated string | |
289 | // without non-alphanumeric characters other than '-' and '_'. | |
290 | // | |
1a4d82fc JJ |
291 | // This is a terrible hack working around how hoedown gives us rendered |
292 | // html for text rather than the raw text. | |
b039eaaf SL |
293 | let mut id = s.clone(); |
294 | let repl_sub = vec!["<em>", "</em>", "<code>", "</code>", | |
295 | "<strong>", "</strong>", | |
296 | "<", ">", "&", "'", """]; | |
297 | for sub in repl_sub { | |
298 | id = id.replace(sub, ""); | |
299 | } | |
300 | let id = id.chars().filter_map(|c| { | |
301 | if c.is_alphanumeric() || c == '-' || c == '_' { | |
302 | if c.is_ascii() { | |
303 | Some(c.to_ascii_lowercase()) | |
304 | } else { | |
305 | Some(c) | |
306 | } | |
307 | } else if c.is_whitespace() && c.is_ascii() { | |
308 | Some('-') | |
309 | } else { | |
310 | None | |
311 | } | |
312 | }).collect::<String>(); | |
1a4d82fc | 313 | |
e9174d1e | 314 | let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state }; |
1a4d82fc JJ |
315 | let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }; |
316 | ||
92a42be0 | 317 | let id = derive_id(id); |
b039eaaf SL |
318 | |
319 | let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| { | |
320 | format!("{} ", builder.push(level as u32, s.clone(), id.clone())) | |
321 | }); | |
1a4d82fc JJ |
322 | |
323 | // Render the HTML | |
b039eaaf SL |
324 | let text = format!("<h{lvl} id='{id}' class='section-header'>\ |
325 | <a href='#{id}'>{sec}{}</a></h{lvl}>", | |
326 | s, lvl = level, id = id, sec = sec); | |
1a4d82fc | 327 | |
85aaf69f | 328 | let text = CString::new(text).unwrap(); |
1a4d82fc JJ |
329 | unsafe { hoedown_buffer_puts(ob, text.as_ptr()) } |
330 | } | |
331 | ||
e9174d1e SL |
332 | extern fn codespan( |
333 | ob: *mut hoedown_buffer, | |
334 | text: *const hoedown_buffer, | |
335 | _: *const hoedown_renderer_data, | |
336 | ) -> libc::c_int { | |
9346a6ac | 337 | let content = if text.is_null() { |
b039eaaf | 338 | "".to_owned() |
9346a6ac AL |
339 | } else { |
340 | let bytes = unsafe { (*text).as_bytes() }; | |
341 | let s = str::from_utf8(bytes).unwrap(); | |
342 | collapse_whitespace(s) | |
343 | }; | |
344 | ||
345 | let content = format!("<code>{}</code>", Escape(&content)); | |
346 | let element = CString::new(content).unwrap(); | |
347 | unsafe { hoedown_buffer_puts(ob, element.as_ptr()); } | |
e9174d1e SL |
348 | // Return anything except 0, which would mean "also print the code span verbatim". |
349 | 1 | |
9346a6ac AL |
350 | } |
351 | ||
1a4d82fc JJ |
352 | unsafe { |
353 | let ob = hoedown_buffer_new(DEF_OUNIT); | |
354 | let renderer = hoedown_html_renderer_new(0, 0); | |
355 | let mut opaque = MyOpaque { | |
356 | dfltblk: (*renderer).blockcode.unwrap(), | |
357 | toc_builder: if print_toc {Some(TocBuilder::new())} else {None} | |
358 | }; | |
85aaf69f SL |
359 | (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque |
360 | = &mut opaque as *mut _ as *mut libc::c_void; | |
c34b1796 AL |
361 | (*renderer).blockcode = Some(block); |
362 | (*renderer).header = Some(header); | |
9346a6ac | 363 | (*renderer).codespan = Some(codespan); |
1a4d82fc JJ |
364 | |
365 | let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16); | |
366 | hoedown_document_render(document, ob, s.as_ptr(), | |
367 | s.len() as libc::size_t); | |
368 | hoedown_document_free(document); | |
369 | ||
370 | hoedown_html_renderer_free(renderer); | |
371 | ||
b039eaaf SL |
372 | let mut ret = opaque.toc_builder.map_or(Ok(()), |builder| { |
373 | write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc()) | |
374 | }); | |
1a4d82fc JJ |
375 | |
376 | if ret.is_ok() { | |
85aaf69f | 377 | let buf = (*ob).as_bytes(); |
1a4d82fc JJ |
378 | ret = w.write_str(str::from_utf8(buf).unwrap()); |
379 | } | |
380 | hoedown_buffer_free(ob); | |
381 | ret | |
382 | } | |
383 | } | |
384 | ||
385 | pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) { | |
386 | extern fn block(_ob: *mut hoedown_buffer, | |
387 | text: *const hoedown_buffer, | |
388 | lang: *const hoedown_buffer, | |
e9174d1e | 389 | data: *const hoedown_renderer_data) { |
1a4d82fc JJ |
390 | unsafe { |
391 | if text.is_null() { return } | |
392 | let block_info = if lang.is_null() { | |
393 | LangString::all_false() | |
394 | } else { | |
85aaf69f | 395 | let lang = (*lang).as_bytes(); |
1a4d82fc JJ |
396 | let s = str::from_utf8(lang).unwrap(); |
397 | LangString::parse(s) | |
398 | }; | |
399 | if !block_info.rust { return } | |
85aaf69f | 400 | let text = (*text).as_bytes(); |
e9174d1e | 401 | let opaque = (*data).opaque as *mut hoedown_html_renderer_state; |
1a4d82fc JJ |
402 | let tests = &mut *((*opaque).opaque as *mut ::test::Collector); |
403 | let text = str::from_utf8(text).unwrap(); | |
404 | let lines = text.lines().map(|l| { | |
405 | stripped_filtered_line(l).unwrap_or(l) | |
406 | }); | |
c1a9b12d | 407 | let text = lines.collect::<Vec<&str>>().join("\n"); |
b039eaaf | 408 | tests.add_test(text.to_owned(), |
c34b1796 | 409 | block_info.should_panic, block_info.no_run, |
7453a54e SL |
410 | block_info.ignore, block_info.test_harness, |
411 | block_info.compile_fail); | |
1a4d82fc JJ |
412 | } |
413 | } | |
414 | ||
415 | extern fn header(_ob: *mut hoedown_buffer, | |
416 | text: *const hoedown_buffer, | |
e9174d1e | 417 | level: libc::c_int, data: *const hoedown_renderer_data) { |
1a4d82fc | 418 | unsafe { |
e9174d1e | 419 | let opaque = (*data).opaque as *mut hoedown_html_renderer_state; |
1a4d82fc JJ |
420 | let tests = &mut *((*opaque).opaque as *mut ::test::Collector); |
421 | if text.is_null() { | |
422 | tests.register_header("", level as u32); | |
423 | } else { | |
85aaf69f | 424 | let text = (*text).as_bytes(); |
1a4d82fc JJ |
425 | let text = str::from_utf8(text).unwrap(); |
426 | tests.register_header(text, level as u32); | |
427 | } | |
428 | } | |
429 | } | |
430 | ||
431 | unsafe { | |
432 | let ob = hoedown_buffer_new(DEF_OUNIT); | |
433 | let renderer = hoedown_html_renderer_new(0, 0); | |
c34b1796 AL |
434 | (*renderer).blockcode = Some(block); |
435 | (*renderer).header = Some(header); | |
85aaf69f SL |
436 | (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque |
437 | = tests as *mut _ as *mut libc::c_void; | |
1a4d82fc JJ |
438 | |
439 | let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16); | |
440 | hoedown_document_render(document, ob, doc.as_ptr(), | |
441 | doc.len() as libc::size_t); | |
442 | hoedown_document_free(document); | |
443 | ||
444 | hoedown_html_renderer_free(renderer); | |
445 | hoedown_buffer_free(ob); | |
446 | } | |
447 | } | |
448 | ||
85aaf69f | 449 | #[derive(Eq, PartialEq, Clone, Debug)] |
1a4d82fc | 450 | struct LangString { |
c34b1796 | 451 | should_panic: bool, |
1a4d82fc JJ |
452 | no_run: bool, |
453 | ignore: bool, | |
454 | rust: bool, | |
455 | test_harness: bool, | |
7453a54e | 456 | compile_fail: bool, |
1a4d82fc JJ |
457 | } |
458 | ||
459 | impl LangString { | |
460 | fn all_false() -> LangString { | |
461 | LangString { | |
c34b1796 | 462 | should_panic: false, |
1a4d82fc JJ |
463 | no_run: false, |
464 | ignore: false, | |
465 | rust: true, // NB This used to be `notrust = false` | |
466 | test_harness: false, | |
7453a54e | 467 | compile_fail: false, |
1a4d82fc JJ |
468 | } |
469 | } | |
470 | ||
471 | fn parse(string: &str) -> LangString { | |
472 | let mut seen_rust_tags = false; | |
473 | let mut seen_other_tags = false; | |
474 | let mut data = LangString::all_false(); | |
7453a54e SL |
475 | let allow_compile_fail = match get_unstable_features_setting() { |
476 | UnstableFeatures::Allow | UnstableFeatures::Cheat=> true, | |
477 | _ => false, | |
478 | }; | |
1a4d82fc | 479 | |
85aaf69f | 480 | let tokens = string.split(|c: char| |
1a4d82fc JJ |
481 | !(c == '_' || c == '-' || c.is_alphanumeric()) |
482 | ); | |
483 | ||
484 | for token in tokens { | |
485 | match token { | |
486 | "" => {}, | |
c34b1796 | 487 | "should_panic" => { data.should_panic = true; seen_rust_tags = true; }, |
1a4d82fc JJ |
488 | "no_run" => { data.no_run = true; seen_rust_tags = true; }, |
489 | "ignore" => { data.ignore = true; seen_rust_tags = true; }, | |
490 | "rust" => { data.rust = true; seen_rust_tags = true; }, | |
7453a54e SL |
491 | "test_harness" => { data.test_harness = true; seen_rust_tags = true; }, |
492 | "compile_fail" if allow_compile_fail => { | |
493 | data.compile_fail = true; | |
494 | seen_rust_tags = true; | |
495 | data.no_run = true; | |
496 | }, | |
1a4d82fc JJ |
497 | _ => { seen_other_tags = true } |
498 | } | |
499 | } | |
500 | ||
501 | data.rust &= !seen_other_tags || seen_rust_tags; | |
502 | ||
503 | data | |
504 | } | |
505 | } | |
506 | ||
85aaf69f | 507 | impl<'a> fmt::Display for Markdown<'a> { |
1a4d82fc JJ |
508 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
509 | let Markdown(md) = *self; | |
510 | // This is actually common enough to special-case | |
9346a6ac | 511 | if md.is_empty() { return Ok(()) } |
85aaf69f | 512 | render(fmt, md, false) |
1a4d82fc JJ |
513 | } |
514 | } | |
515 | ||
85aaf69f | 516 | impl<'a> fmt::Display for MarkdownWithToc<'a> { |
1a4d82fc JJ |
517 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
518 | let MarkdownWithToc(md) = *self; | |
85aaf69f SL |
519 | render(fmt, md, true) |
520 | } | |
521 | } | |
522 | ||
523 | pub fn plain_summary_line(md: &str) -> String { | |
524 | extern fn link(_ob: *mut hoedown_buffer, | |
525 | _link: *const hoedown_buffer, | |
526 | _title: *const hoedown_buffer, | |
527 | content: *const hoedown_buffer, | |
e9174d1e | 528 | data: *const hoedown_renderer_data) -> libc::c_int |
85aaf69f SL |
529 | { |
530 | unsafe { | |
531 | if !content.is_null() && (*content).size > 0 { | |
e9174d1e | 532 | let ob = (*data).opaque as *mut hoedown_buffer; |
85aaf69f SL |
533 | hoedown_buffer_put(ob, (*content).data as *const libc::c_char, |
534 | (*content).size); | |
535 | } | |
536 | } | |
537 | 1 | |
538 | } | |
539 | ||
540 | extern fn normal_text(_ob: *mut hoedown_buffer, | |
541 | text: *const hoedown_buffer, | |
e9174d1e | 542 | data: *const hoedown_renderer_data) |
85aaf69f SL |
543 | { |
544 | unsafe { | |
e9174d1e | 545 | let ob = (*data).opaque as *mut hoedown_buffer; |
85aaf69f SL |
546 | hoedown_buffer_put(ob, (*text).data as *const libc::c_char, |
547 | (*text).size); | |
548 | } | |
549 | } | |
550 | ||
551 | unsafe { | |
552 | let ob = hoedown_buffer_new(DEF_OUNIT); | |
553 | let mut plain_renderer: hoedown_renderer = ::std::mem::zeroed(); | |
c34b1796 | 554 | let renderer: *mut hoedown_renderer = &mut plain_renderer; |
85aaf69f | 555 | (*renderer).opaque = ob as *mut libc::c_void; |
c34b1796 AL |
556 | (*renderer).link = Some(link); |
557 | (*renderer).normal_text = Some(normal_text); | |
85aaf69f SL |
558 | |
559 | let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16); | |
560 | hoedown_document_render(document, ob, md.as_ptr(), | |
561 | md.len() as libc::size_t); | |
562 | hoedown_document_free(document); | |
563 | let plain_slice = (*ob).as_bytes(); | |
b039eaaf | 564 | let plain = str::from_utf8(plain_slice).unwrap_or("").to_owned(); |
85aaf69f SL |
565 | hoedown_buffer_free(ob); |
566 | plain | |
1a4d82fc JJ |
567 | } |
568 | } | |
569 | ||
570 | #[cfg(test)] | |
571 | mod tests { | |
572 | use super::{LangString, Markdown}; | |
b039eaaf | 573 | use super::plain_summary_line; |
92a42be0 | 574 | use html::render::reset_ids; |
1a4d82fc JJ |
575 | |
576 | #[test] | |
577 | fn test_lang_string_parse() { | |
578 | fn t(s: &str, | |
7453a54e SL |
579 | should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool, |
580 | compile_fail: bool) { | |
1a4d82fc | 581 | assert_eq!(LangString::parse(s), LangString { |
c34b1796 | 582 | should_panic: should_panic, |
1a4d82fc JJ |
583 | no_run: no_run, |
584 | ignore: ignore, | |
585 | rust: rust, | |
586 | test_harness: test_harness, | |
7453a54e | 587 | compile_fail: compile_fail, |
1a4d82fc JJ |
588 | }) |
589 | } | |
590 | ||
7453a54e SL |
591 | // marker | should_panic| no_run| ignore| rust | test_harness| compile_fail |
592 | t("", false, false, false, true, false, false); | |
593 | t("rust", false, false, false, true, false, false); | |
594 | t("sh", false, false, false, false, false, false); | |
595 | t("ignore", false, false, true, true, false, false); | |
596 | t("should_panic", true, false, false, true, false, false); | |
597 | t("no_run", false, true, false, true, false, false); | |
598 | t("test_harness", false, false, false, true, true, false); | |
599 | t("compile_fail", false, true, false, true, false, true); | |
600 | t("{.no_run .example}", false, true, false, true, false, false); | |
601 | t("{.sh .should_panic}", true, false, false, true, false, false); | |
602 | t("{.example .rust}", false, false, false, true, false, false); | |
603 | t("{.test_harness .rust}", false, false, false, true, true, false); | |
1a4d82fc JJ |
604 | } |
605 | ||
606 | #[test] | |
607 | fn issue_17736() { | |
608 | let markdown = "# title"; | |
85aaf69f | 609 | format!("{}", Markdown(markdown)); |
54a0048b | 610 | reset_ids(true); |
85aaf69f SL |
611 | } |
612 | ||
b039eaaf SL |
613 | #[test] |
614 | fn test_header() { | |
615 | fn t(input: &str, expect: &str) { | |
616 | let output = format!("{}", Markdown(input)); | |
617 | assert_eq!(output, expect); | |
54a0048b | 618 | reset_ids(true); |
b039eaaf SL |
619 | } |
620 | ||
621 | t("# Foo bar", "\n<h1 id='foo-bar' class='section-header'>\ | |
622 | <a href='#foo-bar'>Foo bar</a></h1>"); | |
623 | t("## Foo-bar_baz qux", "\n<h2 id='foo-bar_baz-qux' class=\'section-\ | |
624 | header'><a href='#foo-bar_baz-qux'>Foo-bar_baz qux</a></h2>"); | |
625 | t("### **Foo** *bar* baz!?!& -_qux_-%", | |
626 | "\n<h3 id='foo-bar-baz--_qux_-' class='section-header'>\ | |
627 | <a href='#foo-bar-baz--_qux_-'><strong>Foo</strong> \ | |
628 | <em>bar</em> baz!?!& -_qux_-%</a></h3>"); | |
629 | t("####**Foo?** & \\*bar?!* _`baz`_ ❤ #qux", | |
630 | "\n<h4 id='foo--bar--baz--qux' class='section-header'>\ | |
631 | <a href='#foo--bar--baz--qux'><strong>Foo?</strong> & *bar?!* \ | |
632 | <em><code>baz</code></em> ❤ #qux</a></h4>"); | |
633 | } | |
634 | ||
92a42be0 SL |
635 | #[test] |
636 | fn test_header_ids_multiple_blocks() { | |
637 | fn t(input: &str, expect: &str) { | |
638 | let output = format!("{}", Markdown(input)); | |
639 | assert_eq!(output, expect); | |
640 | } | |
641 | ||
642 | let test = || { | |
643 | t("# Example", "\n<h1 id='example' class='section-header'>\ | |
644 | <a href='#example'>Example</a></h1>"); | |
645 | t("# Panics", "\n<h1 id='panics' class='section-header'>\ | |
646 | <a href='#panics'>Panics</a></h1>"); | |
647 | t("# Example", "\n<h1 id='example-1' class='section-header'>\ | |
648 | <a href='#example-1'>Example</a></h1>"); | |
649 | t("# Main", "\n<h1 id='main-1' class='section-header'>\ | |
650 | <a href='#main-1'>Main</a></h1>"); | |
651 | t("# Example", "\n<h1 id='example-2' class='section-header'>\ | |
652 | <a href='#example-2'>Example</a></h1>"); | |
653 | t("# Panics", "\n<h1 id='panics-1' class='section-header'>\ | |
654 | <a href='#panics-1'>Panics</a></h1>"); | |
655 | }; | |
656 | test(); | |
54a0048b | 657 | reset_ids(true); |
92a42be0 SL |
658 | test(); |
659 | } | |
660 | ||
85aaf69f SL |
661 | #[test] |
662 | fn test_plain_summary_line() { | |
663 | fn t(input: &str, expect: &str) { | |
664 | let output = plain_summary_line(input); | |
665 | assert_eq!(output, expect); | |
666 | } | |
667 | ||
e9174d1e | 668 | t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)"); |
85aaf69f SL |
669 | t("code `let x = i32;` ...", "code `let x = i32;` ..."); |
670 | t("type `Type<'static>` ...", "type `Type<'static>` ..."); | |
671 | t("# top header", "top header"); | |
672 | t("## header", "header"); | |
1a4d82fc JJ |
673 | } |
674 | } |