]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
Rollup merge of #40981 - Technius:master, r=steveklabnik
[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         }
431         buffer.push_str(&format!("<li>{}</li>", content));
432     }
433
434     fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
435             shorter: MarkdownOutputStyle) {
436         debug!("List");
437         let mut content = String::new();
438         while let Some(event) = parser.next() {
439             match event {
440                 Event::End(Tag::List(_)) => break,
441                 Event::Start(Tag::Item) => {
442                     list_item(parser, &mut content, toc_builder, shorter);
443                 }
444                 x => {
445                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
446                 }
447             }
448         }
449         buffer.push_str(&format!("<ul>{}</ul>", content));
450     }
451
452     fn emphasis(parser: &mut ParserWrapper, buffer: &mut String,
453                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
454                 id: &mut Option<&mut String>) {
455         debug!("Emphasis");
456         let mut content = String::new();
457         event_loop_break!(parser, toc_builder, shorter, content, false, id,
458                           Event::End(Tag::Emphasis));
459         buffer.push_str(&format!("<em>{}</em>", content));
460     }
461
462     fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
463               shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
464         debug!("Strong");
465         let mut content = String::new();
466         event_loop_break!(parser, toc_builder, shorter, content, false, id,
467                           Event::End(Tag::Strong));
468         buffer.push_str(&format!("<strong>{}</strong>", content));
469     }
470
471     fn footnote(parser: &mut ParserWrapper, buffer: &mut String,
472                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
473                 id: &mut Option<&mut String>) {
474         debug!("FootnoteDefinition");
475         let mut content = String::new();
476         event_loop_break!(parser, toc_builder, shorter, content, true, id,
477                           Event::End(Tag::FootnoteDefinition(_)));
478         buffer.push_str(&content);
479     }
480
481     fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
482             shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
483         debug!("Rule");
484         let mut content = String::new();
485         event_loop_break!(parser, toc_builder, shorter, content, true, id,
486                           Event::End(Tag::Rule));
487         buffer.push_str("<hr>");
488     }
489
490     fn looper<'a>(parser: &'a mut ParserWrapper, buffer: &mut String, next_event: Option<Event<'a>>,
491                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
492                   id: &mut Option<&mut String>) -> bool {
493         if let Some(event) = next_event {
494             match event {
495                 Event::Start(Tag::CodeBlock(lang)) => {
496                     code_block(parser, buffer, &*lang);
497                 }
498                 Event::Start(Tag::Header(level)) => {
499                     heading(parser, buffer, toc_builder, shorter, level);
500                 }
501                 Event::Start(Tag::Code) => {
502                     inline_code(parser, buffer, toc_builder, shorter, id);
503                 }
504                 Event::Start(Tag::Paragraph) => {
505                     paragraph(parser, buffer, toc_builder, shorter, id);
506                 }
507                 Event::Start(Tag::Link(ref url, ref t)) => {
508                     link(parser, buffer, toc_builder, shorter, url, t.as_ref(), id);
509                 }
510                 Event::Start(Tag::Image(ref url, ref t)) => {
511                     image(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
512                 }
513                 Event::Start(Tag::Table(_)) => {
514                     table(parser, buffer, toc_builder, shorter);
515                 }
516                 Event::Start(Tag::BlockQuote) => {
517                     blockquote(parser, buffer, toc_builder, shorter);
518                 }
519                 Event::Start(Tag::List(_)) => {
520                     list(parser, buffer, toc_builder, shorter);
521                 }
522                 Event::Start(Tag::Emphasis) => {
523                     emphasis(parser, buffer, toc_builder, shorter, id);
524                 }
525                 Event::Start(Tag::Strong) => {
526                     strong(parser, buffer, toc_builder, shorter, id);
527                 }
528                 Event::Start(Tag::Rule) => {
529                     rule(parser, buffer, toc_builder, shorter, id);
530                 }
531                 Event::Start(Tag::FootnoteDefinition(ref def)) => {
532                     debug!("FootnoteDefinition");
533                     let mut content = String::new();
534                     let def = def.as_ref();
535                     footnote(parser, &mut content, toc_builder, shorter, id);
536                     let entry = parser.get_entry(def);
537                     let cur_id = (*entry).1;
538                     (*entry).0.push_str(&format!("<li id=\"ref{}\">{}&nbsp;<a href=\"#supref{0}\" \
539                                                   rev=\"footnote\">↩</a></p></li>",
540                                                  cur_id,
541                                                  if content.ends_with("</p>") {
542                                                      &content[..content.len() - 4]
543                                                  } else {
544                                                      &content
545                                                  }));
546                 }
547                 Event::FootnoteReference(ref reference) => {
548                     debug!("FootnoteReference");
549                     let entry = parser.get_entry(reference.as_ref());
550                     buffer.push_str(&format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}</a>\
551                                               </sup>",
552                                              (*entry).1));
553                 }
554                 Event::HardBreak => {
555                     debug!("HardBreak");
556                     if shorter.is_fancy() {
557                         buffer.push_str("<br>");
558                     } else if !buffer.is_empty() {
559                         buffer.push(' ');
560                     }
561                 }
562                 Event::Html(h) | Event::InlineHtml(h) => {
563                     debug!("Html/InlineHtml");
564                     buffer.push_str(&*h);
565                 }
566                 _ => {}
567             }
568             shorter.is_fancy()
569         } else {
570             false
571         }
572     }
573
574     let mut toc_builder = if print_toc {
575         Some(TocBuilder::new())
576     } else {
577         None
578     };
579     let mut buffer = String::new();
580     let mut parser = ParserWrapper::new(s);
581     loop {
582         let next_event = parser.next();
583         if !looper(&mut parser, &mut buffer, next_event, &mut toc_builder, shorter, &mut None) {
584             break
585         }
586     }
587     if !parser.footnotes.is_empty() {
588         let mut v: Vec<_> = parser.footnotes.values().collect();
589         v.sort_by(|a, b| a.1.cmp(&b.1));
590         buffer.push_str(&format!("<div class=\"footnotes\"><hr><ol>{}</ol></div>",
591                                  v.iter()
592                                   .map(|s| s.0.as_str())
593                                   .collect::<Vec<_>>()
594                                   .join("")));
595     }
596     let mut ret = toc_builder.map_or(Ok(()), |builder| {
597         write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
598     });
599
600     if ret.is_ok() {
601         ret = w.write_str(&buffer);
602     }
603     ret
604 }
605
606 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
607     tests.set_position(position);
608
609     let mut parser = Parser::new(doc);
610     let mut prev_offset = 0;
611     let mut nb_lines = 0;
612     let mut register_header = None;
613     'main: while let Some(event) = parser.next() {
614         match event {
615             Event::Start(Tag::CodeBlock(s)) => {
616                 let block_info = if s.is_empty() {
617                     LangString::all_false()
618                 } else {
619                     LangString::parse(&*s)
620                 };
621                 if !block_info.rust {
622                     continue
623                 }
624                 let mut test_s = String::new();
625                 let mut offset = None;
626                 loop {
627                     let event = parser.next();
628                     if let Some(event) = event {
629                         match event {
630                             Event::End(Tag::CodeBlock(_)) => break,
631                             Event::Text(ref s) => {
632                                 test_s.push_str(s);
633                                 if offset.is_none() {
634                                     offset = Some(parser.get_offset());
635                                 }
636                             }
637                             _ => {}
638                         }
639                     } else {
640                         break 'main;
641                     }
642                 }
643                 let offset = offset.unwrap_or(0);
644                 let lines = test_s.lines().map(|l| {
645                     stripped_filtered_line(l).unwrap_or(l)
646                 });
647                 let text = lines.collect::<Vec<&str>>().join("\n");
648                 nb_lines += doc[prev_offset..offset].lines().count();
649                 let line = tests.get_line() + (nb_lines - 1);
650                 let filename = tests.get_filename();
651                 tests.add_test(text.to_owned(),
652                                block_info.should_panic, block_info.no_run,
653                                block_info.ignore, block_info.test_harness,
654                                block_info.compile_fail, block_info.error_codes,
655                                line, filename);
656                 prev_offset = offset;
657             }
658             Event::Start(Tag::Header(level)) => {
659                 register_header = Some(level as u32);
660             }
661             Event::Text(ref s) if register_header.is_some() => {
662                 let level = register_header.unwrap();
663                 if s.is_empty() {
664                     tests.register_header("", level);
665                 } else {
666                     tests.register_header(s, level);
667                 }
668                 register_header = None;
669             }
670             _ => {}
671         }
672     }
673 }
674
675 #[derive(Eq, PartialEq, Clone, Debug)]
676 struct LangString {
677     original: String,
678     should_panic: bool,
679     no_run: bool,
680     ignore: bool,
681     rust: bool,
682     test_harness: bool,
683     compile_fail: bool,
684     error_codes: Vec<String>,
685 }
686
687 impl LangString {
688     fn all_false() -> LangString {
689         LangString {
690             original: String::new(),
691             should_panic: false,
692             no_run: false,
693             ignore: false,
694             rust: true,  // NB This used to be `notrust = false`
695             test_harness: false,
696             compile_fail: false,
697             error_codes: Vec::new(),
698         }
699     }
700
701     fn parse(string: &str) -> LangString {
702         let mut seen_rust_tags = false;
703         let mut seen_other_tags = false;
704         let mut data = LangString::all_false();
705         let mut allow_compile_fail = false;
706         let mut allow_error_code_check = false;
707         if UnstableFeatures::from_environment().is_nightly_build() {
708             allow_compile_fail = true;
709             allow_error_code_check = true;
710         }
711
712         data.original = string.to_owned();
713         let tokens = string.split(|c: char|
714             !(c == '_' || c == '-' || c.is_alphanumeric())
715         );
716
717         for token in tokens {
718             match token {
719                 "" => {},
720                 "should_panic" => { data.should_panic = true; seen_rust_tags = true; },
721                 "no_run" => { data.no_run = true; seen_rust_tags = true; },
722                 "ignore" => { data.ignore = true; seen_rust_tags = true; },
723                 "rust" => { data.rust = true; seen_rust_tags = true; },
724                 "test_harness" => { data.test_harness = true; seen_rust_tags = true; },
725                 "compile_fail" if allow_compile_fail => {
726                     data.compile_fail = true;
727                     seen_rust_tags = true;
728                     data.no_run = true;
729                 }
730                 x if allow_error_code_check && x.starts_with("E") && x.len() == 5 => {
731                     if let Ok(_) = x[1..].parse::<u32>() {
732                         data.error_codes.push(x.to_owned());
733                         seen_rust_tags = true;
734                     } else {
735                         seen_other_tags = true;
736                     }
737                 }
738                 _ => { seen_other_tags = true }
739             }
740         }
741
742         data.rust &= !seen_other_tags || seen_rust_tags;
743
744         data
745     }
746 }
747
748 impl<'a> fmt::Display for Markdown<'a> {
749     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
750         let Markdown(md, shorter) = *self;
751         // This is actually common enough to special-case
752         if md.is_empty() { return Ok(()) }
753         render(fmt, md, false, shorter)
754     }
755 }
756
757 impl<'a> fmt::Display for MarkdownWithToc<'a> {
758     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
759         let MarkdownWithToc(md) = *self;
760         render(fmt, md, true, MarkdownOutputStyle::Fancy)
761     }
762 }
763
764 impl<'a> fmt::Display for MarkdownHtml<'a> {
765     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
766         let MarkdownHtml(md) = *self;
767         // This is actually common enough to special-case
768         if md.is_empty() { return Ok(()) }
769         render(fmt, md, false, MarkdownOutputStyle::Fancy)
770     }
771 }
772
773 pub fn plain_summary_line(md: &str) -> String {
774     struct ParserWrapper<'a> {
775         inner: Parser<'a>,
776         is_in: isize,
777         is_first: bool,
778     }
779
780     impl<'a> Iterator for ParserWrapper<'a> {
781         type Item = String;
782
783         fn next(&mut self) -> Option<String> {
784             let next_event = self.inner.next();
785             if next_event.is_none() {
786                 return None
787             }
788             let next_event = next_event.unwrap();
789             let (ret, is_in) = match next_event {
790                 Event::Start(Tag::Paragraph) => (None, 1),
791                 Event::Start(Tag::Link(_, ref t)) if !self.is_first => {
792                     (Some(t.as_ref().to_owned()), 1)
793                 }
794                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
795                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
796                 Event::Start(Tag::Header(_)) => (None, 1),
797                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
798                 Event::End(Tag::Link(_, ref t)) => (Some(t.as_ref().to_owned()), -1),
799                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
800                 _ => (None, 0),
801             };
802             if is_in > 0 || (is_in < 0 && self.is_in > 0) {
803                 self.is_in += is_in;
804             }
805             if ret.is_some() {
806                 self.is_first = false;
807                 ret
808             } else {
809                 Some(String::new())
810             }
811         }
812     }
813     let mut s = String::with_capacity(md.len() * 3 / 2);
814     let mut p = ParserWrapper {
815         inner: Parser::new(md),
816         is_in: 0,
817         is_first: true,
818     };
819     while let Some(t) = p.next() {
820         if !t.is_empty() {
821             s.push_str(&t);
822         }
823     }
824     s
825 }
826
827 #[cfg(test)]
828 mod tests {
829     use super::{LangString, Markdown, MarkdownHtml, MarkdownOutputStyle};
830     use super::plain_summary_line;
831     use html::render::reset_ids;
832
833     #[test]
834     fn test_lang_string_parse() {
835         fn t(s: &str,
836             should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
837             compile_fail: bool, error_codes: Vec<String>) {
838             assert_eq!(LangString::parse(s), LangString {
839                 should_panic: should_panic,
840                 no_run: no_run,
841                 ignore: ignore,
842                 rust: rust,
843                 test_harness: test_harness,
844                 compile_fail: compile_fail,
845                 error_codes: error_codes,
846                 original: s.to_owned(),
847             })
848         }
849
850         // marker                | should_panic| no_run| ignore| rust | test_harness| compile_fail
851         //                       | error_codes
852         t("",                      false,        false,  false,  true,  false, false, Vec::new());
853         t("rust",                  false,        false,  false,  true,  false, false, Vec::new());
854         t("sh",                    false,        false,  false,  false, false, false, Vec::new());
855         t("ignore",                false,        false,  true,   true,  false, false, Vec::new());
856         t("should_panic",          true,         false,  false,  true,  false, false, Vec::new());
857         t("no_run",                false,        true,   false,  true,  false, false, Vec::new());
858         t("test_harness",          false,        false,  false,  true,  true,  false, Vec::new());
859         t("compile_fail",          false,        true,   false,  true,  false, true,  Vec::new());
860         t("{.no_run .example}",    false,        true,   false,  true,  false, false, Vec::new());
861         t("{.sh .should_panic}",   true,         false,  false,  true,  false, false, Vec::new());
862         t("{.example .rust}",      false,        false,  false,  true,  false, false, Vec::new());
863         t("{.test_harness .rust}", false,        false,  false,  true,  true,  false, Vec::new());
864     }
865
866     #[test]
867     fn issue_17736() {
868         let markdown = "# title";
869         format!("{}", Markdown(markdown, MarkdownOutputStyle::Fancy));
870         reset_ids(true);
871     }
872
873     #[test]
874     fn test_header() {
875         fn t(input: &str, expect: &str) {
876             let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
877             assert_eq!(output, expect, "original: {}", input);
878             reset_ids(true);
879         }
880
881         t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
882           <a href=\"#foo-bar\">Foo bar</a></h1>");
883         t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
884           header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
885         t("### **Foo** *bar* baz!?!& -_qux_-%",
886           "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
887           <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
888           <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
889         t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
890           "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
891           <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
892           <em><code>baz</code></em> ❤ #qux</a></h4>");
893     }
894
895     #[test]
896     fn test_header_ids_multiple_blocks() {
897         fn t(input: &str, expect: &str) {
898             let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
899             assert_eq!(output, expect, "original: {}", input);
900         }
901
902         let test = || {
903             t("# Example", "<h1 id=\"example\" class=\"section-header\">\
904               <a href=\"#example\">Example</a></h1>");
905             t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
906               <a href=\"#panics\">Panics</a></h1>");
907             t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
908               <a href=\"#example-1\">Example</a></h1>");
909             t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
910               <a href=\"#main-1\">Main</a></h1>");
911             t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
912               <a href=\"#example-2\">Example</a></h1>");
913             t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
914               <a href=\"#panics-1\">Panics</a></h1>");
915         };
916         test();
917         reset_ids(true);
918         test();
919     }
920
921     #[test]
922     fn test_plain_summary_line() {
923         fn t(input: &str, expect: &str) {
924             let output = plain_summary_line(input);
925             assert_eq!(output, expect, "original: {}", input);
926         }
927
928         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
929         t("code `let x = i32;` ...", "code `let x = i32;` ...");
930         t("type `Type<'static>` ...", "type `Type<'static>` ...");
931         t("# top header", "top header");
932         t("## header", "header");
933     }
934
935     #[test]
936     fn test_markdown_html_escape() {
937         fn t(input: &str, expect: &str) {
938             let output = format!("{}", MarkdownHtml(input));
939             assert_eq!(output, expect, "original: {}", input);
940         }
941
942         t("`Struct<'a, T>`", "<p><code>Struct&lt;&#39;a, T&gt;</code></p>");
943         t("Struct<'a, T>", "<p>Struct&lt;&#39;a, T&gt;</p>");
944     }
945 }