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