]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
dce0c4b001a0d94b86909027ca54a3fc0bc8b2f4
[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 a list of link replacements
60 // The third parameter is whether we need a shorter version or not.
61 pub struct Markdown<'a>(pub &'a str, pub &'a [(String, String)], 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, pub &'a [(String, String)]);
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         let compile_fail;
164         let ignore;
165         if let Some(Event::Start(Tag::CodeBlock(lang))) = event {
166             let parse_result = LangString::parse(&lang);
167             if !parse_result.rust {
168                 return Some(Event::Start(Tag::CodeBlock(lang)));
169             }
170             compile_fail = parse_result.compile_fail;
171             ignore = parse_result.ignore;
172         } else {
173             return event;
174         }
175
176         let mut origtext = String::new();
177         for event in &mut self.inner {
178             match event {
179                 Event::End(Tag::CodeBlock(..)) => break,
180                 Event::Text(ref s) => {
181                     origtext.push_str(s);
182                 }
183                 _ => {}
184             }
185         }
186         let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
187         let text = lines.collect::<Vec<&str>>().join("\n");
188         PLAYGROUND.with(|play| {
189             // insert newline to clearly separate it from the
190             // previous block so we can shorten the html output
191             let mut s = String::from("\n");
192             let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
193                 if url.is_empty() {
194                     return None;
195                 }
196                 let test = origtext.lines()
197                     .map(|l| map_line(l).for_code())
198                     .collect::<Vec<&str>>().join("\n");
199                 let krate = krate.as_ref().map(|s| &**s);
200                 let (test, _) = test::make_test(&test, krate, false,
201                                            &Default::default());
202                 let channel = if test.contains("#![feature(") {
203                     "&amp;version=nightly"
204                 } else {
205                     ""
206                 };
207                 // These characters don't need to be escaped in a URI.
208                 // FIXME: use a library function for percent encoding.
209                 fn dont_escape(c: u8) -> bool {
210                     (b'a' <= c && c <= b'z') ||
211                     (b'A' <= c && c <= b'Z') ||
212                     (b'0' <= c && c <= b'9') ||
213                     c == b'-' || c == b'_' || c == b'.' ||
214                     c == b'~' || c == b'!' || c == b'\'' ||
215                     c == b'(' || c == b')' || c == b'*'
216                 }
217                 let mut test_escaped = String::new();
218                 for b in test.bytes() {
219                     if dont_escape(b) {
220                         test_escaped.push(char::from(b));
221                     } else {
222                         write!(test_escaped, "%{:02X}", b).unwrap();
223                     }
224                 }
225                 Some(format!(
226                     r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
227                     url, test_escaped, channel
228                 ))
229             });
230             let tooltip = if ignore {
231                 Some(("This example is not tested", "ignore"))
232             } else if compile_fail {
233                 Some(("This example deliberately fails to compile", "compile_fail"))
234             } else {
235                 None
236             };
237             s.push_str(&highlight::render_with_highlighting(
238                         &text,
239                         Some(&format!("rust-example-rendered{}",
240                                       if ignore { " ignore" }
241                                       else if compile_fail { " compile_fail" }
242                                       else { "" })),
243                         None,
244                         playground_button.as_ref().map(String::as_str),
245                         tooltip));
246             Some(Event::Html(s.into()))
247         })
248     }
249 }
250
251 /// Make headings links with anchor ids and build up TOC.
252 struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
253     inner: I,
254     links: &'b [(String, String)]
255 }
256
257 impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
258     fn new(iter: I, links: &'b [(String, String)]) -> Self {
259         LinkReplacer {
260             inner: iter,
261             links
262         }
263     }
264 }
265
266 impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
267     type Item = Event<'a>;
268
269     fn next(&mut self) -> Option<Self::Item> {
270         let event = self.inner.next();
271         if let Some(Event::Start(Tag::Link(dest, text))) = event {
272             if let Some(&(_, ref replace)) = self.links.into_iter().find(|link| &*link.0 == &*dest)
273             {
274                 Some(Event::Start(Tag::Link(replace.to_owned().into(), text)))
275             } else {
276                 Some(Event::Start(Tag::Link(dest, text)))
277             }
278         } else {
279             event
280         }
281     }
282 }
283
284 /// Make headings links with anchor ids and build up TOC.
285 struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
286     inner: I,
287     toc: Option<&'b mut TocBuilder>,
288     buf: VecDeque<Event<'a>>,
289 }
290
291 impl<'a, 'b, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, I> {
292     fn new(iter: I, toc: Option<&'b mut TocBuilder>) -> Self {
293         HeadingLinks {
294             inner: iter,
295             toc,
296             buf: VecDeque::new(),
297         }
298     }
299 }
300
301 impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, I> {
302     type Item = Event<'a>;
303
304     fn next(&mut self) -> Option<Self::Item> {
305         if let Some(e) = self.buf.pop_front() {
306             return Some(e);
307         }
308
309         let event = self.inner.next();
310         if let Some(Event::Start(Tag::Header(level))) = event {
311             let mut id = String::new();
312             for event in &mut self.inner {
313                 match event {
314                     Event::End(Tag::Header(..)) => break,
315                     Event::Text(ref text) => id.extend(text.chars().filter_map(slugify)),
316                     _ => {},
317                 }
318                 self.buf.push_back(event);
319             }
320             let id = derive_id(id);
321
322             if let Some(ref mut builder) = self.toc {
323                 let mut html_header = String::new();
324                 html::push_html(&mut html_header, self.buf.iter().cloned());
325                 let sec = builder.push(level as u32, html_header, id.clone());
326                 self.buf.push_front(Event::InlineHtml(format!("{} ", sec).into()));
327             }
328
329             self.buf.push_back(Event::InlineHtml(format!("</a></h{}>", level).into()));
330
331             let start_tags = format!("<h{level} id=\"{id}\" class=\"section-header\">\
332                                       <a href=\"#{id}\">",
333                                      id = id,
334                                      level = level);
335             return Some(Event::InlineHtml(start_tags.into()));
336         }
337         event
338     }
339 }
340
341 /// Extracts just the first paragraph.
342 struct SummaryLine<'a, I: Iterator<Item = Event<'a>>> {
343     inner: I,
344     started: bool,
345     depth: u32,
346 }
347
348 impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> {
349     fn new(iter: I) -> Self {
350         SummaryLine {
351             inner: iter,
352             started: false,
353             depth: 0,
354         }
355     }
356 }
357
358 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
359     type Item = Event<'a>;
360
361     fn next(&mut self) -> Option<Self::Item> {
362         if self.started && self.depth == 0 {
363             return None;
364         }
365         if !self.started {
366             self.started = true;
367         }
368         let event = self.inner.next();
369         match event {
370             Some(Event::Start(..)) => self.depth += 1,
371             Some(Event::End(..)) => self.depth -= 1,
372             _ => {}
373         }
374         event
375     }
376 }
377
378 /// Moves all footnote definitions to the end and add back links to the
379 /// references.
380 struct Footnotes<'a, I: Iterator<Item = Event<'a>>> {
381     inner: I,
382     footnotes: HashMap<String, (Vec<Event<'a>>, u16)>,
383 }
384
385 impl<'a, I: Iterator<Item = Event<'a>>> Footnotes<'a, I> {
386     fn new(iter: I) -> Self {
387         Footnotes {
388             inner: iter,
389             footnotes: HashMap::new(),
390         }
391     }
392     fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
393         let new_id = self.footnotes.keys().count() + 1;
394         let key = key.to_owned();
395         self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
396     }
397 }
398
399 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
400     type Item = Event<'a>;
401
402     fn next(&mut self) -> Option<Self::Item> {
403         loop {
404             match self.inner.next() {
405                 Some(Event::FootnoteReference(ref reference)) => {
406                     let entry = self.get_entry(&reference);
407                     let reference = format!("<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}\
408                                              </a></sup>",
409                                             (*entry).1);
410                     return Some(Event::Html(reference.into()));
411                 }
412                 Some(Event::Start(Tag::FootnoteDefinition(def))) => {
413                     let mut content = Vec::new();
414                     for event in &mut self.inner {
415                         if let Event::End(Tag::FootnoteDefinition(..)) = event {
416                             break;
417                         }
418                         content.push(event);
419                     }
420                     let entry = self.get_entry(&def);
421                     (*entry).0 = content;
422                 }
423                 Some(e) => return Some(e),
424                 None => {
425                     if !self.footnotes.is_empty() {
426                         let mut v: Vec<_> = self.footnotes.drain().map(|(_, x)| x).collect();
427                         v.sort_by(|a, b| a.1.cmp(&b.1));
428                         let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
429                         for (mut content, id) in v {
430                             write!(ret, "<li id=\"fn{}\">", id).unwrap();
431                             let mut is_paragraph = false;
432                             if let Some(&Event::End(Tag::Paragraph)) = content.last() {
433                                 content.pop();
434                                 is_paragraph = true;
435                             }
436                             html::push_html(&mut ret, content.into_iter());
437                             write!(ret,
438                                    "&nbsp;<a href=\"#fnref{}\" rev=\"footnote\">↩</a>",
439                                    id).unwrap();
440                             if is_paragraph {
441                                 ret.push_str("</p>");
442                             }
443                             ret.push_str("</li>");
444                         }
445                         ret.push_str("</ol></div>");
446                         return Some(Event::Html(ret.into()));
447                     } else {
448                         return None;
449                     }
450                 }
451             }
452         }
453     }
454 }
455
456 const DEF_OUNIT: libc::size_t = 64;
457 const HOEDOWN_EXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 11;
458 const HOEDOWN_EXT_TABLES: libc::c_uint = 1 << 0;
459 const HOEDOWN_EXT_FENCED_CODE: libc::c_uint = 1 << 1;
460 const HOEDOWN_EXT_AUTOLINK: libc::c_uint = 1 << 3;
461 const HOEDOWN_EXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
462 const HOEDOWN_EXT_SUPERSCRIPT: libc::c_uint = 1 << 8;
463 const HOEDOWN_EXT_FOOTNOTES: libc::c_uint = 1 << 2;
464 const HOEDOWN_HTML_ESCAPE: libc::c_uint = 1 << 1;
465
466 const HOEDOWN_EXTENSIONS: libc::c_uint =
467     HOEDOWN_EXT_NO_INTRA_EMPHASIS | HOEDOWN_EXT_TABLES |
468     HOEDOWN_EXT_FENCED_CODE | HOEDOWN_EXT_AUTOLINK |
469     HOEDOWN_EXT_STRIKETHROUGH | HOEDOWN_EXT_SUPERSCRIPT |
470     HOEDOWN_EXT_FOOTNOTES;
471
472 enum hoedown_document {}
473
474 type blockcodefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
475                                  *const hoedown_buffer, *const hoedown_renderer_data,
476                                  libc::size_t);
477
478 type blockquotefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
479                                   *const hoedown_renderer_data, libc::size_t);
480
481 type headerfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
482                               libc::c_int, *const hoedown_renderer_data,
483                               libc::size_t);
484
485 type blockhtmlfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
486                                  *const hoedown_renderer_data, libc::size_t);
487
488 type codespanfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
489                                 *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
490
491 type linkfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
492                              *const hoedown_buffer, *const hoedown_buffer,
493                              *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
494
495 type entityfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
496                                *const hoedown_renderer_data, libc::size_t);
497
498 type normaltextfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
499                                   *const hoedown_renderer_data, libc::size_t);
500
501 #[repr(C)]
502 struct hoedown_renderer_data {
503     opaque: *mut libc::c_void,
504 }
505
506 #[repr(C)]
507 struct hoedown_renderer {
508     opaque: *mut libc::c_void,
509
510     blockcode: Option<blockcodefn>,
511     blockquote: Option<blockquotefn>,
512     header: Option<headerfn>,
513
514     other_block_level_callbacks: [libc::size_t; 11],
515
516     blockhtml: Option<blockhtmlfn>,
517
518     /* span level callbacks - NULL or return 0 prints the span verbatim */
519     autolink: libc::size_t, // unused
520     codespan: Option<codespanfn>,
521     other_span_level_callbacks_1: [libc::size_t; 7],
522     link: Option<linkfn>,
523     other_span_level_callbacks_2: [libc::size_t; 6],
524
525     /* low level callbacks - NULL copies input directly into the output */
526     entity: Option<entityfn>,
527     normal_text: Option<normaltextfn>,
528
529     /* header and footer */
530     other_callbacks: [libc::size_t; 2],
531 }
532
533 #[repr(C)]
534 struct hoedown_html_renderer_state {
535     opaque: *mut libc::c_void,
536     toc_data: html_toc_data,
537     flags: libc::c_uint,
538     link_attributes: Option<extern "C" fn(*mut hoedown_buffer,
539                                           *const hoedown_buffer,
540                                           *const hoedown_renderer_data)>,
541 }
542
543 #[repr(C)]
544 struct html_toc_data {
545     header_count: libc::c_int,
546     current_level: libc::c_int,
547     level_offset: libc::c_int,
548     nesting_level: libc::c_int,
549 }
550
551 #[repr(C)]
552 struct hoedown_buffer {
553     data: *const u8,
554     size: libc::size_t,
555     asize: libc::size_t,
556     unit: libc::size_t,
557 }
558
559 struct MyOpaque {
560     dfltblk: extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
561                            *const hoedown_buffer, *const hoedown_renderer_data,
562                            libc::size_t),
563     toc_builder: Option<TocBuilder>,
564     links_out: Option<Vec<String>>,
565     links_replace: Vec<(String, String)>,
566 }
567
568 extern {
569     fn hoedown_html_renderer_new(render_flags: libc::c_uint,
570                                  nesting_level: libc::c_int)
571         -> *mut hoedown_renderer;
572     fn hoedown_html_renderer_free(renderer: *mut hoedown_renderer);
573
574     fn hoedown_document_new(rndr: *const hoedown_renderer,
575                             extensions: libc::c_uint,
576                             max_nesting: libc::size_t) -> *mut hoedown_document;
577     fn hoedown_document_render(doc: *mut hoedown_document,
578                                ob: *mut hoedown_buffer,
579                                document: *const u8,
580                                doc_size: libc::size_t);
581     fn hoedown_document_free(md: *mut hoedown_document);
582
583     fn hoedown_buffer_new(unit: libc::size_t) -> *mut hoedown_buffer;
584     fn hoedown_buffer_free(b: *mut hoedown_buffer);
585     fn hoedown_buffer_put(b: *mut hoedown_buffer, c: *const u8, len: libc::size_t);
586 }
587
588 impl hoedown_buffer {
589     fn as_bytes(&self) -> &[u8] {
590         unsafe { slice::from_raw_parts(self.data, self.size as usize) }
591     }
592 }
593
594 extern fn hoedown_block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
595                         lang: *const hoedown_buffer, data: *const hoedown_renderer_data,
596                         line: libc::size_t) {
597     unsafe {
598         if orig_text.is_null() { return }
599
600         let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
601         let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque);
602         let text = (*orig_text).as_bytes();
603         let origtext = str::from_utf8(text).unwrap();
604         let origtext = origtext.trim_left();
605         debug!("docblock: ==============\n{:?}\n=======", text);
606         let mut compile_fail = false;
607         let mut ignore = false;
608
609         let rendered = if lang.is_null() || origtext.is_empty() {
610             false
611         } else {
612             let rlang = (*lang).as_bytes();
613             let rlang = str::from_utf8(rlang).unwrap();
614             let parse_result = LangString::parse(rlang);
615             compile_fail = parse_result.compile_fail;
616             ignore = parse_result.ignore;
617             if !parse_result.rust {
618                 (my_opaque.dfltblk)(ob, orig_text, lang,
619                                     opaque as *const hoedown_renderer_data,
620                                     line);
621                 true
622             } else {
623                 false
624             }
625         };
626
627         let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
628         let text = lines.collect::<Vec<&str>>().join("\n");
629         if rendered { return }
630         PLAYGROUND.with(|play| {
631             // insert newline to clearly separate it from the
632             // previous block so we can shorten the html output
633             let mut s = String::from("\n");
634             let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
635                 if url.is_empty() {
636                     return None;
637                 }
638                 let test = origtext.lines()
639                     .map(|l| map_line(l).for_code())
640                     .collect::<Vec<&str>>().join("\n");
641                 let krate = krate.as_ref().map(|s| &**s);
642                 let (test, _) = test::make_test(&test, krate, false,
643                                                 &Default::default());
644                 let channel = if test.contains("#![feature(") {
645                     "&amp;version=nightly"
646                 } else {
647                     ""
648                 };
649                 // These characters don't need to be escaped in a URI.
650                 // FIXME: use a library function for percent encoding.
651                 fn dont_escape(c: u8) -> bool {
652                     (b'a' <= c && c <= b'z') ||
653                     (b'A' <= c && c <= b'Z') ||
654                     (b'0' <= c && c <= b'9') ||
655                     c == b'-' || c == b'_' || c == b'.' ||
656                     c == b'~' || c == b'!' || c == b'\'' ||
657                     c == b'(' || c == b')' || c == b'*'
658                 }
659                 let mut test_escaped = String::new();
660                 for b in test.bytes() {
661                     if dont_escape(b) {
662                         test_escaped.push(char::from(b));
663                     } else {
664                         write!(test_escaped, "%{:02X}", b).unwrap();
665                     }
666                 }
667                 Some(format!(
668                     r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
669                     url, test_escaped, channel
670                 ))
671             });
672             let tooltip = if ignore {
673                 Some(("This example is not tested", "ignore"))
674             } else if compile_fail {
675                 Some(("This example deliberately fails to compile", "compile_fail"))
676             } else {
677                 None
678             };
679             s.push_str(&highlight::render_with_highlighting(
680                            &text,
681                            Some(&format!("rust-example-rendered{}",
682                                          if ignore { " ignore" }
683                                          else if compile_fail { " compile_fail" }
684                                          else { "" })),
685                            None,
686                            playground_button.as_ref().map(String::as_str),
687                            tooltip));
688             hoedown_buffer_put(ob, s.as_ptr(), s.len());
689         })
690     }
691 }
692
693 extern fn hoedown_header(ob: *mut hoedown_buffer, text: *const hoedown_buffer,
694                          level: libc::c_int, data: *const hoedown_renderer_data,
695                          _: libc::size_t) {
696     // hoedown does this, we may as well too
697     unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); }
698
699     // Extract the text provided
700     let s = if text.is_null() {
701         "".to_owned()
702     } else {
703         let s = unsafe { (*text).as_bytes() };
704         str::from_utf8(&s).unwrap().to_owned()
705     };
706
707     // Discard '<em>', '<code>' tags and some escaped characters,
708     // transform the contents of the header into a hyphenated string
709     // without non-alphanumeric characters other than '-' and '_'.
710     //
711     // This is a terrible hack working around how hoedown gives us rendered
712     // html for text rather than the raw text.
713     let mut id = s.clone();
714     let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
715                         "<strong>", "</strong>",
716                         "&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
717     for sub in repl_sub {
718         id = id.replace(sub, "");
719     }
720     let id = id.chars().filter_map(|c| {
721         if c.is_alphanumeric() || c == '-' || c == '_' {
722             if c.is_ascii() {
723                 Some(c.to_ascii_lowercase())
724             } else {
725                 Some(c)
726             }
727         } else if c.is_whitespace() && c.is_ascii() {
728             Some('-')
729         } else {
730             None
731         }
732     }).collect::<String>();
733
734     let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
735     let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
736
737     let id = derive_id(id);
738
739     let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| {
740         format!("{} ", builder.push(level as u32, s.clone(), id.clone()))
741     });
742
743     // Render the HTML
744     let text = format!("<h{lvl} id='{id}' class='section-header'>\
745                        <a href='#{id}'>{sec}{}</a></h{lvl}>",
746                        s, lvl = level, id = id, sec = sec);
747
748     unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); }
749 }
750
751 extern fn hoedown_codespan(
752     ob: *mut hoedown_buffer,
753     text: *const hoedown_buffer,
754     _: *const hoedown_renderer_data,
755     _: libc::size_t
756 ) -> libc::c_int {
757     let content = if text.is_null() {
758         "".to_owned()
759     } else {
760         let bytes = unsafe { (*text).as_bytes() };
761         let s = str::from_utf8(bytes).unwrap();
762         collapse_whitespace(s)
763     };
764
765     let content = format!("<code>{}</code>", Escape(&content));
766     unsafe {
767         hoedown_buffer_put(ob, content.as_ptr(), content.len());
768     }
769     // Return anything except 0, which would mean "also print the code span verbatim".
770     1
771 }
772
773 pub fn render(w: &mut fmt::Formatter,
774               s: &str,
775               links: &[(String, String)],
776               print_toc: bool,
777               html_flags: libc::c_uint) -> fmt::Result {
778     // copied from pulldown-cmark (MIT license, Google)
779     // https://github.com/google/pulldown-cmark
780     // this is temporary till we remove the hoedown renderer
781     static HREF_SAFE: [u8; 128] = [
782             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
783             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
784             0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
785             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
786             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
787             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
788             0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
789             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
790         ];
791
792     static HEX_CHARS: &'static [u8] = b"0123456789ABCDEF";
793
794     fn escape_href(ob: &mut String, s: &str) {
795         let mut mark = 0;
796         for i in 0..s.len() {
797             let c = s.as_bytes()[i];
798             if c >= 0x80 || HREF_SAFE[c as usize] == 0 {
799                 // character needing escape
800
801                 // write partial substring up to mark
802                 if mark < i {
803                     ob.push_str(&s[mark..i]);
804                 }
805                 match c {
806                     b'&' => {
807                         ob.push_str("&amp;");
808                     },
809                     b'\'' => {
810                         ob.push_str("&#x27;");
811                     },
812                     _ => {
813                         let mut buf = [0u8; 3];
814                         buf[0] = b'%';
815                         buf[1] = HEX_CHARS[((c as usize) >> 4) & 0xF];
816                         buf[2] = HEX_CHARS[(c as usize) & 0xF];
817                         ob.push_str(str::from_utf8(&buf).unwrap());
818                     }
819                 }
820                 mark = i + 1;  // all escaped characters are ASCII
821             }
822         }
823         ob.push_str(&s[mark..]);
824     }
825     // end code copied from pulldown-cmark
826
827     extern fn hoedown_link(
828         ob: *mut hoedown_buffer,
829         content: *const hoedown_buffer,
830         link: *const hoedown_buffer,
831         title: *const hoedown_buffer,
832         data: *const hoedown_renderer_data,
833         _line: libc::size_t
834     ) -> libc::c_int {
835         if link.is_null() {
836             return 0;
837         }
838
839         let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
840         let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
841
842         let link = {
843             let s = unsafe { (*link).as_bytes() };
844             str::from_utf8(s).unwrap().to_owned()
845         };
846
847         let link = if let Some(&(_, ref new_target)) = opaque.links_replace
848                                                              .iter()
849                                                              .find(|t| &*t.0 == &*link) {
850             new_target.to_owned()
851         } else {
852             link
853         };
854
855         let content = unsafe {
856             content.as_ref().map(|c| {
857                 let s = c.as_bytes();
858                 str::from_utf8(s).unwrap().to_owned()
859             })
860         };
861
862         let mut link_buf = String::new();
863         escape_href(&mut link_buf, &link);
864
865         let title = unsafe {
866             title.as_ref().map(|t| {
867                 let s = t.as_bytes();
868                 str::from_utf8(s).unwrap().to_owned()
869             })
870         };
871
872         let link_out = format!("<a href=\"{link}\"{title}>{content}</a>",
873                                link = link_buf,
874                                title = title.map_or(String::new(),
875                                                     |t| format!(" title=\"{}\"", t)),
876                                content = content.unwrap_or(String::new()));
877
878         unsafe { hoedown_buffer_put(ob, link_out.as_ptr(), link_out.len()); }
879
880         //return "anything but 0" to show we've written the link in
881         1
882     }
883
884     unsafe {
885         let ob = hoedown_buffer_new(DEF_OUNIT);
886         let renderer = hoedown_html_renderer_new(html_flags, 0);
887         let mut opaque = MyOpaque {
888             dfltblk: (*renderer).blockcode.unwrap(),
889             toc_builder: if print_toc {Some(TocBuilder::new())} else {None},
890             links_out: None,
891             links_replace: links.to_vec(),
892         };
893         (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
894                 = &mut opaque as *mut _ as *mut libc::c_void;
895         (*renderer).blockcode = Some(hoedown_block);
896         (*renderer).header = Some(hoedown_header);
897         (*renderer).codespan = Some(hoedown_codespan);
898         (*renderer).link = Some(hoedown_link);
899
900         let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
901         hoedown_document_render(document, ob, s.as_ptr(),
902                                 s.len() as libc::size_t);
903         hoedown_document_free(document);
904
905         hoedown_html_renderer_free(renderer);
906
907         let mut ret = opaque.toc_builder.map_or(Ok(()), |builder| {
908             write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
909         });
910
911         if ret.is_ok() {
912             let buf = (*ob).as_bytes();
913             ret = w.write_str(str::from_utf8(buf).unwrap());
914         }
915         hoedown_buffer_free(ob);
916         ret
917     }
918 }
919
920 pub fn old_find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
921     extern fn block(_ob: *mut hoedown_buffer,
922                     text: *const hoedown_buffer,
923                     lang: *const hoedown_buffer,
924                     data: *const hoedown_renderer_data,
925                     line: libc::size_t) {
926         unsafe {
927             if text.is_null() { return }
928             let block_info = if lang.is_null() {
929                 LangString::all_false()
930             } else {
931                 let lang = (*lang).as_bytes();
932                 let s = str::from_utf8(lang).unwrap();
933                 LangString::parse(s)
934             };
935             if !block_info.rust { return }
936             let text = (*text).as_bytes();
937             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
938             let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
939             let text = str::from_utf8(text).unwrap();
940             let lines = text.lines().map(|l| map_line(l).for_code());
941             let text = lines.collect::<Vec<&str>>().join("\n");
942             let filename = tests.get_filename();
943
944             if tests.render_type == RenderType::Hoedown {
945                 let line = tests.get_line() + line;
946                 tests.add_test(text.to_owned(),
947                                block_info.should_panic, block_info.no_run,
948                                block_info.ignore, block_info.test_harness,
949                                block_info.compile_fail, block_info.error_codes,
950                                line, filename, block_info.allow_fail);
951             } else {
952                 tests.add_old_test(text, filename);
953             }
954         }
955     }
956
957     extern fn header(_ob: *mut hoedown_buffer,
958                      text: *const hoedown_buffer,
959                      level: libc::c_int, data: *const hoedown_renderer_data,
960                      _: libc::size_t) {
961         unsafe {
962             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
963             let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
964             if text.is_null() {
965                 tests.register_header("", level as u32);
966             } else {
967                 let text = (*text).as_bytes();
968                 let text = str::from_utf8(text).unwrap();
969                 tests.register_header(text, level as u32);
970             }
971         }
972     }
973
974     tests.set_position(position);
975     unsafe {
976         let ob = hoedown_buffer_new(DEF_OUNIT);
977         let renderer = hoedown_html_renderer_new(0, 0);
978         (*renderer).blockcode = Some(block);
979         (*renderer).header = Some(header);
980         (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
981                 = tests as *mut _ as *mut libc::c_void;
982
983         let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
984         hoedown_document_render(document, ob, doc.as_ptr(),
985                                 doc.len() as libc::size_t);
986         hoedown_document_free(document);
987
988         hoedown_html_renderer_free(renderer);
989         hoedown_buffer_free(ob);
990     }
991 }
992
993 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
994     tests.set_position(position);
995
996     let mut parser = Parser::new(doc);
997     let mut prev_offset = 0;
998     let mut nb_lines = 0;
999     let mut register_header = None;
1000     'main: while let Some(event) = parser.next() {
1001         match event {
1002             Event::Start(Tag::CodeBlock(s)) => {
1003                 let block_info = if s.is_empty() {
1004                     LangString::all_false()
1005                 } else {
1006                     LangString::parse(&*s)
1007                 };
1008                 if !block_info.rust {
1009                     continue
1010                 }
1011                 let mut test_s = String::new();
1012                 let mut offset = None;
1013                 loop {
1014                     let event = parser.next();
1015                     if let Some(event) = event {
1016                         match event {
1017                             Event::End(Tag::CodeBlock(_)) => break,
1018                             Event::Text(ref s) => {
1019                                 test_s.push_str(s);
1020                                 if offset.is_none() {
1021                                     offset = Some(parser.get_offset());
1022                                 }
1023                             }
1024                             _ => {}
1025                         }
1026                     } else {
1027                         break 'main;
1028                     }
1029                 }
1030                 let offset = offset.unwrap_or(0);
1031                 let lines = test_s.lines().map(|l| map_line(l).for_code());
1032                 let text = lines.collect::<Vec<&str>>().join("\n");
1033                 nb_lines += doc[prev_offset..offset].lines().count();
1034                 let line = tests.get_line() + (nb_lines - 1);
1035                 let filename = tests.get_filename();
1036                 tests.add_test(text.to_owned(),
1037                                block_info.should_panic, block_info.no_run,
1038                                block_info.ignore, block_info.test_harness,
1039                                block_info.compile_fail, block_info.error_codes,
1040                                line, filename, block_info.allow_fail);
1041                 prev_offset = offset;
1042             }
1043             Event::Start(Tag::Header(level)) => {
1044                 register_header = Some(level as u32);
1045             }
1046             Event::Text(ref s) if register_header.is_some() => {
1047                 let level = register_header.unwrap();
1048                 if s.is_empty() {
1049                     tests.register_header("", level);
1050                 } else {
1051                     tests.register_header(s, level);
1052                 }
1053                 register_header = None;
1054             }
1055             _ => {}
1056         }
1057     }
1058 }
1059
1060 #[derive(Eq, PartialEq, Clone, Debug)]
1061 struct LangString {
1062     original: String,
1063     should_panic: bool,
1064     no_run: bool,
1065     ignore: bool,
1066     rust: bool,
1067     test_harness: bool,
1068     compile_fail: bool,
1069     error_codes: Vec<String>,
1070     allow_fail: bool,
1071 }
1072
1073 impl LangString {
1074     fn all_false() -> LangString {
1075         LangString {
1076             original: String::new(),
1077             should_panic: false,
1078             no_run: false,
1079             ignore: false,
1080             rust: true,  // NB This used to be `notrust = false`
1081             test_harness: false,
1082             compile_fail: false,
1083             error_codes: Vec::new(),
1084             allow_fail: false,
1085         }
1086     }
1087
1088     fn parse(string: &str) -> LangString {
1089         let mut seen_rust_tags = false;
1090         let mut seen_other_tags = false;
1091         let mut data = LangString::all_false();
1092         let mut allow_error_code_check = false;
1093         if UnstableFeatures::from_environment().is_nightly_build() {
1094             allow_error_code_check = true;
1095         }
1096
1097         data.original = string.to_owned();
1098         let tokens = string.split(|c: char|
1099             !(c == '_' || c == '-' || c.is_alphanumeric())
1100         );
1101
1102         for token in tokens {
1103             match token.trim() {
1104                 "" => {},
1105                 "should_panic" => {
1106                     data.should_panic = true;
1107                     seen_rust_tags = seen_other_tags == false;
1108                 }
1109                 "no_run" => { data.no_run = true; seen_rust_tags = !seen_other_tags; }
1110                 "ignore" => { data.ignore = true; seen_rust_tags = !seen_other_tags; }
1111                 "allow_fail" => { data.allow_fail = true; seen_rust_tags = !seen_other_tags; }
1112                 "rust" => { data.rust = true; seen_rust_tags = true; }
1113                 "test_harness" => {
1114                     data.test_harness = true;
1115                     seen_rust_tags = !seen_other_tags || seen_rust_tags;
1116                 }
1117                 "compile_fail" => {
1118                     data.compile_fail = true;
1119                     seen_rust_tags = !seen_other_tags || seen_rust_tags;
1120                     data.no_run = true;
1121                 }
1122                 x if allow_error_code_check && x.starts_with("E") && x.len() == 5 => {
1123                     if let Ok(_) = x[1..].parse::<u32>() {
1124                         data.error_codes.push(x.to_owned());
1125                         seen_rust_tags = !seen_other_tags || seen_rust_tags;
1126                     } else {
1127                         seen_other_tags = true;
1128                     }
1129                 }
1130                 _ => { seen_other_tags = true }
1131             }
1132         }
1133
1134         data.rust &= !seen_other_tags || seen_rust_tags;
1135
1136         data
1137     }
1138 }
1139
1140 impl<'a> fmt::Display for Markdown<'a> {
1141     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1142         let Markdown(md, links, render_type) = *self;
1143
1144         // This is actually common enough to special-case
1145         if md.is_empty() { return Ok(()) }
1146         if render_type == RenderType::Hoedown {
1147             render(fmt, md, links, false, 0)
1148         } else {
1149             let mut opts = Options::empty();
1150             opts.insert(OPTION_ENABLE_TABLES);
1151             opts.insert(OPTION_ENABLE_FOOTNOTES);
1152
1153             let p = Parser::new_ext(md, opts);
1154
1155             let mut s = String::with_capacity(md.len() * 3 / 2);
1156
1157             html::push_html(&mut s,
1158                             Footnotes::new(
1159                                 CodeBlocks::new(
1160                                     LinkReplacer::new(
1161                                         HeadingLinks::new(p, None),
1162                                         links))));
1163
1164             fmt.write_str(&s)
1165         }
1166     }
1167 }
1168
1169 impl<'a> fmt::Display for MarkdownWithToc<'a> {
1170     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1171         let MarkdownWithToc(md, render_type) = *self;
1172
1173         if render_type == RenderType::Hoedown {
1174             render(fmt, md, &[], true, 0)
1175         } else {
1176             let mut opts = Options::empty();
1177             opts.insert(OPTION_ENABLE_TABLES);
1178             opts.insert(OPTION_ENABLE_FOOTNOTES);
1179
1180             let p = Parser::new_ext(md, opts);
1181
1182             let mut s = String::with_capacity(md.len() * 3 / 2);
1183
1184             let mut toc = TocBuilder::new();
1185
1186             html::push_html(&mut s,
1187                             Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, Some(&mut toc)))));
1188
1189             write!(fmt, "<nav id=\"TOC\">{}</nav>", toc.into_toc())?;
1190
1191             fmt.write_str(&s)
1192         }
1193     }
1194 }
1195
1196 impl<'a> fmt::Display for MarkdownHtml<'a> {
1197     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1198         let MarkdownHtml(md, render_type) = *self;
1199
1200         // This is actually common enough to special-case
1201         if md.is_empty() { return Ok(()) }
1202         if render_type == RenderType::Hoedown {
1203             render(fmt, md, &[], false, HOEDOWN_HTML_ESCAPE)
1204         } else {
1205             let mut opts = Options::empty();
1206             opts.insert(OPTION_ENABLE_TABLES);
1207             opts.insert(OPTION_ENABLE_FOOTNOTES);
1208
1209             let p = Parser::new_ext(md, opts);
1210
1211             // Treat inline HTML as plain text.
1212             let p = p.map(|event| match event {
1213                 Event::Html(text) | Event::InlineHtml(text) => Event::Text(text),
1214                 _ => event
1215             });
1216
1217             let mut s = String::with_capacity(md.len() * 3 / 2);
1218
1219             html::push_html(&mut s,
1220                             Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
1221
1222             fmt.write_str(&s)
1223         }
1224     }
1225 }
1226
1227 impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
1228     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1229         let MarkdownSummaryLine(md, links) = *self;
1230         // This is actually common enough to special-case
1231         if md.is_empty() { return Ok(()) }
1232
1233         let p = Parser::new(md);
1234
1235         let mut s = String::new();
1236
1237         html::push_html(&mut s, LinkReplacer::new(SummaryLine::new(p), links));
1238
1239         fmt.write_str(&s)
1240     }
1241 }
1242
1243 pub fn plain_summary_line(md: &str) -> String {
1244     struct ParserWrapper<'a> {
1245         inner: Parser<'a>,
1246         is_in: isize,
1247         is_first: bool,
1248     }
1249
1250     impl<'a> Iterator for ParserWrapper<'a> {
1251         type Item = String;
1252
1253         fn next(&mut self) -> Option<String> {
1254             let next_event = self.inner.next();
1255             if next_event.is_none() {
1256                 return None
1257             }
1258             let next_event = next_event.unwrap();
1259             let (ret, is_in) = match next_event {
1260                 Event::Start(Tag::Paragraph) => (None, 1),
1261                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
1262                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
1263                 Event::Start(Tag::Header(_)) => (None, 1),
1264                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
1265                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
1266                 _ => (None, 0),
1267             };
1268             if is_in > 0 || (is_in < 0 && self.is_in > 0) {
1269                 self.is_in += is_in;
1270             }
1271             if ret.is_some() {
1272                 self.is_first = false;
1273                 ret
1274             } else {
1275                 Some(String::new())
1276             }
1277         }
1278     }
1279     let mut s = String::with_capacity(md.len() * 3 / 2);
1280     let mut p = ParserWrapper {
1281         inner: Parser::new(md),
1282         is_in: 0,
1283         is_first: true,
1284     };
1285     while let Some(t) = p.next() {
1286         if !t.is_empty() {
1287             s.push_str(&t);
1288         }
1289     }
1290     s
1291 }
1292
1293 pub fn markdown_links(md: &str, render_type: RenderType) -> Vec<String> {
1294     if md.is_empty() {
1295         return vec![];
1296     }
1297
1298     match render_type {
1299         RenderType::Hoedown => {
1300             extern fn hoedown_link(
1301                 _ob: *mut hoedown_buffer,
1302                 _content: *const hoedown_buffer,
1303                 link: *const hoedown_buffer,
1304                 _title: *const hoedown_buffer,
1305                 data: *const hoedown_renderer_data,
1306                 _line: libc::size_t
1307             ) -> libc::c_int {
1308                 if link.is_null() {
1309                     return 0;
1310                 }
1311
1312                 let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state };
1313                 let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
1314
1315                 if let Some(ref mut links) = opaque.links_out {
1316                     let s = unsafe { (*link).as_bytes() };
1317                     let s = str::from_utf8(&s).unwrap().to_owned();
1318
1319                     debug!("found link: {}", s);
1320
1321                     links.push(s);
1322                 }
1323
1324                 //returning 0 here means "emit the span verbatim", but we're not using the output
1325                 //anyway so we don't really care
1326                 0
1327             }
1328
1329             unsafe {
1330                 let ob = hoedown_buffer_new(DEF_OUNIT);
1331                 let renderer = hoedown_html_renderer_new(0, 0);
1332                 let mut opaque = MyOpaque {
1333                     dfltblk: (*renderer).blockcode.unwrap(),
1334                     toc_builder: None,
1335                     links_out: Some(vec![]),
1336                     links_replace: vec![],
1337                 };
1338                 (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque
1339                         = &mut opaque as *mut _ as *mut libc::c_void;
1340                 (*renderer).header = Some(hoedown_header);
1341                 (*renderer).codespan = Some(hoedown_codespan);
1342                 (*renderer).link = Some(hoedown_link);
1343
1344                 let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
1345                 hoedown_document_render(document, ob, md.as_ptr(),
1346                                         md.len() as libc::size_t);
1347                 hoedown_document_free(document);
1348
1349                 hoedown_html_renderer_free(renderer);
1350                 hoedown_buffer_free(ob);
1351
1352                 opaque.links_out.unwrap()
1353             }
1354         }
1355         RenderType::Pulldown => {
1356             let mut opts = Options::empty();
1357             opts.insert(OPTION_ENABLE_TABLES);
1358             opts.insert(OPTION_ENABLE_FOOTNOTES);
1359
1360             let p = Parser::new_ext(md, opts);
1361
1362             let iter = Footnotes::new(HeadingLinks::new(p, None));
1363             let mut links = vec![];
1364
1365             for ev in iter {
1366                 if let Event::Start(Tag::Link(dest, _)) = ev {
1367                     debug!("found link: {}", dest);
1368                     links.push(dest.into_owned());
1369                 }
1370             }
1371
1372             links
1373         }
1374     }
1375 }
1376
1377 #[cfg(test)]
1378 mod tests {
1379     use super::{LangString, Markdown, MarkdownHtml};
1380     use super::plain_summary_line;
1381     use super::RenderType;
1382     use html::render::reset_ids;
1383
1384     #[test]
1385     fn test_lang_string_parse() {
1386         fn t(s: &str,
1387             should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
1388             compile_fail: bool, allow_fail: bool, error_codes: Vec<String>) {
1389             assert_eq!(LangString::parse(s), LangString {
1390                 should_panic,
1391                 no_run,
1392                 ignore,
1393                 rust,
1394                 test_harness,
1395                 compile_fail,
1396                 error_codes,
1397                 original: s.to_owned(),
1398                 allow_fail,
1399             })
1400         }
1401
1402         fn v() -> Vec<String> {
1403             Vec::new()
1404         }
1405
1406         // marker                | should_panic| no_run| ignore| rust | test_harness| compile_fail
1407         //                       | allow_fail | error_codes
1408         t("",                      false,        false,  false,  true,  false, false, false, v());
1409         t("rust",                  false,        false,  false,  true,  false, false, false, v());
1410         t("sh",                    false,        false,  false,  false, false, false, false, v());
1411         t("ignore",                false,        false,  true,   true,  false, false, false, v());
1412         t("should_panic",          true,         false,  false,  true,  false, false, false, v());
1413         t("no_run",                false,        true,   false,  true,  false, false, false, v());
1414         t("test_harness",          false,        false,  false,  true,  true,  false, false, v());
1415         t("compile_fail",          false,        true,   false,  true,  false, true,  false, v());
1416         t("allow_fail",            false,        false,  false,  true,  false, false, true,  v());
1417         t("{.no_run .example}",    false,        true,   false,  true,  false, false, false, v());
1418         t("{.sh .should_panic}",   true,         false,  false,  false, false, false, false, v());
1419         t("{.example .rust}",      false,        false,  false,  true,  false, false, false, v());
1420         t("{.test_harness .rust}", false,        false,  false,  true,  true,  false, false, v());
1421         t("text, no_run",          false,        true,   false,  false, false, false, false, v());
1422         t("text,no_run",           false,        true,   false,  false, false, false, false, v());
1423     }
1424
1425     #[test]
1426     fn issue_17736() {
1427         let markdown = "# title";
1428         format!("{}", Markdown(markdown, &[], RenderType::Pulldown));
1429         reset_ids(true);
1430     }
1431
1432     #[test]
1433     fn test_header() {
1434         fn t(input: &str, expect: &str) {
1435             let output = format!("{}", Markdown(input, &[], RenderType::Pulldown));
1436             assert_eq!(output, expect, "original: {}", input);
1437             reset_ids(true);
1438         }
1439
1440         t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
1441           <a href=\"#foo-bar\">Foo bar</a></h1>");
1442         t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
1443           header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
1444         t("### **Foo** *bar* baz!?!& -_qux_-%",
1445           "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
1446           <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
1447           <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
1448         t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
1449           "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
1450           <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
1451           <em><code>baz</code></em> ❤ #qux</a></h4>");
1452     }
1453
1454     #[test]
1455     fn test_header_ids_multiple_blocks() {
1456         fn t(input: &str, expect: &str) {
1457             let output = format!("{}", Markdown(input, &[], RenderType::Pulldown));
1458             assert_eq!(output, expect, "original: {}", input);
1459         }
1460
1461         let test = || {
1462             t("# Example", "<h1 id=\"example\" class=\"section-header\">\
1463               <a href=\"#example\">Example</a></h1>");
1464             t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
1465               <a href=\"#panics\">Panics</a></h1>");
1466             t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
1467               <a href=\"#example-1\">Example</a></h1>");
1468             t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
1469               <a href=\"#main-1\">Main</a></h1>");
1470             t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
1471               <a href=\"#example-2\">Example</a></h1>");
1472             t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
1473               <a href=\"#panics-1\">Panics</a></h1>");
1474         };
1475         test();
1476         reset_ids(true);
1477         test();
1478     }
1479
1480     #[test]
1481     fn test_plain_summary_line() {
1482         fn t(input: &str, expect: &str) {
1483             let output = plain_summary_line(input);
1484             assert_eq!(output, expect, "original: {}", input);
1485         }
1486
1487         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
1488         t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
1489         t("code `let x = i32;` ...", "code `let x = i32;` ...");
1490         t("type `Type<'static>` ...", "type `Type<'static>` ...");
1491         t("# top header", "top header");
1492         t("## header", "header");
1493     }
1494
1495     #[test]
1496     fn test_markdown_html_escape() {
1497         fn t(input: &str, expect: &str) {
1498             let output = format!("{}", MarkdownHtml(input, RenderType::Pulldown));
1499             assert_eq!(output, expect, "original: {}", input);
1500         }
1501
1502         t("`Struct<'a, T>`", "<p><code>Struct&lt;'a, T&gt;</code></p>\n");
1503         t("Struct<'a, T>", "<p>Struct&lt;'a, T&gt;</p>\n");
1504         t("Struct<br>", "<p>Struct&lt;br&gt;</p>\n");
1505     }
1506 }