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