]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
rustdoc: pretty-print Unevaluated expressions in types.
[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 pulldown-cmark
14 //! rust-library. This module exposes all of the
15 //! functionality through a unit-struct, `Markdown`, which has an implementation
16 //! of `fmt::Display`. Example usage:
17 //!
18 //! ```
19 //! #![feature(rustc_private)]
20 //!
21 //! use rustdoc::html::markdown::{RenderType, Markdown};
22 //!
23 //! let s = "My *markdown* _text_";
24 //! let html = format!("{}", Markdown(s, RenderType::Pulldown));
25 //! // ... something using html
26 //! ```
27
28 #![allow(non_camel_case_types)]
29
30 use libc;
31 use std::slice;
32
33 use std::ascii::AsciiExt;
34 use std::cell::RefCell;
35 use std::collections::{HashMap, VecDeque};
36 use std::default::Default;
37 use std::fmt::{self, Write};
38 use std::str;
39 use syntax::feature_gate::UnstableFeatures;
40 use syntax::codemap::Span;
41
42 use html::render::derive_id;
43 use html::toc::TocBuilder;
44 use html::highlight;
45 use html::escape::Escape;
46 use test;
47
48 use pulldown_cmark::{html, Event, Tag, Parser};
49 use pulldown_cmark::{Options, OPTION_ENABLE_FOOTNOTES, OPTION_ENABLE_TABLES};
50
51 #[derive(PartialEq, Debug, Clone, Copy)]
52 pub enum RenderType {
53     Hoedown,
54     Pulldown,
55 }
56
57 /// A unit struct which has the `fmt::Display` trait implemented. When
58 /// formatted, this struct will emit the HTML corresponding to the rendered
59 /// version of the contained markdown string.
60 // The second parameter is whether we need a shorter version or not.
61 pub struct Markdown<'a>(pub &'a str, pub RenderType);
62 /// A unit struct like `Markdown`, that renders the markdown with a
63 /// table of contents.
64 pub struct MarkdownWithToc<'a>(pub &'a str, pub RenderType);
65 /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
66 pub struct MarkdownHtml<'a>(pub &'a str, pub RenderType);
67 /// A unit struct like `Markdown`, that renders only the first paragraph.
68 pub struct MarkdownSummaryLine<'a>(pub &'a str);
69
70 /// Controls whether a line will be hidden or shown in HTML output.
71 ///
72 /// All lines are used in documentation tests.
73 enum Line<'a> {
74     Hidden(&'a str),
75     Shown(&'a str),
76 }
77
78 impl<'a> Line<'a> {
79     fn for_html(self) -> Option<&'a str> {
80         match self {
81             Line::Shown(l) => Some(l),
82             Line::Hidden(_) => None,
83         }
84     }
85
86     fn for_code(self) -> &'a str {
87         match self {
88             Line::Shown(l) |
89             Line::Hidden(l) => l,
90         }
91     }
92 }
93
94 // FIXME: There is a minor inconsistency here. For lines that start with ##, we
95 // have no easy way of removing a potential single space after the hashes, which
96 // is done in the single # case. This inconsistency seems okay, if non-ideal. In
97 // order to fix it we'd have to iterate to find the first non-# character, and
98 // then reallocate to remove it; which would make us return a String.
99 fn map_line(s: &str) -> Line {
100     let trimmed = s.trim();
101     if trimmed.starts_with("##") {
102         Line::Shown(&trimmed[1..])
103     } else if trimmed.starts_with("# ") {
104         // # text
105         Line::Hidden(&trimmed[2..])
106     } else if trimmed == "#" {
107         // We cannot handle '#text' because it could be #[attr].
108         Line::Hidden("")
109     } else {
110         Line::Shown(s)
111     }
112 }
113
114 /// Returns a new string with all consecutive whitespace collapsed into
115 /// single spaces.
116 ///
117 /// Any leading or trailing whitespace will be trimmed.
118 fn collapse_whitespace(s: &str) -> String {
119     s.split_whitespace().collect::<Vec<_>>().join(" ")
120 }
121
122 /// Convert chars from a title for an id.
123 ///
124 /// "Hello, world!" -> "hello-world"
125 fn slugify(c: char) -> Option<char> {
126     if c.is_alphanumeric() || c == '-' || c == '_' {
127         if c.is_ascii() {
128             Some(c.to_ascii_lowercase())
129         } else {
130             Some(c)
131         }
132     } else if c.is_whitespace() && c.is_ascii() {
133         Some('-')
134     } else {
135         None
136     }
137 }
138
139 // Information about the playground if a URL has been specified, containing an
140 // optional crate name and the URL.
141 thread_local!(pub static PLAYGROUND: RefCell<Option<(Option<String>, String)>> = {
142     RefCell::new(None)
143 });
144
145 /// Adds syntax highlighting and playground Run buttons to rust code blocks.
146 struct CodeBlocks<'a, I: Iterator<Item = Event<'a>>> {
147     inner: I,
148 }
149
150 impl<'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'a, I> {
151     fn new(iter: I) -> Self {
152         CodeBlocks {
153             inner: iter,
154         }
155     }
156 }
157
158 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
159     type Item = Event<'a>;
160
161     fn next(&mut self) -> Option<Self::Item> {
162         let event = self.inner.next();
163         if let Some(Event::Start(Tag::CodeBlock(lang))) = event {
164             if !LangString::parse(&lang).rust {
165                 return Some(Event::Start(Tag::CodeBlock(lang)));
166             }
167         } else {
168             return event;
169         }
170
171         let mut origtext = String::new();
172         for event in &mut self.inner {
173             match event {
174                 Event::End(Tag::CodeBlock(..)) => break,
175                 Event::Text(ref s) => {
176                     origtext.push_str(s);
177                 }
178                 _ => {}
179             }
180         }
181         let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
182         let text = lines.collect::<Vec<&str>>().join("\n");
183         PLAYGROUND.with(|play| {
184             // insert newline to clearly separate it from the
185             // previous block so we can shorten the html output
186             let mut s = String::from("\n");
187             let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
188                 if url.is_empty() {
189                     return None;
190                 }
191                 let test = origtext.lines()
192                     .map(|l| map_line(l).for_code())
193                     .collect::<Vec<&str>>().join("\n");
194                 let krate = krate.as_ref().map(|s| &**s);
195                 let test = test::make_test(&test, krate, false,
196                                            &Default::default());
197                 let channel = if test.contains("#![feature(") {
198                     "&amp;version=nightly"
199                 } else {
200                     ""
201                 };
202                 // These characters don't need to be escaped in a URI.
203                 // FIXME: use a library function for percent encoding.
204                 fn dont_escape(c: u8) -> bool {
205                     (b'a' <= c && c <= b'z') ||
206                     (b'A' <= c && c <= b'Z') ||
207                     (b'0' <= c && c <= b'9') ||
208                     c == b'-' || c == b'_' || c == b'.' ||
209                     c == b'~' || c == b'!' || c == b'\'' ||
210                     c == b'(' || c == b')' || c == b'*'
211                 }
212                 let mut test_escaped = String::new();
213                 for b in test.bytes() {
214                     if dont_escape(b) {
215                         test_escaped.push(char::from(b));
216                     } else {
217                         write!(test_escaped, "%{:02X}", b).unwrap();
218                     }
219                 }
220                 Some(format!(
221                     r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
222                     url, test_escaped, channel
223                 ))
224             });
225             s.push_str(&highlight::render_with_highlighting(
226                         &text,
227                         Some("rust-example-rendered"),
228                         None,
229                         playground_button.as_ref().map(String::as_str)));
230             Some(Event::Html(s.into()))
231         })
232     }
233 }
234
235 /// Make headings links with anchor ids and build up TOC.
236 struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
237     inner: I,
238     toc: Option<&'b mut TocBuilder>,
239     buf: VecDeque<Event<'a>>,
240 }
241
242 impl<'a, 'b, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, I> {
243     fn new(iter: I, toc: Option<&'b mut TocBuilder>) -> Self {
244         HeadingLinks {
245             inner: iter,
246             toc,
247             buf: VecDeque::new(),
248         }
249     }
250 }
251
252 impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, I> {
253     type Item = Event<'a>;
254
255     fn next(&mut self) -> Option<Self::Item> {
256         if let Some(e) = self.buf.pop_front() {
257             return Some(e);
258         }
259
260         let event = self.inner.next();
261         if let Some(Event::Start(Tag::Header(level))) = event {
262             let mut id = String::new();
263             for event in &mut self.inner {
264                 match event {
265                     Event::End(Tag::Header(..)) => break,
266                     Event::Text(ref text) => id.extend(text.chars().filter_map(slugify)),
267                     _ => {},
268                 }
269                 self.buf.push_back(event);
270             }
271             let id = derive_id(id);
272
273             if let Some(ref mut builder) = self.toc {
274                 let mut html_header = String::new();
275                 html::push_html(&mut html_header, self.buf.iter().cloned());
276                 let sec = builder.push(level as u32, html_header, id.clone());
277                 self.buf.push_front(Event::InlineHtml(format!("{} ", sec).into()));
278             }
279
280             self.buf.push_back(Event::InlineHtml(format!("</a></h{}>", level).into()));
281
282             let start_tags = format!("<h{level} id=\"{id}\" class=\"section-header\">\
283                                       <a href=\"#{id}\">",
284                                      id = id,
285                                      level = level);
286             return Some(Event::InlineHtml(start_tags.into()));
287         }
288         event
289     }
290 }
291
292 /// Extracts just the first paragraph.
293 struct SummaryLine<'a, I: Iterator<Item = Event<'a>>> {
294     inner: I,
295     started: bool,
296     depth: u32,
297 }
298
299 impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> {
300     fn new(iter: I) -> Self {
301         SummaryLine {
302             inner: iter,
303             started: false,
304             depth: 0,
305         }
306     }
307 }
308
309 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
310     type Item = Event<'a>;
311
312     fn next(&mut self) -> Option<Self::Item> {
313         if self.started && self.depth == 0 {
314             return None;
315         }
316         if !self.started {
317             self.started = true;
318         }
319         let event = self.inner.next();
320         match event {
321             Some(Event::Start(..)) => self.depth += 1,
322             Some(Event::End(..)) => self.depth -= 1,
323             _ => {}
324         }
325         event
326     }
327 }
328
329 /// Moves all footnote definitions to the end and add back links to the
330 /// references.
331 struct Footnotes<'a, I: Iterator<Item = Event<'a>>> {
332     inner: I,
333     footnotes: HashMap<String, (Vec<Event<'a>>, u16)>,
334 }
335
336 impl<'a, I: Iterator<Item = Event<'a>>> Footnotes<'a, I> {
337     fn new(iter: I) -> Self {
338         Footnotes {
339             inner: iter,
340             footnotes: HashMap::new(),
341         }
342     }
343     fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
344         let new_id = self.footnotes.keys().count() + 1;
345         let key = key.to_owned();
346         self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
347     }
348 }
349
350 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
351     type Item = Event<'a>;
352
353     fn next(&mut self) -> Option<Self::Item> {
354         loop {
355             match self.inner.next() {
356                 Some(Event::FootnoteReference(ref reference)) => {
357                     let entry = self.get_entry(&reference);
358                     let reference = format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}\
359                                              </a></sup>",
360                                             (*entry).1);
361                     return Some(Event::Html(reference.into()));
362                 }
363                 Some(Event::Start(Tag::FootnoteDefinition(def))) => {
364                     let mut content = Vec::new();
365                     for event in &mut self.inner {
366                         if let Event::End(Tag::FootnoteDefinition(..)) = event {
367                             break;
368                         }
369                         content.push(event);
370                     }
371                     let entry = self.get_entry(&def);
372                     (*entry).0 = content;
373                 }
374                 Some(e) => return Some(e),
375                 None => {
376                     if !self.footnotes.is_empty() {
377                         let mut v: Vec<_> = self.footnotes.drain().map(|(_, x)| x).collect();
378                         v.sort_by(|a, b| a.1.cmp(&b.1));
379                         let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
380                         for (mut content, id) in v {
381                             write!(ret, "<li id=\"ref{}\">", id).unwrap();
382                             let mut is_paragraph = false;
383                             if let Some(&Event::End(Tag::Paragraph)) = content.last() {
384                                 content.pop();
385                                 is_paragraph = true;
386                             }
387                             html::push_html(&mut ret, content.into_iter());
388                             write!(ret,
389                                    "&nbsp;<a href=\"#supref{}\" rev=\"footnote\">↩</a>",
390                                    id).unwrap();
391                             if is_paragraph {
392                                 ret.push_str("</p>");
393                             }
394                             ret.push_str("</li>");
395                         }
396                         ret.push_str("</ol></div>");
397                         return Some(Event::Html(ret.into()));
398                     } else {
399                         return None;
400                     }
401                 }
402             }
403         }
404     }
405 }
406
407 const DEF_OUNIT: libc::size_t = 64;
408 const HOEDOWN_EXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 11;
409 const HOEDOWN_EXT_TABLES: libc::c_uint = 1 << 0;
410 const HOEDOWN_EXT_FENCED_CODE: libc::c_uint = 1 << 1;
411 const HOEDOWN_EXT_AUTOLINK: libc::c_uint = 1 << 3;
412 const HOEDOWN_EXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
413 const HOEDOWN_EXT_SUPERSCRIPT: libc::c_uint = 1 << 8;
414 const HOEDOWN_EXT_FOOTNOTES: libc::c_uint = 1 << 2;
415 const HOEDOWN_HTML_ESCAPE: libc::c_uint = 1 << 1;
416
417 const HOEDOWN_EXTENSIONS: libc::c_uint =
418     HOEDOWN_EXT_NO_INTRA_EMPHASIS | HOEDOWN_EXT_TABLES |
419     HOEDOWN_EXT_FENCED_CODE | HOEDOWN_EXT_AUTOLINK |
420     HOEDOWN_EXT_STRIKETHROUGH | HOEDOWN_EXT_SUPERSCRIPT |
421     HOEDOWN_EXT_FOOTNOTES;
422
423 enum hoedown_document {}
424
425 type blockcodefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
426                                  *const hoedown_buffer, *const hoedown_renderer_data,
427                                  libc::size_t);
428
429 type blockquotefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
430                                   *const hoedown_renderer_data, libc::size_t);
431
432 type headerfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
433                               libc::c_int, *const hoedown_renderer_data,
434                               libc::size_t);
435
436 type blockhtmlfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
437                                  *const hoedown_renderer_data, libc::size_t);
438
439 type codespanfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
440                                 *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
441
442 type linkfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
443                              *const hoedown_buffer, *const hoedown_buffer,
444                              *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
445
446 type entityfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
447                                *const hoedown_renderer_data, libc::size_t);
448
449 type normaltextfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
450                                   *const hoedown_renderer_data, libc::size_t);
451
452 #[repr(C)]
453 struct hoedown_renderer_data {
454     opaque: *mut libc::c_void,
455 }
456
457 #[repr(C)]
458 struct hoedown_renderer {
459     opaque: *mut libc::c_void,
460
461     blockcode: Option<blockcodefn>,
462     blockquote: Option<blockquotefn>,
463     header: Option<headerfn>,
464
465     other_block_level_callbacks: [libc::size_t; 11],
466
467     blockhtml: Option<blockhtmlfn>,
468
469     /* span level callbacks - NULL or return 0 prints the span verbatim */
470     autolink: libc::size_t, // unused
471     codespan: Option<codespanfn>,
472     other_span_level_callbacks_1: [libc::size_t; 7],
473     link: Option<linkfn>,
474     other_span_level_callbacks_2: [libc::size_t; 6],
475
476     /* low level callbacks - NULL copies input directly into the output */
477     entity: Option<entityfn>,
478     normal_text: Option<normaltextfn>,
479
480     /* header and footer */
481     other_callbacks: [libc::size_t; 2],
482 }
483
484 #[repr(C)]
485 struct hoedown_html_renderer_state {
486     opaque: *mut libc::c_void,
487     toc_data: html_toc_data,
488     flags: libc::c_uint,
489     link_attributes: Option<extern "C" fn(*mut hoedown_buffer,
490                                           *const hoedown_buffer,
491                                           *const hoedown_renderer_data)>,
492 }
493
494 #[repr(C)]
495 struct html_toc_data {
496     header_count: libc::c_int,
497     current_level: libc::c_int,
498     level_offset: libc::c_int,
499     nesting_level: libc::c_int,
500 }
501
502 #[repr(C)]
503 struct hoedown_buffer {
504     data: *const u8,
505     size: libc::size_t,
506     asize: libc::size_t,
507     unit: libc::size_t,
508 }
509
510 struct MyOpaque {
511     dfltblk: extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
512                            *const hoedown_buffer, *const hoedown_renderer_data,
513                            libc::size_t),
514     toc_builder: Option<TocBuilder>,
515 }
516
517 extern {
518     fn hoedown_html_renderer_new(render_flags: libc::c_uint,
519                                  nesting_level: libc::c_int)
520         -> *mut hoedown_renderer;
521     fn hoedown_html_renderer_free(renderer: *mut hoedown_renderer);
522
523     fn hoedown_document_new(rndr: *const hoedown_renderer,
524                             extensions: libc::c_uint,
525                             max_nesting: libc::size_t) -> *mut hoedown_document;
526     fn hoedown_document_render(doc: *mut hoedown_document,
527                                ob: *mut hoedown_buffer,
528                                document: *const u8,
529                                doc_size: libc::size_t);
530     fn hoedown_document_free(md: *mut hoedown_document);
531
532     fn hoedown_buffer_new(unit: libc::size_t) -> *mut hoedown_buffer;
533     fn hoedown_buffer_free(b: *mut hoedown_buffer);
534     fn hoedown_buffer_put(b: *mut hoedown_buffer, c: *const u8, len: libc::size_t);
535 }
536
537 impl hoedown_buffer {
538     fn as_bytes(&self) -> &[u8] {
539         unsafe { slice::from_raw_parts(self.data, self.size as usize) }
540     }
541 }
542
543 pub fn render(w: &mut fmt::Formatter,
544               s: &str,
545               print_toc: bool,
546               html_flags: libc::c_uint) -> fmt::Result {
547     extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
548                     lang: *const hoedown_buffer, data: *const hoedown_renderer_data,
549                     line: libc::size_t) {
550         unsafe {
551             if orig_text.is_null() { return }
552
553             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
554             let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque);
555             let text = (*orig_text).as_bytes();
556             let origtext = str::from_utf8(text).unwrap();
557             let origtext = origtext.trim_left();
558             debug!("docblock: ==============\n{:?}\n=======", text);
559             let rendered = if lang.is_null() || origtext.is_empty() {
560                 false
561             } else {
562                 let rlang = (*lang).as_bytes();
563                 let rlang = str::from_utf8(rlang).unwrap();
564                 if !LangString::parse(rlang).rust {
565                     (my_opaque.dfltblk)(ob, orig_text, lang,
566                                         opaque as *const hoedown_renderer_data,
567                                         line);
568                     true
569                 } else {
570                     false
571                 }
572             };
573
574             let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
575             let text = lines.collect::<Vec<&str>>().join("\n");
576             if rendered { return }
577             PLAYGROUND.with(|play| {
578                 // insert newline to clearly separate it from the
579                 // previous block so we can shorten the html output
580                 let mut s = String::from("\n");
581                 let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
582                     if url.is_empty() {
583                         return None;
584                     }
585                     let test = origtext.lines()
586                         .map(|l| map_line(l).for_code())
587                         .collect::<Vec<&str>>().join("\n");
588                     let krate = krate.as_ref().map(|s| &**s);
589                     let test = test::make_test(&test, krate, false,
590                                                &Default::default());
591                     let channel = if test.contains("#![feature(") {
592                         "&amp;version=nightly"
593                     } else {
594                         ""
595                     };
596                     // These characters don't need to be escaped in a URI.
597                     // FIXME: use a library function for percent encoding.
598                     fn dont_escape(c: u8) -> bool {
599                         (b'a' <= c && c <= b'z') ||
600                         (b'A' <= c && c <= b'Z') ||
601                         (b'0' <= c && c <= b'9') ||
602                         c == b'-' || c == b'_' || c == b'.' ||
603                         c == b'~' || c == b'!' || c == b'\'' ||
604                         c == b'(' || c == b')' || c == b'*'
605                     }
606                     let mut test_escaped = String::new();
607                     for b in test.bytes() {
608                         if dont_escape(b) {
609                             test_escaped.push(char::from(b));
610                         } else {
611                             write!(test_escaped, "%{:02X}", b).unwrap();
612                         }
613                     }
614                     Some(format!(
615                         r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
616                         url, test_escaped, channel
617                     ))
618                 });
619                 s.push_str(&highlight::render_with_highlighting(
620                                &text,
621                                Some("rust-example-rendered"),
622                                None,
623                                playground_button.as_ref().map(String::as_str)));
624                 hoedown_buffer_put(ob, s.as_ptr(), s.len());
625             })
626         }
627     }
628
629     extern fn header(ob: *mut hoedown_buffer, text: *const hoedown_buffer,
630                      level: libc::c_int, data: *const hoedown_renderer_data,
631                      _: libc::size_t) {
632         // hoedown does this, we may as well too
633         unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); }
634
635         // Extract the text provided
636         let s = if text.is_null() {
637             "".to_owned()
638         } else {
639             let s = unsafe { (*text).as_bytes() };
640             str::from_utf8(&s).unwrap().to_owned()
641         };
642
643         // Discard '<em>', '<code>' tags and some escaped characters,
644         // transform the contents of the header into a hyphenated string
645         // without non-alphanumeric characters other than '-' and '_'.
646         //
647         // This is a terrible hack working around how hoedown gives us rendered
648         // html for text rather than the raw text.
649         let mut id = s.clone();
650         let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
651                             "<strong>", "</strong>",
652                             "&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
653         for sub in repl_sub {
654             id = id.replace(sub, "");
655         }
656         let id = id.chars().filter_map(|c| {
657             if c.is_alphanumeric() || c == '-' || c == '_' {
658                 if c.is_ascii() {
659                     Some(c.to_ascii_lowercase())
660                 } else {
661                     Some(c)
662                 }
663             } else if c.is_whitespace() && c.is_ascii() {
664                 Some('-')
665             } else {
666                 None
667             }
668         }).collect::<String>();
669
670         let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
671         let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
672
673         let id = derive_id(id);
674
675         let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
676             format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
677         });
678
679         // Render the HTML
680         let text = format!("<h{lvl} id='{id}' class='section-header'>\
681                            <a href='#{id}'>{sec}{}</a></h{lvl}>",
682                            s, lvl = level, id = id, sec = sec);
683
684         unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); }
685     }
686
687     extern fn codespan(
688         ob: *mut hoedown_buffer,
689         text: *const hoedown_buffer,
690         _: *const hoedown_renderer_data,
691         _: libc::size_t
692     ) -> libc::c_int {
693         let content = if text.is_null() {
694             "".to_owned()
695         } else {
696             let bytes = unsafe { (*text).as_bytes() };
697             let s = str::from_utf8(bytes).unwrap();
698             collapse_whitespace(s)
699         };
700
701         let content = format!("<code>{}</code>", Escape(&content));
702         unsafe {
703             hoedown_buffer_put(ob, content.as_ptr(), content.len());
704         }
705         // Return anything except 0, which would mean "also print the code span verbatim".
706         1
707     }
708
709     unsafe {
710         let ob = hoedown_buffer_new(DEF_OUNIT);
711         let renderer = hoedown_html_renderer_new(html_flags, 0);
712         let mut opaque = MyOpaque {
713             dfltblk: (*renderer).blockcode.unwrap(),
714             toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
715         };
716         (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
717                 = &mut opaque as *mut _ as *mut libc::c_void;
718         (*renderer).blockcode = Some(block);
719         (*renderer).header = Some(header);
720         (*renderer).codespan = Some(codespan);
721
722         let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
723         hoedown_document_render(document, ob, s.as_ptr(),
724                                 s.len() as libc::size_t);
725         hoedown_document_free(document);
726
727         hoedown_html_renderer_free(renderer);
728
729         let mut ret = opaque.toc_builder.map_or(Ok(()), |builder| {
730             write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
731         });
732
733         if ret.is_ok() {
734             let buf = (*ob).as_bytes();
735             ret = w.write_str(str::from_utf8(buf).unwrap());
736         }
737         hoedown_buffer_free(ob);
738         ret
739     }
740 }
741
742 pub fn old_find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
743     extern fn block(_ob: *mut hoedown_buffer,
744                     text: *const hoedown_buffer,
745                     lang: *const hoedown_buffer,
746                     data: *const hoedown_renderer_data,
747                     line: libc::size_t) {
748         unsafe {
749             if text.is_null() { return }
750             let block_info = if lang.is_null() {
751                 LangString::all_false()
752             } else {
753                 let lang = (*lang).as_bytes();
754                 let s = str::from_utf8(lang).unwrap();
755                 LangString::parse(s)
756             };
757             if !block_info.rust { return }
758             let text = (*text).as_bytes();
759             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
760             let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
761             let text = str::from_utf8(text).unwrap();
762             let lines = text.lines().map(|l| map_line(l).for_code());
763             let text = lines.collect::<Vec<&str>>().join("\n");
764             let filename = tests.get_filename();
765
766             if tests.render_type == RenderType::Hoedown {
767                 let line = tests.get_line() + line;
768                 tests.add_test(text.to_owned(),
769                                block_info.should_panic, block_info.no_run,
770                                block_info.ignore, block_info.test_harness,
771                                block_info.compile_fail, block_info.error_codes,
772                                line, filename, block_info.allow_fail);
773             } else {
774                 tests.add_old_test(text, filename);
775             }
776         }
777     }
778
779     extern fn header(_ob: *mut hoedown_buffer,
780                      text: *const hoedown_buffer,
781                      level: libc::c_int, data: *const hoedown_renderer_data,
782                      _: libc::size_t) {
783         unsafe {
784             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
785             let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
786             if text.is_null() {
787                 tests.register_header("", level as u32);
788             } else {
789                 let text = (*text).as_bytes();
790                 let text = str::from_utf8(text).unwrap();
791                 tests.register_header(text, level as u32);
792             }
793         }
794     }
795
796     tests.set_position(position);
797     unsafe {
798         let ob = hoedown_buffer_new(DEF_OUNIT);
799         let renderer = hoedown_html_renderer_new(0, 0);
800         (*renderer).blockcode = Some(block);
801         (*renderer).header = Some(header);
802         (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
803                 = tests as *mut _ as *mut libc::c_void;
804
805         let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
806         hoedown_document_render(document, ob, doc.as_ptr(),
807                                 doc.len() as libc::size_t);
808         hoedown_document_free(document);
809
810         hoedown_html_renderer_free(renderer);
811         hoedown_buffer_free(ob);
812     }
813 }
814
815 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
816     tests.set_position(position);
817
818     let mut parser = Parser::new(doc);
819     let mut prev_offset = 0;
820     let mut nb_lines = 0;
821     let mut register_header = None;
822     'main: while let Some(event) = parser.next() {
823         match event {
824             Event::Start(Tag::CodeBlock(s)) => {
825                 let block_info = if s.is_empty() {
826                     LangString::all_false()
827                 } else {
828                     LangString::parse(&*s)
829                 };
830                 if !block_info.rust {
831                     continue
832                 }
833                 let mut test_s = String::new();
834                 let mut offset = None;
835                 loop {
836                     let event = parser.next();
837                     if let Some(event) = event {
838                         match event {
839                             Event::End(Tag::CodeBlock(_)) => break,
840                             Event::Text(ref s) => {
841                                 test_s.push_str(s);
842                                 if offset.is_none() {
843                                     offset = Some(parser.get_offset());
844                                 }
845                             }
846                             _ => {}
847                         }
848                     } else {
849                         break 'main;
850                     }
851                 }
852                 let offset = offset.unwrap_or(0);
853                 let lines = test_s.lines().map(|l| map_line(l).for_code());
854                 let text = lines.collect::<Vec<&str>>().join("\n");
855                 nb_lines += doc[prev_offset..offset].lines().count();
856                 let line = tests.get_line() + (nb_lines - 1);
857                 let filename = tests.get_filename();
858                 tests.add_test(text.to_owned(),
859                                block_info.should_panic, block_info.no_run,
860                                block_info.ignore, block_info.test_harness,
861                                block_info.compile_fail, block_info.error_codes,
862                                line, filename, block_info.allow_fail);
863                 prev_offset = offset;
864             }
865             Event::Start(Tag::Header(level)) => {
866                 register_header = Some(level as u32);
867             }
868             Event::Text(ref s) if register_header.is_some() => {
869                 let level = register_header.unwrap();
870                 if s.is_empty() {
871                     tests.register_header("", level);
872                 } else {
873                     tests.register_header(s, level);
874                 }
875                 register_header = None;
876             }
877             _ => {}
878         }
879     }
880 }
881
882 #[derive(Eq, PartialEq, Clone, Debug)]
883 struct LangString {
884     original: String,
885     should_panic: bool,
886     no_run: bool,
887     ignore: bool,
888     rust: bool,
889     test_harness: bool,
890     compile_fail: bool,
891     error_codes: Vec<String>,
892     allow_fail: bool,
893 }
894
895 impl LangString {
896     fn all_false() -> LangString {
897         LangString {
898             original: String::new(),
899             should_panic: false,
900             no_run: false,
901             ignore: false,
902             rust: true,  // NB This used to be `notrust = false`
903             test_harness: false,
904             compile_fail: false,
905             error_codes: Vec::new(),
906             allow_fail: false,
907         }
908     }
909
910     fn parse(string: &str) -> LangString {
911         let mut seen_rust_tags = false;
912         let mut seen_other_tags = false;
913         let mut data = LangString::all_false();
914         let mut allow_compile_fail = false;
915         let mut allow_error_code_check = false;
916         if UnstableFeatures::from_environment().is_nightly_build() {
917             allow_compile_fail = true;
918             allow_error_code_check = true;
919         }
920
921         data.original = string.to_owned();
922         let tokens = string.split(|c: char|
923             !(c == '_' || c == '-' || c.is_alphanumeric())
924         );
925
926         for token in tokens {
927             match token.trim() {
928                 "" => {},
929                 "should_panic" => {
930                     data.should_panic = true;
931                     seen_rust_tags = seen_other_tags == false;
932                 }
933                 "no_run" => { data.no_run = true; seen_rust_tags = !seen_other_tags; }
934                 "ignore" => { data.ignore = true; seen_rust_tags = !seen_other_tags; }
935                 "allow_fail" => { data.allow_fail = true; seen_rust_tags = !seen_other_tags; }
936                 "rust" => { data.rust = true; seen_rust_tags = true; }
937                 "test_harness" => {
938                     data.test_harness = true;
939                     seen_rust_tags = !seen_other_tags || seen_rust_tags;
940                 }
941                 "compile_fail" if allow_compile_fail => {
942                     data.compile_fail = true;
943                     seen_rust_tags = !seen_other_tags || seen_rust_tags;
944                     data.no_run = true;
945                 }
946                 x if allow_error_code_check && x.starts_with("E") && x.len() == 5 => {
947                     if let Ok(_) = x[1..].parse::<u32>() {
948                         data.error_codes.push(x.to_owned());
949                         seen_rust_tags = !seen_other_tags || seen_rust_tags;
950                     } else {
951                         seen_other_tags = true;
952                     }
953                 }
954                 _ => { seen_other_tags = true }
955             }
956         }
957
958         data.rust &= !seen_other_tags || seen_rust_tags;
959
960         data
961     }
962 }
963
964 impl<'a> fmt::Display for Markdown<'a> {
965     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
966         let Markdown(md, render_type) = *self;
967
968         // This is actually common enough to special-case
969         if md.is_empty() { return Ok(()) }
970         if render_type == RenderType::Hoedown {
971             render(fmt, md, false, 0)
972         } else {
973             let mut opts = Options::empty();
974             opts.insert(OPTION_ENABLE_TABLES);
975             opts.insert(OPTION_ENABLE_FOOTNOTES);
976
977             let p = Parser::new_ext(md, opts);
978
979             let mut s = String::with_capacity(md.len() * 3 / 2);
980
981             html::push_html(&mut s,
982                             Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
983
984             fmt.write_str(&s)
985         }
986     }
987 }
988
989 impl<'a> fmt::Display for MarkdownWithToc<'a> {
990     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
991         let MarkdownWithToc(md, render_type) = *self;
992
993         if render_type == RenderType::Hoedown {
994             render(fmt, md, true, 0)
995         } else {
996             let mut opts = Options::empty();
997             opts.insert(OPTION_ENABLE_TABLES);
998             opts.insert(OPTION_ENABLE_FOOTNOTES);
999
1000             let p = Parser::new_ext(md, opts);
1001
1002             let mut s = String::with_capacity(md.len() * 3 / 2);
1003
1004             let mut toc = TocBuilder::new();
1005
1006             html::push_html(&mut s,
1007                             Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, Some(&mut toc)))));
1008
1009             write!(fmt, "<nav id=\"TOC\">{}</nav>", toc.into_toc())?;
1010
1011             fmt.write_str(&s)
1012         }
1013     }
1014 }
1015
1016 impl<'a> fmt::Display for MarkdownHtml<'a> {
1017     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1018         let MarkdownHtml(md, render_type) = *self;
1019
1020         // This is actually common enough to special-case
1021         if md.is_empty() { return Ok(()) }
1022         if render_type == RenderType::Hoedown {
1023             render(fmt, md, false, HOEDOWN_HTML_ESCAPE)
1024         } else {
1025             let mut opts = Options::empty();
1026             opts.insert(OPTION_ENABLE_TABLES);
1027             opts.insert(OPTION_ENABLE_FOOTNOTES);
1028
1029             let p = Parser::new_ext(md, opts);
1030
1031             // Treat inline HTML as plain text.
1032             let p = p.map(|event| match event {
1033                 Event::Html(text) | Event::InlineHtml(text) => Event::Text(text),
1034                 _ => event
1035             });
1036
1037             let mut s = String::with_capacity(md.len() * 3 / 2);
1038
1039             html::push_html(&mut s,
1040                             Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
1041
1042             fmt.write_str(&s)
1043         }
1044     }
1045 }
1046
1047 impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
1048     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1049         let MarkdownSummaryLine(md) = *self;
1050         // This is actually common enough to special-case
1051         if md.is_empty() { return Ok(()) }
1052
1053         let p = Parser::new(md);
1054
1055         let mut s = String::new();
1056
1057         html::push_html(&mut s, SummaryLine::new(p));
1058
1059         fmt.write_str(&s)
1060     }
1061 }
1062
1063 pub fn plain_summary_line(md: &str) -> String {
1064     struct ParserWrapper<'a> {
1065         inner: Parser<'a>,
1066         is_in: isize,
1067         is_first: bool,
1068     }
1069
1070     impl<'a> Iterator for ParserWrapper<'a> {
1071         type Item = String;
1072
1073         fn next(&mut self) -> Option<String> {
1074             let next_event = self.inner.next();
1075             if next_event.is_none() {
1076                 return None
1077             }
1078             let next_event = next_event.unwrap();
1079             let (ret, is_in) = match next_event {
1080                 Event::Start(Tag::Paragraph) => (None, 1),
1081                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
1082                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
1083                 Event::Start(Tag::Header(_)) => (None, 1),
1084                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
1085                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
1086                 _ => (None, 0),
1087             };
1088             if is_in > 0 || (is_in < 0 && self.is_in > 0) {
1089                 self.is_in += is_in;
1090             }
1091             if ret.is_some() {
1092                 self.is_first = false;
1093                 ret
1094             } else {
1095                 Some(String::new())
1096             }
1097         }
1098     }
1099     let mut s = String::with_capacity(md.len() * 3 / 2);
1100     let mut p = ParserWrapper {
1101         inner: Parser::new(md),
1102         is_in: 0,
1103         is_first: true,
1104     };
1105     while let Some(t) = p.next() {
1106         if !t.is_empty() {
1107             s.push_str(&t);
1108         }
1109     }
1110     s
1111 }
1112
1113 #[cfg(test)]
1114 mod tests {
1115     use super::{LangString, Markdown, MarkdownHtml};
1116     use super::plain_summary_line;
1117     use super::RenderType;
1118     use html::render::reset_ids;
1119
1120     #[test]
1121     fn test_lang_string_parse() {
1122         fn t(s: &str,
1123             should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
1124             compile_fail: bool, allow_fail: bool, error_codes: Vec<String>) {
1125             assert_eq!(LangString::parse(s), LangString {
1126                 should_panic,
1127                 no_run,
1128                 ignore,
1129                 rust,
1130                 test_harness,
1131                 compile_fail,
1132                 error_codes,
1133                 original: s.to_owned(),
1134                 allow_fail,
1135             })
1136         }
1137
1138         fn v() -> Vec<String> {
1139             Vec::new()
1140         }
1141
1142         // marker                | should_panic| no_run| ignore| rust | test_harness| compile_fail
1143         //                       | allow_fail | error_codes
1144         t("",                      false,        false,  false,  true,  false, false, false, v());
1145         t("rust",                  false,        false,  false,  true,  false, false, false, v());
1146         t("sh",                    false,        false,  false,  false, false, false, false, v());
1147         t("ignore",                false,        false,  true,   true,  false, false, false, v());
1148         t("should_panic",          true,         false,  false,  true,  false, false, false, v());
1149         t("no_run",                false,        true,   false,  true,  false, false, false, v());
1150         t("test_harness",          false,        false,  false,  true,  true,  false, false, v());
1151         t("compile_fail",          false,        true,   false,  true,  false, true,  false, v());
1152         t("allow_fail",            false,        false,  false,  true,  false, false, true,  v());
1153         t("{.no_run .example}",    false,        true,   false,  true,  false, false, false, v());
1154         t("{.sh .should_panic}",   true,         false,  false,  false, false, false, false, v());
1155         t("{.example .rust}",      false,        false,  false,  true,  false, false, false, v());
1156         t("{.test_harness .rust}", false,        false,  false,  true,  true,  false, false, v());
1157         t("text, no_run",          false,        true,   false,  false, false, false, false, v());
1158         t("text,no_run",           false,        true,   false,  false, false, false, false, v());
1159     }
1160
1161     #[test]
1162     fn issue_17736() {
1163         let markdown = "# title";
1164         format!("{}", Markdown(markdown, RenderType::Pulldown));
1165         reset_ids(true);
1166     }
1167
1168     #[test]
1169     fn test_header() {
1170         fn t(input: &str, expect: &str) {
1171             let output = format!("{}", Markdown(input, RenderType::Pulldown));
1172             assert_eq!(output, expect, "original: {}", input);
1173             reset_ids(true);
1174         }
1175
1176         t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
1177           <a href=\"#foo-bar\">Foo bar</a></h1>");
1178         t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
1179           header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
1180         t("### **Foo** *bar* baz!?!& -_qux_-%",
1181           "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
1182           <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
1183           <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
1184         t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
1185           "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
1186           <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
1187           <em><code>baz</code></em> ❤ #qux</a></h4>");
1188     }
1189
1190     #[test]
1191     fn test_header_ids_multiple_blocks() {
1192         fn t(input: &str, expect: &str) {
1193             let output = format!("{}", Markdown(input, RenderType::Pulldown));
1194             assert_eq!(output, expect, "original: {}", input);
1195         }
1196
1197         let test = || {
1198             t("# Example", "<h1 id=\"example\" class=\"section-header\">\
1199               <a href=\"#example\">Example</a></h1>");
1200             t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
1201               <a href=\"#panics\">Panics</a></h1>");
1202             t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
1203               <a href=\"#example-1\">Example</a></h1>");
1204             t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
1205               <a href=\"#main-1\">Main</a></h1>");
1206             t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
1207               <a href=\"#example-2\">Example</a></h1>");
1208             t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
1209               <a href=\"#panics-1\">Panics</a></h1>");
1210         };
1211         test();
1212         reset_ids(true);
1213         test();
1214     }
1215
1216     #[test]
1217     fn test_plain_summary_line() {
1218         fn t(input: &str, expect: &str) {
1219             let output = plain_summary_line(input);
1220             assert_eq!(output, expect, "original: {}", input);
1221         }
1222
1223         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
1224         t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
1225         t("code `let x = i32;` ...", "code `let x = i32;` ...");
1226         t("type `Type<'static>` ...", "type `Type<'static>` ...");
1227         t("# top header", "top header");
1228         t("## header", "header");
1229     }
1230
1231     #[test]
1232     fn test_markdown_html_escape() {
1233         fn t(input: &str, expect: &str) {
1234             let output = format!("{}", MarkdownHtml(input, RenderType::Pulldown));
1235             assert_eq!(output, expect, "original: {}", input);
1236         }
1237
1238         t("`Struct<'a, T>`", "<p><code>Struct&lt;'a, T&gt;</code></p>\n");
1239         t("Struct<'a, T>", "<p>Struct&lt;'a, T&gt;</p>\n");
1240         t("Struct<br>", "<p>Struct&lt;br&gt;</p>\n");
1241     }
1242 }