]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
Account for --remap-path-prefix in save-analysis
[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::{IdMap, Markdown, ErrorCodes};
22 //! use std::cell::RefCell;
23 //!
24 //! let s = "My *markdown* _text_";
25 //! let mut id_map = IdMap::new();
26 //! let html = format!("{}", Markdown(s, &[], RefCell::new(&mut id_map), ErrorCodes::Yes));
27 //! // ... something using html
28 //! ```
29
30 #![allow(non_camel_case_types)]
31
32 use std::cell::RefCell;
33 use std::collections::{HashMap, VecDeque};
34 use std::default::Default;
35 use std::fmt::{self, Write};
36 use std::borrow::Cow;
37 use std::ops::Range;
38 use std::str;
39
40 use html::toc::TocBuilder;
41 use html::highlight;
42 use test;
43
44 use pulldown_cmark::{html, Event, Tag, Parser};
45 use pulldown_cmark::{Options, OPTION_ENABLE_FOOTNOTES, OPTION_ENABLE_TABLES};
46
47 /// A unit struct which has the `fmt::Display` trait implemented. When
48 /// formatted, this struct will emit the HTML corresponding to the rendered
49 /// version of the contained markdown string.
50 /// The second parameter is a list of link replacements
51 pub struct Markdown<'a>(
52     pub &'a str, pub &'a [(String, String)], pub RefCell<&'a mut IdMap>, pub ErrorCodes);
53 /// A unit struct like `Markdown`, that renders the markdown with a
54 /// table of contents.
55 pub struct MarkdownWithToc<'a>(pub &'a str, pub RefCell<&'a mut IdMap>, pub ErrorCodes);
56 /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
57 pub struct MarkdownHtml<'a>(pub &'a str, pub RefCell<&'a mut IdMap>, pub ErrorCodes);
58 /// A unit struct like `Markdown`, that renders only the first paragraph.
59 pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]);
60
61 #[derive(Copy, Clone, PartialEq, Debug)]
62 pub enum ErrorCodes {
63     Yes,
64     No,
65 }
66
67 impl ErrorCodes {
68     pub fn from(b: bool) -> Self {
69         match b {
70             true => ErrorCodes::Yes,
71             false => ErrorCodes::No,
72         }
73     }
74
75     pub fn as_bool(self) -> bool {
76         match self {
77             ErrorCodes::Yes => true,
78             ErrorCodes::No => false,
79         }
80     }
81 }
82
83 /// Controls whether a line will be hidden or shown in HTML output.
84 ///
85 /// All lines are used in documentation tests.
86 enum Line<'a> {
87     Hidden(&'a str),
88     Shown(Cow<'a, str>),
89 }
90
91 impl<'a> Line<'a> {
92     fn for_html(self) -> Option<Cow<'a, str>> {
93         match self {
94             Line::Shown(l) => Some(l),
95             Line::Hidden(_) => None,
96         }
97     }
98
99     fn for_code(self) -> Cow<'a, str> {
100         match self {
101             Line::Shown(l) => l,
102             Line::Hidden(l) => Cow::Borrowed(l),
103         }
104     }
105 }
106
107 // FIXME: There is a minor inconsistency here. For lines that start with ##, we
108 // have no easy way of removing a potential single space after the hashes, which
109 // is done in the single # case. This inconsistency seems okay, if non-ideal. In
110 // order to fix it we'd have to iterate to find the first non-# character, and
111 // then reallocate to remove it; which would make us return a String.
112 fn map_line(s: &str) -> Line {
113     let trimmed = s.trim();
114     if trimmed.starts_with("##") {
115         Line::Shown(Cow::Owned(s.replacen("##", "#", 1)))
116     } else if trimmed.starts_with("# ") {
117         // # text
118         Line::Hidden(&trimmed[2..])
119     } else if trimmed == "#" {
120         // We cannot handle '#text' because it could be #[attr].
121         Line::Hidden("")
122     } else {
123         Line::Shown(Cow::Borrowed(s))
124     }
125 }
126
127 /// Convert chars from a title for an id.
128 ///
129 /// "Hello, world!" -> "hello-world"
130 fn slugify(c: char) -> Option<char> {
131     if c.is_alphanumeric() || c == '-' || c == '_' {
132         if c.is_ascii() {
133             Some(c.to_ascii_lowercase())
134         } else {
135             Some(c)
136         }
137     } else if c.is_whitespace() && c.is_ascii() {
138         Some('-')
139     } else {
140         None
141     }
142 }
143
144 // Information about the playground if a URL has been specified, containing an
145 // optional crate name and the URL.
146 thread_local!(pub static PLAYGROUND: RefCell<Option<(Option<String>, String)>> = {
147     RefCell::new(None)
148 });
149
150 /// Adds syntax highlighting and playground Run buttons to rust code blocks.
151 struct CodeBlocks<'a, I: Iterator<Item = Event<'a>>> {
152     inner: I,
153     check_error_codes: ErrorCodes,
154 }
155
156 impl<'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'a, I> {
157     fn new(iter: I, error_codes: ErrorCodes) -> Self {
158         CodeBlocks {
159             inner: iter,
160             check_error_codes: error_codes,
161         }
162     }
163 }
164
165 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
166     type Item = Event<'a>;
167
168     fn next(&mut self) -> Option<Self::Item> {
169         let event = self.inner.next();
170         let compile_fail;
171         let ignore;
172         if let Some(Event::Start(Tag::CodeBlock(lang))) = event {
173             let parse_result = LangString::parse(&lang, self.check_error_codes);
174             if !parse_result.rust {
175                 return Some(Event::Start(Tag::CodeBlock(lang)));
176             }
177             compile_fail = parse_result.compile_fail;
178             ignore = parse_result.ignore;
179         } else {
180             return event;
181         }
182
183         let mut origtext = String::new();
184         for event in &mut self.inner {
185             match event {
186                 Event::End(Tag::CodeBlock(..)) => break,
187                 Event::Text(ref s) => {
188                     origtext.push_str(s);
189                 }
190                 _ => {}
191             }
192         }
193         let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
194         let text = lines.collect::<Vec<Cow<str>>>().join("\n");
195         PLAYGROUND.with(|play| {
196             // insert newline to clearly separate it from the
197             // previous block so we can shorten the html output
198             let mut s = String::from("\n");
199             let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
200                 if url.is_empty() {
201                     return None;
202                 }
203                 let test = origtext.lines()
204                     .map(|l| map_line(l).for_code())
205                     .collect::<Vec<Cow<str>>>().join("\n");
206                 let krate = krate.as_ref().map(|s| &**s);
207                 let (test, _) = test::make_test(&test, krate, false,
208                                            &Default::default());
209                 let channel = if test.contains("#![feature(") {
210                     "&amp;version=nightly"
211                 } else {
212                     ""
213                 };
214                 // These characters don't need to be escaped in a URI.
215                 // FIXME: use a library function for percent encoding.
216                 fn dont_escape(c: u8) -> bool {
217                     (b'a' <= c && c <= b'z') ||
218                     (b'A' <= c && c <= b'Z') ||
219                     (b'0' <= c && c <= b'9') ||
220                     c == b'-' || c == b'_' || c == b'.' ||
221                     c == b'~' || c == b'!' || c == b'\'' ||
222                     c == b'(' || c == b')' || c == b'*'
223                 }
224                 let mut test_escaped = String::new();
225                 for b in test.bytes() {
226                     if dont_escape(b) {
227                         test_escaped.push(char::from(b));
228                     } else {
229                         write!(test_escaped, "%{:02X}", b).unwrap();
230                     }
231                 }
232                 Some(format!(
233                     r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
234                     url, test_escaped, channel
235                 ))
236             });
237             let tooltip = if ignore {
238                 Some(("This example is not tested", "ignore"))
239             } else if compile_fail {
240                 Some(("This example deliberately fails to compile", "compile_fail"))
241             } else {
242                 None
243             };
244             s.push_str(&highlight::render_with_highlighting(
245                         &text,
246                         Some(&format!("rust-example-rendered{}",
247                                       if ignore { " ignore" }
248                                       else if compile_fail { " compile_fail" }
249                                       else { "" })),
250                         playground_button.as_ref().map(String::as_str),
251                         tooltip));
252             Some(Event::Html(s.into()))
253         })
254     }
255 }
256
257 /// Make headings links with anchor ids and build up TOC.
258 struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
259     inner: I,
260     links: &'b [(String, String)],
261 }
262
263 impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
264     fn new(iter: I, links: &'b [(String, String)]) -> Self {
265         LinkReplacer {
266             inner: iter,
267             links,
268         }
269     }
270 }
271
272 impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
273     type Item = Event<'a>;
274
275     fn next(&mut self) -> Option<Self::Item> {
276         let event = self.inner.next();
277         if let Some(Event::Start(Tag::Link(dest, text))) = event {
278             if let Some(&(_, ref replace)) = self.links.into_iter().find(|link| &*link.0 == &*dest)
279             {
280                 Some(Event::Start(Tag::Link(replace.to_owned().into(), text)))
281             } else {
282                 Some(Event::Start(Tag::Link(dest, text)))
283             }
284         } else {
285             event
286         }
287     }
288 }
289
290 /// Make headings links with anchor ids and build up TOC.
291 struct HeadingLinks<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> {
292     inner: I,
293     toc: Option<&'b mut TocBuilder>,
294     buf: VecDeque<Event<'a>>,
295     id_map: &'ids mut IdMap,
296 }
297
298 impl<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, 'ids, I> {
299     fn new(iter: I, toc: Option<&'b mut TocBuilder>, ids: &'ids mut IdMap) -> Self {
300         HeadingLinks {
301             inner: iter,
302             toc,
303             buf: VecDeque::new(),
304             id_map: ids,
305         }
306     }
307 }
308
309 impl<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, 'ids, I> {
310     type Item = Event<'a>;
311
312     fn next(&mut self) -> Option<Self::Item> {
313         if let Some(e) = self.buf.pop_front() {
314             return Some(e);
315         }
316
317         let event = self.inner.next();
318         if let Some(Event::Start(Tag::Header(level))) = event {
319             let mut id = String::new();
320             for event in &mut self.inner {
321                 match event {
322                     Event::End(Tag::Header(..)) => break,
323                     Event::Text(ref text) => id.extend(text.chars().filter_map(slugify)),
324                     _ => {},
325                 }
326                 self.buf.push_back(event);
327             }
328             let id = self.id_map.derive(id);
329
330             if let Some(ref mut builder) = self.toc {
331                 let mut html_header = String::new();
332                 html::push_html(&mut html_header, self.buf.iter().cloned());
333                 let sec = builder.push(level as u32, html_header, id.clone());
334                 self.buf.push_front(Event::InlineHtml(format!("{} ", sec).into()));
335             }
336
337             self.buf.push_back(Event::InlineHtml(format!("</a></h{}>", level).into()));
338
339             let start_tags = format!("<h{level} id=\"{id}\" class=\"section-header\">\
340                                       <a href=\"#{id}\">",
341                                      id = id,
342                                      level = level);
343             return Some(Event::InlineHtml(start_tags.into()));
344         }
345         event
346     }
347 }
348
349 /// Extracts just the first paragraph.
350 struct SummaryLine<'a, I: Iterator<Item = Event<'a>>> {
351     inner: I,
352     started: bool,
353     depth: u32,
354 }
355
356 impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> {
357     fn new(iter: I) -> Self {
358         SummaryLine {
359             inner: iter,
360             started: false,
361             depth: 0,
362         }
363     }
364 }
365
366 fn check_if_allowed_tag(t: &Tag) -> bool {
367     match *t {
368         Tag::Paragraph
369         | Tag::CodeBlock(_)
370         | Tag::Item
371         | Tag::Emphasis
372         | Tag::Strong
373         | Tag::Code
374         | Tag::Link(_, _)
375         | Tag::BlockQuote => true,
376         _ => false,
377     }
378 }
379
380 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
381     type Item = Event<'a>;
382
383     fn next(&mut self) -> Option<Self::Item> {
384         if self.started && self.depth == 0 {
385             return None;
386         }
387         if !self.started {
388             self.started = true;
389         }
390         let event = self.inner.next();
391         let mut is_start = true;
392         let is_allowed_tag = match event {
393             Some(Event::Start(ref c)) => {
394                 self.depth += 1;
395                 check_if_allowed_tag(c)
396             }
397             Some(Event::End(ref c)) => {
398                 self.depth -= 1;
399                 is_start = false;
400                 check_if_allowed_tag(c)
401             }
402             _ => true,
403         };
404         if is_allowed_tag == false {
405             if is_start {
406                 Some(Event::Start(Tag::Paragraph))
407             } else {
408                 Some(Event::End(Tag::Paragraph))
409             }
410         } else {
411             event
412         }
413     }
414 }
415
416 /// Moves all footnote definitions to the end and add back links to the
417 /// references.
418 struct Footnotes<'a, I: Iterator<Item = Event<'a>>> {
419     inner: I,
420     footnotes: HashMap<String, (Vec<Event<'a>>, u16)>,
421 }
422
423 impl<'a, I: Iterator<Item = Event<'a>>> Footnotes<'a, I> {
424     fn new(iter: I) -> Self {
425         Footnotes {
426             inner: iter,
427             footnotes: HashMap::new(),
428         }
429     }
430     fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
431         let new_id = self.footnotes.keys().count() + 1;
432         let key = key.to_owned();
433         self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
434     }
435 }
436
437 impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
438     type Item = Event<'a>;
439
440     fn next(&mut self) -> Option<Self::Item> {
441         loop {
442             match self.inner.next() {
443                 Some(Event::FootnoteReference(ref reference)) => {
444                     let entry = self.get_entry(&reference);
445                     let reference = format!("<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}\
446                                              </a></sup>",
447                                             (*entry).1);
448                     return Some(Event::Html(reference.into()));
449                 }
450                 Some(Event::Start(Tag::FootnoteDefinition(def))) => {
451                     let mut content = Vec::new();
452                     for event in &mut self.inner {
453                         if let Event::End(Tag::FootnoteDefinition(..)) = event {
454                             break;
455                         }
456                         content.push(event);
457                     }
458                     let entry = self.get_entry(&def);
459                     (*entry).0 = content;
460                 }
461                 Some(e) => return Some(e),
462                 None => {
463                     if !self.footnotes.is_empty() {
464                         let mut v: Vec<_> = self.footnotes.drain().map(|(_, x)| x).collect();
465                         v.sort_by(|a, b| a.1.cmp(&b.1));
466                         let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
467                         for (mut content, id) in v {
468                             write!(ret, "<li id=\"fn{}\">", id).unwrap();
469                             let mut is_paragraph = false;
470                             if let Some(&Event::End(Tag::Paragraph)) = content.last() {
471                                 content.pop();
472                                 is_paragraph = true;
473                             }
474                             html::push_html(&mut ret, content.into_iter());
475                             write!(ret,
476                                    "&nbsp;<a href=\"#fnref{}\" rev=\"footnote\">↩</a>",
477                                    id).unwrap();
478                             if is_paragraph {
479                                 ret.push_str("</p>");
480                             }
481                             ret.push_str("</li>");
482                         }
483                         ret.push_str("</ol></div>");
484                         return Some(Event::Html(ret.into()));
485                     } else {
486                         return None;
487                     }
488                 }
489             }
490         }
491     }
492 }
493
494 pub struct TestableCodeError(());
495
496 impl fmt::Display for TestableCodeError {
497     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
498         write!(f, "invalid start of a new code block")
499     }
500 }
501
502 pub fn find_testable_code(
503     doc: &str, tests: &mut test::Collector, error_codes: ErrorCodes,
504 ) -> Result<(), TestableCodeError> {
505     let mut parser = Parser::new(doc);
506     let mut prev_offset = 0;
507     let mut nb_lines = 0;
508     let mut register_header = None;
509     'main: while let Some(event) = parser.next() {
510         match event {
511             Event::Start(Tag::CodeBlock(s)) => {
512                 let block_info = if s.is_empty() {
513                     LangString::all_false()
514                 } else {
515                     LangString::parse(&*s, error_codes)
516                 };
517                 if !block_info.rust {
518                     continue
519                 }
520                 let mut test_s = String::new();
521                 let mut offset = None;
522                 loop {
523                     let event = parser.next();
524                     if let Some(event) = event {
525                         match event {
526                             Event::End(Tag::CodeBlock(_)) => break,
527                             Event::Text(ref s) => {
528                                 test_s.push_str(s);
529                                 if offset.is_none() {
530                                     offset = Some(parser.get_offset());
531                                 }
532                             }
533                             _ => {}
534                         }
535                     } else {
536                         break 'main;
537                     }
538                 }
539                 if let Some(offset) = offset {
540                     let lines = test_s.lines().map(|l| map_line(l).for_code());
541                     let text = lines.collect::<Vec<Cow<str>>>().join("\n");
542                     nb_lines += doc[prev_offset..offset].lines().count();
543                     let line = tests.get_line() + (nb_lines - 1);
544                     tests.add_test(text, block_info, line);
545                     prev_offset = offset;
546                 } else {
547                     return Err(TestableCodeError(()));
548                 }
549             }
550             Event::Start(Tag::Header(level)) => {
551                 register_header = Some(level as u32);
552             }
553             Event::Text(ref s) if register_header.is_some() => {
554                 let level = register_header.unwrap();
555                 if s.is_empty() {
556                     tests.register_header("", level);
557                 } else {
558                     tests.register_header(s, level);
559                 }
560                 register_header = None;
561             }
562             _ => {}
563         }
564     }
565     Ok(())
566 }
567
568 #[derive(Eq, PartialEq, Clone, Debug)]
569 pub struct LangString {
570     original: String,
571     pub should_panic: bool,
572     pub no_run: bool,
573     pub ignore: bool,
574     pub rust: bool,
575     pub test_harness: bool,
576     pub compile_fail: bool,
577     pub error_codes: Vec<String>,
578     pub allow_fail: bool,
579 }
580
581 impl LangString {
582     fn all_false() -> LangString {
583         LangString {
584             original: String::new(),
585             should_panic: false,
586             no_run: false,
587             ignore: false,
588             rust: true,  // NB This used to be `notrust = false`
589             test_harness: false,
590             compile_fail: false,
591             error_codes: Vec::new(),
592             allow_fail: false,
593         }
594     }
595
596     fn parse(string: &str, allow_error_code_check: ErrorCodes) -> LangString {
597         let allow_error_code_check = allow_error_code_check.as_bool();
598         let mut seen_rust_tags = false;
599         let mut seen_other_tags = false;
600         let mut data = LangString::all_false();
601
602         data.original = string.to_owned();
603         let tokens = string.split(|c: char|
604             !(c == '_' || c == '-' || c.is_alphanumeric())
605         );
606
607         for token in tokens {
608             match token.trim() {
609                 "" => {},
610                 "should_panic" => {
611                     data.should_panic = true;
612                     seen_rust_tags = seen_other_tags == false;
613                 }
614                 "no_run" => { data.no_run = true; seen_rust_tags = !seen_other_tags; }
615                 "ignore" => { data.ignore = true; seen_rust_tags = !seen_other_tags; }
616                 "allow_fail" => { data.allow_fail = true; seen_rust_tags = !seen_other_tags; }
617                 "rust" => { data.rust = true; seen_rust_tags = true; }
618                 "test_harness" => {
619                     data.test_harness = true;
620                     seen_rust_tags = !seen_other_tags || seen_rust_tags;
621                 }
622                 "compile_fail" => {
623                     data.compile_fail = true;
624                     seen_rust_tags = !seen_other_tags || seen_rust_tags;
625                     data.no_run = true;
626                 }
627                 x if allow_error_code_check && x.starts_with("E") && x.len() == 5 => {
628                     if let Ok(_) = x[1..].parse::<u32>() {
629                         data.error_codes.push(x.to_owned());
630                         seen_rust_tags = !seen_other_tags || seen_rust_tags;
631                     } else {
632                         seen_other_tags = true;
633                     }
634                 }
635                 _ => { seen_other_tags = true }
636             }
637         }
638
639         data.rust &= !seen_other_tags || seen_rust_tags;
640
641         data
642     }
643 }
644
645 impl<'a> fmt::Display for Markdown<'a> {
646     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
647         let Markdown(md, links, ref ids, codes) = *self;
648         let mut ids = ids.borrow_mut();
649
650         // This is actually common enough to special-case
651         if md.is_empty() { return Ok(()) }
652         let mut opts = Options::empty();
653         opts.insert(OPTION_ENABLE_TABLES);
654         opts.insert(OPTION_ENABLE_FOOTNOTES);
655
656         let replacer = |_: &str, s: &str| {
657             if let Some(&(_, ref replace)) = links.into_iter().find(|link| &*link.0 == s) {
658                 Some((replace.clone(), s.to_owned()))
659             } else {
660                 None
661             }
662         };
663
664         let p = Parser::new_with_broken_link_callback(md, opts, Some(&replacer));
665
666         let mut s = String::with_capacity(md.len() * 3 / 2);
667
668         let p = HeadingLinks::new(p, None, &mut ids);
669         let p = LinkReplacer::new(p, links);
670         let p = CodeBlocks::new(p, codes);
671         let p = Footnotes::new(p);
672         html::push_html(&mut s, p);
673
674         fmt.write_str(&s)
675     }
676 }
677
678 impl<'a> fmt::Display for MarkdownWithToc<'a> {
679     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
680         let MarkdownWithToc(md, ref ids, codes) = *self;
681         let mut ids = ids.borrow_mut();
682
683         let mut opts = Options::empty();
684         opts.insert(OPTION_ENABLE_TABLES);
685         opts.insert(OPTION_ENABLE_FOOTNOTES);
686
687         let p = Parser::new_ext(md, opts);
688
689         let mut s = String::with_capacity(md.len() * 3 / 2);
690
691         let mut toc = TocBuilder::new();
692
693         {
694             let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
695             let p = CodeBlocks::new(p, codes);
696             let p = Footnotes::new(p);
697             html::push_html(&mut s, p);
698         }
699
700         write!(fmt, "<nav id=\"TOC\">{}</nav>", toc.into_toc())?;
701
702         fmt.write_str(&s)
703     }
704 }
705
706 impl<'a> fmt::Display for MarkdownHtml<'a> {
707     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
708         let MarkdownHtml(md, ref ids, codes) = *self;
709         let mut ids = ids.borrow_mut();
710
711         // This is actually common enough to special-case
712         if md.is_empty() { return Ok(()) }
713         let mut opts = Options::empty();
714         opts.insert(OPTION_ENABLE_TABLES);
715         opts.insert(OPTION_ENABLE_FOOTNOTES);
716
717         let p = Parser::new_ext(md, opts);
718
719         // Treat inline HTML as plain text.
720         let p = p.map(|event| match event {
721             Event::Html(text) | Event::InlineHtml(text) => Event::Text(text),
722             _ => event
723         });
724
725         let mut s = String::with_capacity(md.len() * 3 / 2);
726
727         let p = HeadingLinks::new(p, None, &mut ids);
728         let p = CodeBlocks::new(p, codes);
729         let p = Footnotes::new(p);
730         html::push_html(&mut s, p);
731
732         fmt.write_str(&s)
733     }
734 }
735
736 impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
737     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
738         let MarkdownSummaryLine(md, links) = *self;
739         // This is actually common enough to special-case
740         if md.is_empty() { return Ok(()) }
741
742         let replacer = |_: &str, s: &str| {
743             if let Some(&(_, ref replace)) = links.into_iter().find(|link| &*link.0 == s) {
744                 Some((replace.clone(), s.to_owned()))
745             } else {
746                 None
747             }
748         };
749
750         let p = Parser::new_with_broken_link_callback(md, Options::empty(), Some(&replacer));
751
752         let mut s = String::new();
753
754         html::push_html(&mut s, LinkReplacer::new(SummaryLine::new(p), links));
755
756         fmt.write_str(&s)
757     }
758 }
759
760 pub fn plain_summary_line(md: &str) -> String {
761     struct ParserWrapper<'a> {
762         inner: Parser<'a>,
763         is_in: isize,
764         is_first: bool,
765     }
766
767     impl<'a> Iterator for ParserWrapper<'a> {
768         type Item = String;
769
770         fn next(&mut self) -> Option<String> {
771             let next_event = self.inner.next();
772             if next_event.is_none() {
773                 return None
774             }
775             let next_event = next_event.unwrap();
776             let (ret, is_in) = match next_event {
777                 Event::Start(Tag::Paragraph) => (None, 1),
778                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
779                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
780                 Event::Start(Tag::Header(_)) => (None, 1),
781                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
782                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
783                 _ => (None, 0),
784             };
785             if is_in > 0 || (is_in < 0 && self.is_in > 0) {
786                 self.is_in += is_in;
787             }
788             if ret.is_some() {
789                 self.is_first = false;
790                 ret
791             } else {
792                 Some(String::new())
793             }
794         }
795     }
796     let mut s = String::with_capacity(md.len() * 3 / 2);
797     let mut p = ParserWrapper {
798         inner: Parser::new(md),
799         is_in: 0,
800         is_first: true,
801     };
802     while let Some(t) = p.next() {
803         if !t.is_empty() {
804             s.push_str(&t);
805         }
806     }
807     s
808 }
809
810 pub fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
811     if md.is_empty() {
812         return vec![];
813     }
814
815     let mut opts = Options::empty();
816     opts.insert(OPTION_ENABLE_TABLES);
817     opts.insert(OPTION_ENABLE_FOOTNOTES);
818
819     let mut links = vec![];
820     let shortcut_links = RefCell::new(vec![]);
821
822     {
823         let locate = |s: &str| unsafe {
824             let s_start = s.as_ptr();
825             let s_end = s_start.add(s.len());
826             let md_start = md.as_ptr();
827             let md_end = md_start.add(md.len());
828             if md_start <= s_start && s_end <= md_end {
829                 let start = s_start.offset_from(md_start) as usize;
830                 let end = s_end.offset_from(md_start) as usize;
831                 Some(start..end)
832             } else {
833                 None
834             }
835         };
836
837         let push = |_: &str, s: &str| {
838             shortcut_links.borrow_mut().push((s.to_owned(), locate(s)));
839             None
840         };
841         let p = Parser::new_with_broken_link_callback(md, opts,
842             Some(&push));
843
844         // There's no need to thread an IdMap through to here because
845         // the IDs generated aren't going to be emitted anywhere.
846         let mut ids = IdMap::new();
847         let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
848
849         for ev in iter {
850             if let Event::Start(Tag::Link(dest, _)) = ev {
851                 debug!("found link: {}", dest);
852                 links.push(match dest {
853                     Cow::Borrowed(s) => (s.to_owned(), locate(s)),
854                     Cow::Owned(s) => (s, None),
855                 });
856             }
857         }
858     }
859
860     let mut shortcut_links = shortcut_links.into_inner();
861     links.extend(shortcut_links.drain(..));
862
863     links
864 }
865
866 #[derive(Default)]
867 pub struct IdMap {
868     map: HashMap<String, usize>,
869 }
870
871 impl IdMap {
872     pub fn new() -> Self {
873         IdMap::default()
874     }
875
876     pub fn populate<I: IntoIterator<Item=String>>(&mut self, ids: I) {
877         for id in ids {
878             let _ = self.derive(id);
879         }
880     }
881
882     pub fn reset(&mut self) {
883         self.map = HashMap::new();
884     }
885
886     pub fn derive(&mut self, candidate: String) -> String {
887         let id = match self.map.get_mut(&candidate) {
888             None => candidate,
889             Some(a) => {
890                 let id = format!("{}-{}", candidate, *a);
891                 *a += 1;
892                 id
893             }
894         };
895
896         self.map.insert(id.clone(), 1);
897         id
898     }
899 }
900
901 #[cfg(test)]
902 #[test]
903 fn test_unique_id() {
904     let input = ["foo", "examples", "examples", "method.into_iter","examples",
905                  "method.into_iter", "foo", "main", "search", "methods",
906                  "examples", "method.into_iter", "assoc_type.Item", "assoc_type.Item"];
907     let expected = ["foo", "examples", "examples-1", "method.into_iter", "examples-2",
908                     "method.into_iter-1", "foo-1", "main", "search", "methods",
909                     "examples-3", "method.into_iter-2", "assoc_type.Item", "assoc_type.Item-1"];
910
911     let map = RefCell::new(IdMap::new());
912     let test = || {
913         let mut map = map.borrow_mut();
914         let actual: Vec<String> = input.iter().map(|s| map.derive(s.to_string())).collect();
915         assert_eq!(&actual[..], expected);
916     };
917     test();
918     map.borrow_mut().reset();
919     test();
920 }
921
922 #[cfg(test)]
923 mod tests {
924     use super::{ErrorCodes, LangString, Markdown, MarkdownHtml, IdMap};
925     use super::plain_summary_line;
926     use std::cell::RefCell;
927
928     #[test]
929     fn test_lang_string_parse() {
930         fn t(s: &str,
931             should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
932             compile_fail: bool, allow_fail: bool, error_codes: Vec<String>) {
933             assert_eq!(LangString::parse(s, ErrorCodes::Yes), LangString {
934                 should_panic,
935                 no_run,
936                 ignore,
937                 rust,
938                 test_harness,
939                 compile_fail,
940                 error_codes,
941                 original: s.to_owned(),
942                 allow_fail,
943             })
944         }
945
946         fn v() -> Vec<String> {
947             Vec::new()
948         }
949
950         // marker                | should_panic| no_run| ignore| rust | test_harness| compile_fail
951         //                       | allow_fail | error_codes
952         t("",                      false,        false,  false,  true,  false, false, false, v());
953         t("rust",                  false,        false,  false,  true,  false, false, false, v());
954         t("sh",                    false,        false,  false,  false, false, false, false, v());
955         t("ignore",                false,        false,  true,   true,  false, false, false, v());
956         t("should_panic",          true,         false,  false,  true,  false, false, false, v());
957         t("no_run",                false,        true,   false,  true,  false, false, false, v());
958         t("test_harness",          false,        false,  false,  true,  true,  false, false, v());
959         t("compile_fail",          false,        true,   false,  true,  false, true,  false, v());
960         t("allow_fail",            false,        false,  false,  true,  false, false, true,  v());
961         t("{.no_run .example}",    false,        true,   false,  true,  false, false, false, v());
962         t("{.sh .should_panic}",   true,         false,  false,  false, false, false, false, v());
963         t("{.example .rust}",      false,        false,  false,  true,  false, false, false, v());
964         t("{.test_harness .rust}", false,        false,  false,  true,  true,  false, false, v());
965         t("text, no_run",          false,        true,   false,  false, false, false, false, v());
966         t("text,no_run",           false,        true,   false,  false, false, false, false, v());
967     }
968
969     #[test]
970     fn test_header() {
971         fn t(input: &str, expect: &str) {
972             let mut map = IdMap::new();
973             let output = Markdown(input, &[], RefCell::new(&mut map), ErrorCodes::Yes).to_string();
974             assert_eq!(output, expect, "original: {}", input);
975         }
976
977         t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
978           <a href=\"#foo-bar\">Foo bar</a></h1>");
979         t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
980           header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
981         t("### **Foo** *bar* baz!?!& -_qux_-%",
982           "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
983           <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
984           <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
985         t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
986           "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
987           <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
988           <em><code>baz</code></em> ❤ #qux</a></h4>");
989     }
990
991     #[test]
992     fn test_header_ids_multiple_blocks() {
993         let mut map = IdMap::new();
994         fn t(map: &mut IdMap, input: &str, expect: &str) {
995             let output = Markdown(input, &[], RefCell::new(map), ErrorCodes::Yes).to_string();
996             assert_eq!(output, expect, "original: {}", input);
997         }
998
999         t(&mut map, "# Example", "<h1 id=\"example\" class=\"section-header\">\
1000             <a href=\"#example\">Example</a></h1>");
1001         t(&mut map, "# Panics", "<h1 id=\"panics\" class=\"section-header\">\
1002             <a href=\"#panics\">Panics</a></h1>");
1003         t(&mut map, "# Example", "<h1 id=\"example-1\" class=\"section-header\">\
1004             <a href=\"#example-1\">Example</a></h1>");
1005         t(&mut map, "# Main", "<h1 id=\"main\" class=\"section-header\">\
1006             <a href=\"#main\">Main</a></h1>");
1007         t(&mut map, "# Example", "<h1 id=\"example-2\" class=\"section-header\">\
1008             <a href=\"#example-2\">Example</a></h1>");
1009         t(&mut map, "# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
1010             <a href=\"#panics-1\">Panics</a></h1>");
1011     }
1012
1013     #[test]
1014     fn test_plain_summary_line() {
1015         fn t(input: &str, expect: &str) {
1016             let output = plain_summary_line(input);
1017             assert_eq!(output, expect, "original: {}", input);
1018         }
1019
1020         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
1021         t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
1022         t("code `let x = i32;` ...", "code `let x = i32;` ...");
1023         t("type `Type<'static>` ...", "type `Type<'static>` ...");
1024         t("# top header", "top header");
1025         t("## header", "header");
1026     }
1027
1028     #[test]
1029     fn test_markdown_html_escape() {
1030         fn t(input: &str, expect: &str) {
1031             let mut idmap = IdMap::new();
1032             let output = MarkdownHtml(input, RefCell::new(&mut idmap), ErrorCodes::Yes).to_string();
1033             assert_eq!(output, expect, "original: {}", input);
1034         }
1035
1036         t("`Struct<'a, T>`", "<p><code>Struct&lt;'a, T&gt;</code></p>\n");
1037         t("Struct<'a, T>", "<p>Struct&lt;'a, T&gt;</p>\n");
1038         t("Struct<br>", "<p>Struct&lt;br&gt;</p>\n");
1039     }
1040 }