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