]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
change the format of the linked issue number
[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 //! ```rust,ignore
19 //! use rustdoc::html::markdown::{Markdown, MarkdownOutputStyle};
20 //!
21 //! let s = "My *markdown* _text_";
22 //! let html = format!("{}", Markdown(s, MarkdownOutputStyle::Fancy));
23 //! // ... something using html
24 //! ```
25
26 #![allow(non_camel_case_types)]
27
28 use std::ascii::AsciiExt;
29 use std::cell::RefCell;
30 use std::collections::HashMap;
31 use std::default::Default;
32 use std::fmt::{self, Write};
33 use std::str;
34 use syntax::feature_gate::UnstableFeatures;
35 use syntax::codemap::Span;
36
37 use html::render::derive_id;
38 use html::toc::TocBuilder;
39 use html::highlight;
40 use html::escape::Escape;
41 use test;
42
43 use pulldown_cmark::{self, Event, Parser, Tag};
44
45 #[derive(Copy, Clone)]
46 pub enum MarkdownOutputStyle {
47     Compact,
48     Fancy,
49 }
50
51 impl MarkdownOutputStyle {
52     pub fn is_compact(&self) -> bool {
53         match *self {
54             MarkdownOutputStyle::Compact => true,
55             _ => false,
56         }
57     }
58
59     pub fn is_fancy(&self) -> bool {
60         match *self {
61             MarkdownOutputStyle::Fancy => true,
62             _ => false,
63         }
64     }
65 }
66
67 /// A unit struct which has the `fmt::Display` trait implemented. When
68 /// formatted, this struct will emit the HTML corresponding to the rendered
69 /// version of the contained markdown string.
70 // The second parameter is whether we need a shorter version or not.
71 pub struct Markdown<'a>(pub &'a str, pub MarkdownOutputStyle);
72 /// A unit struct like `Markdown`, that renders the markdown with a
73 /// table of contents.
74 pub struct MarkdownWithToc<'a>(pub &'a str);
75 /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
76 pub struct MarkdownHtml<'a>(pub &'a str);
77
78 /// Returns Some(code) if `s` is a line that should be stripped from
79 /// documentation but used in example code. `code` is the portion of
80 /// `s` that should be used in tests. (None for lines that should be
81 /// left as-is.)
82 fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
83     let trimmed = s.trim();
84     if trimmed == "#" {
85         Some("")
86     } else if trimmed.starts_with("# ") {
87         Some(&trimmed[2..])
88     } else {
89         None
90     }
91 }
92
93 /// Returns a new string with all consecutive whitespace collapsed into
94 /// single spaces.
95 ///
96 /// Any leading or trailing whitespace will be trimmed.
97 fn collapse_whitespace(s: &str) -> String {
98     s.split_whitespace().collect::<Vec<_>>().join(" ")
99 }
100
101 // Information about the playground if a URL has been specified, containing an
102 // optional crate name and the URL.
103 thread_local!(pub static PLAYGROUND: RefCell<Option<(Option<String>, String)>> = {
104     RefCell::new(None)
105 });
106
107 macro_rules! event_loop_break {
108     ($parser:expr, $toc_builder:expr, $shorter:expr, $buf:expr, $escape:expr, $id:expr,
109      $($end_event:pat)|*) => {{
110         fn inner(id: &mut Option<&mut String>, s: &str) {
111             if let Some(ref mut id) = *id {
112                 id.push_str(s);
113             }
114         }
115         while let Some(event) = $parser.next() {
116             match event {
117                 $($end_event)|* => break,
118                 Event::Text(ref s) => {
119                     debug!("Text");
120                     inner($id, s);
121                     if $escape {
122                         $buf.push_str(&format!("{}", Escape(s)));
123                     } else {
124                         $buf.push_str(s);
125                     }
126                 }
127                 Event::SoftBreak => {
128                     debug!("SoftBreak");
129                     if !$buf.is_empty() {
130                         $buf.push(' ');
131                     }
132                 }
133                 x => {
134                     looper($parser, &mut $buf, Some(x), $toc_builder, $shorter, $id);
135                 }
136             }
137         }
138     }}
139 }
140
141 struct ParserWrapper<'a> {
142     parser: Parser<'a>,
143     // The key is the footnote reference. The value is the footnote definition and the id.
144     footnotes: HashMap<String, (String, u16)>,
145 }
146
147 impl<'a> ParserWrapper<'a> {
148     pub fn new(s: &'a str) -> ParserWrapper<'a> {
149         ParserWrapper {
150             parser: Parser::new_ext(s, pulldown_cmark::OPTION_ENABLE_TABLES |
151                                        pulldown_cmark::OPTION_ENABLE_FOOTNOTES),
152             footnotes: HashMap::new(),
153         }
154     }
155
156     pub fn next(&mut self) -> Option<Event<'a>> {
157         self.parser.next()
158     }
159
160     pub fn get_entry(&mut self, key: &str) -> &mut (String, u16) {
161         let new_id = self.footnotes.keys().count() + 1;
162         let key = key.to_owned();
163         self.footnotes.entry(key).or_insert((String::new(), new_id as u16))
164     }
165 }
166
167 pub fn render(w: &mut fmt::Formatter,
168               s: &str,
169               print_toc: bool,
170               shorter: MarkdownOutputStyle) -> fmt::Result {
171     fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
172         debug!("CodeBlock");
173         let mut origtext = String::new();
174         while let Some(event) = parser.next() {
175             match event {
176                 Event::End(Tag::CodeBlock(_)) => break,
177                 Event::Text(ref s) => {
178                     origtext.push_str(s);
179                 }
180                 _ => {}
181             }
182         }
183         let origtext = origtext.trim_left();
184         debug!("docblock: ==============\n{:?}\n=======", origtext);
185
186         let lines = origtext.lines().filter(|l| {
187             stripped_filtered_line(*l).is_none()
188         });
189         let text = lines.collect::<Vec<&str>>().join("\n");
190         let block_info = if lang.is_empty() {
191             LangString::all_false()
192         } else {
193             LangString::parse(lang)
194         };
195         if !block_info.rust {
196             buffer.push_str(&format!("<pre><code class=\"language-{}\">{}</code></pre>",
197                             lang, text));
198             return
199         }
200         PLAYGROUND.with(|play| {
201             // insert newline to clearly separate it from the
202             // previous block so we can shorten the html output
203             buffer.push('\n');
204             let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
205                 if url.is_empty() {
206                     return None;
207                 }
208                 let test = origtext.lines().map(|l| {
209                     stripped_filtered_line(l).unwrap_or(l)
210                 }).collect::<Vec<&str>>().join("\n");
211                 let krate = krate.as_ref().map(|s| &**s);
212                 let test = test::maketest(&test, krate, false,
213                                           &Default::default());
214                 let channel = if test.contains("#![feature(") {
215                     "&amp;version=nightly"
216                 } else {
217                     ""
218                 };
219                 // These characters don't need to be escaped in a URI.
220                 // FIXME: use a library function for percent encoding.
221                 fn dont_escape(c: u8) -> bool {
222                     (b'a' <= c && c <= b'z') ||
223                     (b'A' <= c && c <= b'Z') ||
224                     (b'0' <= c && c <= b'9') ||
225                     c == b'-' || c == b'_' || c == b'.' ||
226                     c == b'~' || c == b'!' || c == b'\'' ||
227                     c == b'(' || c == b')' || c == b'*'
228                 }
229                 let mut test_escaped = String::new();
230                 for b in test.bytes() {
231                     if dont_escape(b) {
232                         test_escaped.push(char::from(b));
233                     } else {
234                         write!(test_escaped, "%{:02X}", b).unwrap();
235                     }
236                 }
237                 Some(format!(
238                     r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
239                     url, test_escaped, channel
240                 ))
241             });
242             buffer.push_str(&highlight::render_with_highlighting(
243                             &text,
244                             Some("rust-example-rendered"),
245                             None,
246                             playground_button.as_ref().map(String::as_str)));
247         });
248     }
249
250     fn heading(parser: &mut ParserWrapper, buffer: &mut String,
251                toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle, level: i32) {
252         debug!("Heading");
253         let mut ret = String::new();
254         let mut id = String::new();
255         event_loop_break!(parser, toc_builder, shorter, ret, true, &mut Some(&mut id),
256                           Event::End(Tag::Header(_)));
257         ret = ret.trim_right().to_owned();
258
259         let id = id.chars().filter_map(|c| {
260             if c.is_alphanumeric() || c == '-' || c == '_' {
261                 if c.is_ascii() {
262                     Some(c.to_ascii_lowercase())
263                 } else {
264                     Some(c)
265                 }
266             } else if c.is_whitespace() && c.is_ascii() {
267                 Some('-')
268             } else {
269                 None
270             }
271         }).collect::<String>();
272
273         let id = derive_id(id);
274
275         let sec = toc_builder.as_mut().map_or("".to_owned(), |builder| {
276             format!("{} ", builder.push(level as u32, ret.clone(), id.clone()))
277         });
278
279         // Render the HTML
280         buffer.push_str(&format!("<h{lvl} id=\"{id}\" class=\"section-header\">\
281                                   <a href=\"#{id}\">{sec}{}</a></h{lvl}>",
282                                  ret, lvl = level, id = id, sec = sec));
283     }
284
285     fn inline_code(parser: &mut ParserWrapper, buffer: &mut String,
286                    toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
287                    id: &mut Option<&mut String>) {
288         debug!("InlineCode");
289         let mut content = String::new();
290         event_loop_break!(parser, toc_builder, shorter, content, false, id, Event::End(Tag::Code));
291         buffer.push_str(&format!("<code>{}</code>",
292                                  Escape(&collapse_whitespace(content.trim_right()))));
293     }
294
295     fn link(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
296             shorter: MarkdownOutputStyle, url: &str, title: &str,
297             id: &mut Option<&mut String>) {
298         debug!("Link");
299         let mut content = String::new();
300         event_loop_break!(parser, toc_builder, shorter, content, true, id,
301                           Event::End(Tag::Link(_, _)));
302         if title.is_empty() {
303             buffer.push_str(&format!("<a href=\"{}\">{}</a>", url, content));
304         } else {
305             buffer.push_str(&format!("<a href=\"{}\" title=\"{}\">{}</a>",
306                                      url, Escape(title), content));
307         }
308     }
309
310     fn image(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
311             shorter: MarkdownOutputStyle, url: &str, mut title: String,
312             id: &mut Option<&mut String>) {
313         debug!("Image");
314         event_loop_break!(parser, toc_builder, shorter, title, true, id,
315                           Event::End(Tag::Image(_, _)));
316         buffer.push_str(&format!("<img src=\"{}\" alt=\"{}\">", url, title));
317     }
318
319     fn paragraph(parser: &mut ParserWrapper, buffer: &mut String,
320                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
321                  id: &mut Option<&mut String>) {
322         debug!("Paragraph");
323         let mut content = String::new();
324         event_loop_break!(parser, toc_builder, shorter, content, true, id,
325                           Event::End(Tag::Paragraph));
326         buffer.push_str(&format!("<p>{}</p>", content.trim_right()));
327     }
328
329     fn table_cell(parser: &mut ParserWrapper, buffer: &mut String,
330                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
331         debug!("TableCell");
332         let mut content = String::new();
333         event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
334                           Event::End(Tag::TableHead) |
335                               Event::End(Tag::Table(_)) |
336                               Event::End(Tag::TableRow) |
337                               Event::End(Tag::TableCell));
338         buffer.push_str(&format!("<td>{}</td>", content.trim()));
339     }
340
341     fn table_row(parser: &mut ParserWrapper, buffer: &mut String,
342                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
343         debug!("TableRow");
344         let mut content = String::new();
345         while let Some(event) = parser.next() {
346             match event {
347                 Event::End(Tag::TableHead) |
348                     Event::End(Tag::Table(_)) |
349                     Event::End(Tag::TableRow) => break,
350                 Event::Start(Tag::TableCell) => {
351                     table_cell(parser, &mut content, toc_builder, shorter);
352                 }
353                 x => {
354                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
355                 }
356             }
357         }
358         buffer.push_str(&format!("<tr>{}</tr>", content));
359     }
360
361     fn table_head(parser: &mut ParserWrapper, buffer: &mut String,
362                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
363         debug!("TableHead");
364         let mut content = String::new();
365         while let Some(event) = parser.next() {
366             match event {
367                 Event::End(Tag::TableHead) | Event::End(Tag::Table(_)) => break,
368                 Event::Start(Tag::TableCell) => {
369                     table_cell(parser, &mut content, toc_builder, shorter);
370                 }
371                 x => {
372                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
373                 }
374             }
375         }
376         if !content.is_empty() {
377             buffer.push_str(&format!("<thead><tr>{}</tr></thead>", content.replace("td>", "th>")));
378         }
379     }
380
381     fn table(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
382              shorter: MarkdownOutputStyle) {
383         debug!("Table");
384         let mut content = String::new();
385         let mut rows = String::new();
386         while let Some(event) = parser.next() {
387             match event {
388                 Event::End(Tag::Table(_)) => break,
389                 Event::Start(Tag::TableHead) => {
390                     table_head(parser, &mut content, toc_builder, shorter);
391                 }
392                 Event::Start(Tag::TableRow) => {
393                     table_row(parser, &mut rows, toc_builder, shorter);
394                 }
395                 _ => {}
396             }
397         }
398         buffer.push_str(&format!("<table>{}{}</table>",
399                                  content,
400                                  if shorter.is_compact() || rows.is_empty() {
401                                      String::new()
402                                  } else {
403                                      format!("<tbody>{}</tbody>", rows)
404                                  }));
405     }
406
407     fn blockquote(parser: &mut ParserWrapper, buffer: &mut String,
408                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
409         debug!("BlockQuote");
410         let mut content = String::new();
411         event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
412                           Event::End(Tag::BlockQuote));
413         buffer.push_str(&format!("<blockquote>{}</blockquote>", content.trim_right()));
414     }
415
416     fn list_item(parser: &mut ParserWrapper, buffer: &mut String,
417                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
418         debug!("ListItem");
419         let mut content = String::new();
420         while let Some(event) = parser.next() {
421             match event {
422                 Event::End(Tag::Item) => break,
423                 Event::Text(ref s) => {
424                     content.push_str(&format!("{}", Escape(s)));
425                 }
426                 x => {
427                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
428                 }
429             }
430             if shorter.is_compact() {
431                 break
432             }
433         }
434         buffer.push_str(&format!("<li>{}</li>", content));
435     }
436
437     fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
438             shorter: MarkdownOutputStyle, is_sorted_list: bool) {
439         debug!("List");
440         let mut content = String::new();
441         while let Some(event) = parser.next() {
442             match event {
443                 Event::End(Tag::List(_)) => break,
444                 Event::Start(Tag::Item) => {
445                     list_item(parser, &mut content, toc_builder, shorter);
446                 }
447                 x => {
448                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
449                 }
450             }
451             if shorter.is_compact() {
452                 break
453             }
454         }
455         buffer.push_str(&format!("<{0}>{1}</{0}>",
456                                  if is_sorted_list { "ol" } else { "ul" },
457                                  content));
458     }
459
460     fn emphasis(parser: &mut ParserWrapper, buffer: &mut String,
461                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
462                 id: &mut Option<&mut String>) {
463         debug!("Emphasis");
464         let mut content = String::new();
465         event_loop_break!(parser, toc_builder, shorter, content, false, id,
466                           Event::End(Tag::Emphasis));
467         buffer.push_str(&format!("<em>{}</em>", content));
468     }
469
470     fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
471               shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
472         debug!("Strong");
473         let mut content = String::new();
474         event_loop_break!(parser, toc_builder, shorter, content, false, id,
475                           Event::End(Tag::Strong));
476         buffer.push_str(&format!("<strong>{}</strong>", content));
477     }
478
479     fn footnote(parser: &mut ParserWrapper, buffer: &mut String,
480                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
481                 id: &mut Option<&mut String>) {
482         debug!("FootnoteDefinition");
483         let mut content = String::new();
484         event_loop_break!(parser, toc_builder, shorter, content, true, id,
485                           Event::End(Tag::FootnoteDefinition(_)));
486         buffer.push_str(&content);
487     }
488
489     fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
490             shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
491         debug!("Rule");
492         let mut content = String::new();
493         event_loop_break!(parser, toc_builder, shorter, content, true, id,
494                           Event::End(Tag::Rule));
495         buffer.push_str("<hr>");
496     }
497
498     fn looper<'a>(parser: &'a mut ParserWrapper, buffer: &mut String, next_event: Option<Event<'a>>,
499                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
500                   id: &mut Option<&mut String>) -> bool {
501         if let Some(event) = next_event {
502             match event {
503                 Event::Start(Tag::CodeBlock(lang)) => {
504                     code_block(parser, buffer, &*lang);
505                 }
506                 Event::Start(Tag::Header(level)) => {
507                     heading(parser, buffer, toc_builder, shorter, level);
508                 }
509                 Event::Start(Tag::Code) => {
510                     inline_code(parser, buffer, toc_builder, shorter, id);
511                 }
512                 Event::Start(Tag::Paragraph) => {
513                     paragraph(parser, buffer, toc_builder, shorter, id);
514                 }
515                 Event::Start(Tag::Link(ref url, ref t)) => {
516                     link(parser, buffer, toc_builder, shorter, url, t.as_ref(), id);
517                 }
518                 Event::Start(Tag::Image(ref url, ref t)) => {
519                     image(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
520                 }
521                 Event::Start(Tag::Table(_)) => {
522                     table(parser, buffer, toc_builder, shorter);
523                 }
524                 Event::Start(Tag::BlockQuote) => {
525                     blockquote(parser, buffer, toc_builder, shorter);
526                 }
527                 Event::Start(Tag::List(x)) => {
528                     list(parser, buffer, toc_builder, shorter, x.is_some());
529                 }
530                 Event::Start(Tag::Emphasis) => {
531                     emphasis(parser, buffer, toc_builder, shorter, id);
532                 }
533                 Event::Start(Tag::Strong) => {
534                     strong(parser, buffer, toc_builder, shorter, id);
535                 }
536                 Event::Start(Tag::Rule) => {
537                     rule(parser, buffer, toc_builder, shorter, id);
538                 }
539                 Event::Start(Tag::FootnoteDefinition(ref def)) => {
540                     debug!("FootnoteDefinition");
541                     let mut content = String::new();
542                     let def = def.as_ref();
543                     footnote(parser, &mut content, toc_builder, shorter, id);
544                     let entry = parser.get_entry(def);
545                     let cur_id = (*entry).1;
546                     (*entry).0.push_str(&format!("<li id=\"ref{}\">{}&nbsp;<a href=\"#supref{0}\" \
547                                                   rev=\"footnote\">↩</a></p></li>",
548                                                  cur_id,
549                                                  if content.ends_with("</p>") {
550                                                      &content[..content.len() - 4]
551                                                  } else {
552                                                      &content
553                                                  }));
554                 }
555                 Event::FootnoteReference(ref reference) => {
556                     debug!("FootnoteReference");
557                     let entry = parser.get_entry(reference.as_ref());
558                     buffer.push_str(&format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}</a>\
559                                               </sup>",
560                                              (*entry).1));
561                 }
562                 Event::HardBreak => {
563                     debug!("HardBreak");
564                     if shorter.is_fancy() {
565                         buffer.push_str("<br>");
566                     } else if !buffer.is_empty() {
567                         buffer.push(' ');
568                     }
569                 }
570                 Event::Html(h) | Event::InlineHtml(h) => {
571                     debug!("Html/InlineHtml");
572                     buffer.push_str(&*h);
573                 }
574                 _ => {}
575             }
576             shorter.is_fancy()
577         } else {
578             false
579         }
580     }
581
582     let mut toc_builder = if print_toc {
583         Some(TocBuilder::new())
584     } else {
585         None
586     };
587     let mut buffer = String::new();
588     let mut parser = ParserWrapper::new(s);
589     loop {
590         let next_event = parser.next();
591         if !looper(&mut parser, &mut buffer, next_event, &mut toc_builder, shorter, &mut None) {
592             break
593         }
594     }
595     if !parser.footnotes.is_empty() {
596         let mut v: Vec<_> = parser.footnotes.values().collect();
597         v.sort_by(|a, b| a.1.cmp(&b.1));
598         buffer.push_str(&format!("<div class=\"footnotes\"><hr><ol>{}</ol></div>",
599                                  v.iter()
600                                   .map(|s| s.0.as_str())
601                                   .collect::<Vec<_>>()
602                                   .join("")));
603     }
604     let mut ret = toc_builder.map_or(Ok(()), |builder| {
605         write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
606     });
607
608     if ret.is_ok() {
609         ret = w.write_str(&buffer);
610     }
611     ret
612 }
613
614 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
615     tests.set_position(position);
616
617     let mut parser = Parser::new(doc);
618     let mut prev_offset = 0;
619     let mut nb_lines = 0;
620     let mut register_header = None;
621     'main: while let Some(event) = parser.next() {
622         match event {
623             Event::Start(Tag::CodeBlock(s)) => {
624                 let block_info = if s.is_empty() {
625                     LangString::all_false()
626                 } else {
627                     LangString::parse(&*s)
628                 };
629                 if !block_info.rust {
630                     continue
631                 }
632                 let mut test_s = String::new();
633                 let mut offset = None;
634                 loop {
635                     let event = parser.next();
636                     if let Some(event) = event {
637                         match event {
638                             Event::End(Tag::CodeBlock(_)) => break,
639                             Event::Text(ref s) => {
640                                 test_s.push_str(s);
641                                 if offset.is_none() {
642                                     offset = Some(parser.get_offset());
643                                 }
644                             }
645                             _ => {}
646                         }
647                     } else {
648                         break 'main;
649                     }
650                 }
651                 let offset = offset.unwrap_or(0);
652                 let lines = test_s.lines().map(|l| {
653                     stripped_filtered_line(l).unwrap_or(l)
654                 });
655                 let text = lines.collect::<Vec<&str>>().join("\n");
656                 nb_lines += doc[prev_offset..offset].lines().count();
657                 let line = tests.get_line() + (nb_lines - 1);
658                 let filename = tests.get_filename();
659                 tests.add_test(text.to_owned(),
660                                block_info.should_panic, block_info.no_run,
661                                block_info.ignore, block_info.test_harness,
662                                block_info.compile_fail, block_info.error_codes,
663                                line, filename);
664                 prev_offset = offset;
665             }
666             Event::Start(Tag::Header(level)) => {
667                 register_header = Some(level as u32);
668             }
669             Event::Text(ref s) if register_header.is_some() => {
670                 let level = register_header.unwrap();
671                 if s.is_empty() {
672                     tests.register_header("", level);
673                 } else {
674                     tests.register_header(s, level);
675                 }
676                 register_header = None;
677             }
678             _ => {}
679         }
680     }
681 }
682
683 #[derive(Eq, PartialEq, Clone, Debug)]
684 struct LangString {
685     original: String,
686     should_panic: bool,
687     no_run: bool,
688     ignore: bool,
689     rust: bool,
690     test_harness: bool,
691     compile_fail: bool,
692     error_codes: Vec<String>,
693 }
694
695 impl LangString {
696     fn all_false() -> LangString {
697         LangString {
698             original: String::new(),
699             should_panic: false,
700             no_run: false,
701             ignore: false,
702             rust: true,  // NB This used to be `notrust = false`
703             test_harness: false,
704             compile_fail: false,
705             error_codes: Vec::new(),
706         }
707     }
708
709     fn parse(string: &str) -> LangString {
710         let mut seen_rust_tags = false;
711         let mut seen_other_tags = false;
712         let mut data = LangString::all_false();
713         let mut allow_compile_fail = false;
714         let mut allow_error_code_check = false;
715         if UnstableFeatures::from_environment().is_nightly_build() {
716             allow_compile_fail = true;
717             allow_error_code_check = true;
718         }
719
720         data.original = string.to_owned();
721         let tokens = string.split(|c: char|
722             !(c == '_' || c == '-' || c.is_alphanumeric())
723         );
724
725         for token in tokens {
726             match token {
727                 "" => {},
728                 "should_panic" => { data.should_panic = true; seen_rust_tags = true; },
729                 "no_run" => { data.no_run = true; seen_rust_tags = true; },
730                 "ignore" => { data.ignore = true; seen_rust_tags = true; },
731                 "rust" => { data.rust = true; seen_rust_tags = true; },
732                 "test_harness" => { data.test_harness = true; seen_rust_tags = true; },
733                 "compile_fail" if allow_compile_fail => {
734                     data.compile_fail = true;
735                     seen_rust_tags = true;
736                     data.no_run = true;
737                 }
738                 x if allow_error_code_check && x.starts_with("E") && x.len() == 5 => {
739                     if let Ok(_) = x[1..].parse::<u32>() {
740                         data.error_codes.push(x.to_owned());
741                         seen_rust_tags = true;
742                     } else {
743                         seen_other_tags = true;
744                     }
745                 }
746                 _ => { seen_other_tags = true }
747             }
748         }
749
750         data.rust &= !seen_other_tags || seen_rust_tags;
751
752         data
753     }
754 }
755
756 impl<'a> fmt::Display for Markdown<'a> {
757     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
758         let Markdown(md, shorter) = *self;
759         // This is actually common enough to special-case
760         if md.is_empty() { return Ok(()) }
761         render(fmt, md, false, shorter)
762     }
763 }
764
765 impl<'a> fmt::Display for MarkdownWithToc<'a> {
766     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
767         let MarkdownWithToc(md) = *self;
768         render(fmt, md, true, MarkdownOutputStyle::Fancy)
769     }
770 }
771
772 impl<'a> fmt::Display for MarkdownHtml<'a> {
773     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
774         let MarkdownHtml(md) = *self;
775         // This is actually common enough to special-case
776         if md.is_empty() { return Ok(()) }
777         render(fmt, md, false, MarkdownOutputStyle::Fancy)
778     }
779 }
780
781 pub fn plain_summary_line(md: &str) -> String {
782     struct ParserWrapper<'a> {
783         inner: Parser<'a>,
784         is_in: isize,
785         is_first: bool,
786     }
787
788     impl<'a> Iterator for ParserWrapper<'a> {
789         type Item = String;
790
791         fn next(&mut self) -> Option<String> {
792             let next_event = self.inner.next();
793             if next_event.is_none() {
794                 return None
795             }
796             let next_event = next_event.unwrap();
797             let (ret, is_in) = match next_event {
798                 Event::Start(Tag::Paragraph) => (None, 1),
799                 Event::Start(Tag::Link(_, ref t)) if !self.is_first => {
800                     (Some(t.as_ref().to_owned()), 1)
801                 }
802                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
803                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
804                 Event::Start(Tag::Header(_)) => (None, 1),
805                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
806                 Event::End(Tag::Link(_, ref t)) => (Some(t.as_ref().to_owned()), -1),
807                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
808                 _ => (None, 0),
809             };
810             if is_in > 0 || (is_in < 0 && self.is_in > 0) {
811                 self.is_in += is_in;
812             }
813             if ret.is_some() {
814                 self.is_first = false;
815                 ret
816             } else {
817                 Some(String::new())
818             }
819         }
820     }
821     let mut s = String::with_capacity(md.len() * 3 / 2);
822     let mut p = ParserWrapper {
823         inner: Parser::new(md),
824         is_in: 0,
825         is_first: true,
826     };
827     while let Some(t) = p.next() {
828         if !t.is_empty() {
829             s.push_str(&t);
830         }
831     }
832     s
833 }
834
835 #[cfg(test)]
836 mod tests {
837     use super::{LangString, Markdown, MarkdownHtml, MarkdownOutputStyle};
838     use super::plain_summary_line;
839     use html::render::reset_ids;
840
841     #[test]
842     fn test_lang_string_parse() {
843         fn t(s: &str,
844             should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
845             compile_fail: bool, error_codes: Vec<String>) {
846             assert_eq!(LangString::parse(s), LangString {
847                 should_panic: should_panic,
848                 no_run: no_run,
849                 ignore: ignore,
850                 rust: rust,
851                 test_harness: test_harness,
852                 compile_fail: compile_fail,
853                 error_codes: error_codes,
854                 original: s.to_owned(),
855             })
856         }
857
858         // marker                | should_panic| no_run| ignore| rust | test_harness| compile_fail
859         //                       | error_codes
860         t("",                      false,        false,  false,  true,  false, false, Vec::new());
861         t("rust",                  false,        false,  false,  true,  false, false, Vec::new());
862         t("sh",                    false,        false,  false,  false, false, false, Vec::new());
863         t("ignore",                false,        false,  true,   true,  false, false, Vec::new());
864         t("should_panic",          true,         false,  false,  true,  false, false, Vec::new());
865         t("no_run",                false,        true,   false,  true,  false, false, Vec::new());
866         t("test_harness",          false,        false,  false,  true,  true,  false, Vec::new());
867         t("compile_fail",          false,        true,   false,  true,  false, true,  Vec::new());
868         t("{.no_run .example}",    false,        true,   false,  true,  false, false, Vec::new());
869         t("{.sh .should_panic}",   true,         false,  false,  true,  false, false, Vec::new());
870         t("{.example .rust}",      false,        false,  false,  true,  false, false, Vec::new());
871         t("{.test_harness .rust}", false,        false,  false,  true,  true,  false, Vec::new());
872     }
873
874     #[test]
875     fn issue_17736() {
876         let markdown = "# title";
877         format!("{}", Markdown(markdown, MarkdownOutputStyle::Fancy));
878         reset_ids(true);
879     }
880
881     #[test]
882     fn test_header() {
883         fn t(input: &str, expect: &str) {
884             let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
885             assert_eq!(output, expect, "original: {}", input);
886             reset_ids(true);
887         }
888
889         t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
890           <a href=\"#foo-bar\">Foo bar</a></h1>");
891         t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
892           header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
893         t("### **Foo** *bar* baz!?!& -_qux_-%",
894           "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
895           <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
896           <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
897         t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
898           "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
899           <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
900           <em><code>baz</code></em> ❤ #qux</a></h4>");
901     }
902
903     #[test]
904     fn test_header_ids_multiple_blocks() {
905         fn t(input: &str, expect: &str) {
906             let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
907             assert_eq!(output, expect, "original: {}", input);
908         }
909
910         let test = || {
911             t("# Example", "<h1 id=\"example\" class=\"section-header\">\
912               <a href=\"#example\">Example</a></h1>");
913             t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
914               <a href=\"#panics\">Panics</a></h1>");
915             t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
916               <a href=\"#example-1\">Example</a></h1>");
917             t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
918               <a href=\"#main-1\">Main</a></h1>");
919             t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
920               <a href=\"#example-2\">Example</a></h1>");
921             t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
922               <a href=\"#panics-1\">Panics</a></h1>");
923         };
924         test();
925         reset_ids(true);
926         test();
927     }
928
929     #[test]
930     fn test_plain_summary_line() {
931         fn t(input: &str, expect: &str) {
932             let output = plain_summary_line(input);
933             assert_eq!(output, expect, "original: {}", input);
934         }
935
936         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
937         t("code `let x = i32;` ...", "code `let x = i32;` ...");
938         t("type `Type<'static>` ...", "type `Type<'static>` ...");
939         t("# top header", "top header");
940         t("## header", "header");
941     }
942
943     #[test]
944     fn test_markdown_html_escape() {
945         fn t(input: &str, expect: &str) {
946             let output = format!("{}", MarkdownHtml(input));
947             assert_eq!(output, expect, "original: {}", input);
948         }
949
950         t("`Struct<'a, T>`", "<p><code>Struct&lt;&#39;a, T&gt;</code></p>");
951         t("Struct<'a, T>", "<p>Struct&lt;&#39;a, T&gt;</p>");
952     }
953 }