]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
Fix multiple footnotes and improve testing
[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                     inner($id, s);
120                     if $escape {
121                         $buf.push_str(&format!("{}", Escape(s)));
122                     } else {
123                         $buf.push_str(s);
124                     }
125                 }
126                 Event::SoftBreak | Event::HardBreak if !$buf.is_empty() => {
127                     $buf.push(' ');
128                 }
129                 x => {
130                     looper($parser, &mut $buf, Some(x), $toc_builder, $shorter, $id);
131                 }
132             }
133         }
134     }}
135 }
136
137 struct ParserWrapper<'a> {
138     parser: Parser<'a>,
139     // The key is the footnote reference. The value is the footnote definition and the id.
140     footnotes: HashMap<String, (String, u16)>,
141 }
142
143 impl<'a> ParserWrapper<'a> {
144     pub fn new(s: &'a str) -> ParserWrapper<'a> {
145         ParserWrapper {
146             parser: Parser::new_ext(s, pulldown_cmark::OPTION_ENABLE_TABLES |
147                                        pulldown_cmark::OPTION_ENABLE_FOOTNOTES),
148             footnotes: HashMap::new(),
149         }
150     }
151
152     pub fn next(&mut self) -> Option<Event<'a>> {
153         self.parser.next()
154     }
155
156     pub fn get_entry(&mut self, key: &str) -> &mut (String, u16) {
157         let new_id = self.footnotes.keys().count() + 1;
158         let key = key.to_owned();
159         self.footnotes.entry(key).or_insert((String::new(), new_id as u16))
160     }
161 }
162
163 pub fn render(w: &mut fmt::Formatter,
164               s: &str,
165               print_toc: bool,
166               shorter: MarkdownOutputStyle) -> fmt::Result {
167     fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
168         let mut origtext = String::new();
169         while let Some(event) = parser.next() {
170             match event {
171                 Event::End(Tag::CodeBlock(_)) => break,
172                 Event::Text(ref s) => {
173                     origtext.push_str(s);
174                 }
175                 _ => {}
176             }
177         }
178         let origtext = origtext.trim_left();
179         debug!("docblock: ==============\n{:?}\n=======", origtext);
180
181         let lines = origtext.lines().filter(|l| {
182             stripped_filtered_line(*l).is_none()
183         });
184         let text = lines.collect::<Vec<&str>>().join("\n");
185         let block_info = if lang.is_empty() {
186             LangString::all_false()
187         } else {
188             LangString::parse(lang)
189         };
190         if !block_info.rust {
191             buffer.push_str(&format!("<pre><code class=\"language-{}\">{}</code></pre>",
192                             lang, text));
193             return
194         }
195         PLAYGROUND.with(|play| {
196             // insert newline to clearly separate it from the
197             // previous block so we can shorten the html output
198             buffer.push('\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().map(|l| {
204                     stripped_filtered_line(l).unwrap_or(l)
205                 }).collect::<Vec<&str>>().join("\n");
206                 let krate = krate.as_ref().map(|s| &**s);
207                 let test = test::maketest(&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             buffer.push_str(&highlight::render_with_highlighting(
238                             &text,
239                             Some("rust-example-rendered"),
240                             None,
241                             playground_button.as_ref().map(String::as_str)));
242         });
243     }
244
245     fn heading(parser: &mut ParserWrapper, buffer: &mut String,
246                toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle, level: i32) {
247         let mut ret = String::new();
248         let mut id = String::new();
249         event_loop_break!(parser, toc_builder, shorter, ret, true, &mut Some(&mut id),
250                           Event::End(Tag::Header(_)));
251         ret = ret.trim_right().to_owned();
252
253         let id = id.chars().filter_map(|c| {
254             if c.is_alphanumeric() || c == '-' || c == '_' {
255                 if c.is_ascii() {
256                     Some(c.to_ascii_lowercase())
257                 } else {
258                     Some(c)
259                 }
260             } else if c.is_whitespace() && c.is_ascii() {
261                 Some('-')
262             } else {
263                 None
264             }
265         }).collect::<String>();
266
267         let id = derive_id(id);
268
269         let sec = toc_builder.as_mut().map_or("".to_owned(), |builder| {
270             format!("{} ", builder.push(level as u32, ret.clone(), id.clone()))
271         });
272
273         // Render the HTML
274         buffer.push_str(&format!("<h{lvl} id=\"{id}\" class=\"section-header\">\
275                                   <a href=\"#{id}\">{sec}{}</a></h{lvl}>",
276                                  ret, lvl = level, id = id, sec = sec));
277     }
278
279     fn inline_code(parser: &mut ParserWrapper, buffer: &mut String,
280                    toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
281                    id: &mut Option<&mut String>) {
282         let mut content = String::new();
283         event_loop_break!(parser, toc_builder, shorter, content, false, id, Event::End(Tag::Code));
284         buffer.push_str(&format!("<code>{}</code>",
285                                  Escape(&collapse_whitespace(content.trim_right()))));
286     }
287
288     fn link(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
289             shorter: MarkdownOutputStyle, url: &str, title: &str,
290             id: &mut Option<&mut String>) {
291         let mut content = String::new();
292         event_loop_break!(parser, toc_builder, shorter, content, true, id,
293                           Event::End(Tag::Link(_, _)));
294         if title.is_empty() {
295             buffer.push_str(&format!("<a href=\"{}\">{}</a>", url, content));
296         } else {
297             buffer.push_str(&format!("<a href=\"{}\" title=\"{}\">{}</a>",
298                                      url, Escape(title), content));
299         }
300     }
301
302     fn image(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
303             shorter: MarkdownOutputStyle, url: &str, mut title: String,
304             id: &mut Option<&mut String>) {
305         event_loop_break!(parser, toc_builder, shorter, title, true, id,
306                           Event::End(Tag::Image(_, _)));
307         buffer.push_str(&format!("<img src=\"{}\" alt=\"{}\">", url, title));
308     }
309
310     fn paragraph(parser: &mut ParserWrapper, buffer: &mut String,
311                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
312                  id: &mut Option<&mut String>) {
313         let mut content = String::new();
314         event_loop_break!(parser, toc_builder, shorter, content, true, id,
315                           Event::End(Tag::Paragraph));
316         buffer.push_str(&format!("<p>{}</p>", content.trim_right()));
317     }
318
319     fn table_cell(parser: &mut ParserWrapper, buffer: &mut String,
320                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
321         let mut content = String::new();
322         event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
323                           Event::End(Tag::TableHead) |
324                               Event::End(Tag::Table(_)) |
325                               Event::End(Tag::TableRow) |
326                               Event::End(Tag::TableCell));
327         buffer.push_str(&format!("<td>{}</td>", content.trim()));
328     }
329
330     fn table_row(parser: &mut ParserWrapper, buffer: &mut String,
331                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
332         let mut content = String::new();
333         while let Some(event) = parser.next() {
334             match event {
335                 Event::End(Tag::TableHead) |
336                     Event::End(Tag::Table(_)) |
337                     Event::End(Tag::TableRow) => break,
338                 Event::Start(Tag::TableCell) => {
339                     table_cell(parser, &mut content, toc_builder, shorter);
340                 }
341                 x => {
342                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
343                 }
344             }
345         }
346         buffer.push_str(&format!("<tr>{}</tr>", content));
347     }
348
349     fn table_head(parser: &mut ParserWrapper, buffer: &mut String,
350                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
351         let mut content = String::new();
352         while let Some(event) = parser.next() {
353             match event {
354                 Event::End(Tag::TableHead) | Event::End(Tag::Table(_)) => break,
355                 Event::Start(Tag::TableCell) => {
356                     table_cell(parser, &mut content, toc_builder, shorter);
357                 }
358                 x => {
359                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
360                 }
361             }
362         }
363         if !content.is_empty() {
364             buffer.push_str(&format!("<thead><tr>{}</tr></thead>", content.replace("td>", "th>")));
365         }
366     }
367
368     fn table(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
369              shorter: MarkdownOutputStyle) {
370         let mut content = String::new();
371         let mut rows = String::new();
372         while let Some(event) = parser.next() {
373             match event {
374                 Event::End(Tag::Table(_)) => break,
375                 Event::Start(Tag::TableHead) => {
376                     table_head(parser, &mut content, toc_builder, shorter);
377                 }
378                 Event::Start(Tag::TableRow) => {
379                     table_row(parser, &mut rows, toc_builder, shorter);
380                 }
381                 _ => {}
382             }
383         }
384         buffer.push_str(&format!("<table>{}{}</table>",
385                                  content,
386                                  if shorter.is_compact() || rows.is_empty() {
387                                      String::new()
388                                  } else {
389                                      format!("<tbody>{}</tbody>", rows)
390                                  }));
391     }
392
393     fn blockquote(parser: &mut ParserWrapper, buffer: &mut String,
394                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
395         let mut content = String::new();
396         event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
397                           Event::End(Tag::BlockQuote));
398         buffer.push_str(&format!("<blockquote>{}</blockquote>", content.trim_right()));
399     }
400
401     fn list_item(parser: &mut ParserWrapper, buffer: &mut String,
402                  toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
403         let mut content = String::new();
404         while let Some(event) = parser.next() {
405             match event {
406                 Event::End(Tag::Item) => break,
407                 Event::Text(ref s) => {
408                     content.push_str(&format!("{}", Escape(s)));
409                 }
410                 x => {
411                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
412                 }
413             }
414         }
415         buffer.push_str(&format!("<li>{}</li>", content));
416     }
417
418     fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
419             shorter: MarkdownOutputStyle) {
420         let mut content = String::new();
421         while let Some(event) = parser.next() {
422             match event {
423                 Event::End(Tag::List(_)) => break,
424                 Event::Start(Tag::Item) => {
425                     list_item(parser, &mut content, toc_builder, shorter);
426                 }
427                 x => {
428                     looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
429                 }
430             }
431         }
432         buffer.push_str(&format!("<ul>{}</ul>", content));
433     }
434
435     fn emphasis(parser: &mut ParserWrapper, buffer: &mut String,
436                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
437                 id: &mut Option<&mut String>) {
438         let mut content = String::new();
439         event_loop_break!(parser, toc_builder, shorter, content, false, id,
440                           Event::End(Tag::Emphasis));
441         buffer.push_str(&format!("<em>{}</em>", content));
442     }
443
444     fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
445               shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
446         let mut content = String::new();
447         event_loop_break!(parser, toc_builder, shorter, content, false, id,
448                           Event::End(Tag::Strong));
449         buffer.push_str(&format!("<strong>{}</strong>", content));
450     }
451
452     fn footnote(parser: &mut ParserWrapper, buffer: &mut String,
453                 toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
454                 id: &mut Option<&mut String>) {
455         let mut content = String::new();
456         event_loop_break!(parser, toc_builder, shorter, content, true, id,
457                           Event::End(Tag::FootnoteDefinition(_)));
458         buffer.push_str(&content);
459     }
460
461     fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
462             shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
463         let mut content = String::new();
464         event_loop_break!(parser, toc_builder, shorter, content, true, id,
465                           Event::End(Tag::Rule));
466         buffer.push_str("<hr>");
467     }
468
469     fn looper<'a>(parser: &'a mut ParserWrapper, buffer: &mut String, next_event: Option<Event<'a>>,
470                   toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
471                   id: &mut Option<&mut String>) -> bool {
472         if let Some(event) = next_event {
473             match event {
474                 Event::Start(Tag::CodeBlock(lang)) => {
475                     code_block(parser, buffer, &*lang);
476                 }
477                 Event::Start(Tag::Header(level)) => {
478                     heading(parser, buffer, toc_builder, shorter, level);
479                 }
480                 Event::Start(Tag::Code) => {
481                     inline_code(parser, buffer, toc_builder, shorter, id);
482                 }
483                 Event::Start(Tag::Paragraph) => {
484                     paragraph(parser, buffer, toc_builder, shorter, id);
485                 }
486                 Event::Start(Tag::Link(ref url, ref t)) => {
487                     link(parser, buffer, toc_builder, shorter, url, t.as_ref(), id);
488                 }
489                 Event::Start(Tag::Image(ref url, ref t)) => {
490                     image(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
491                 }
492                 Event::Start(Tag::Table(_)) => {
493                     table(parser, buffer, toc_builder, shorter);
494                 }
495                 Event::Start(Tag::BlockQuote) => {
496                     blockquote(parser, buffer, toc_builder, shorter);
497                 }
498                 Event::Start(Tag::List(_)) => {
499                     list(parser, buffer, toc_builder, shorter);
500                 }
501                 Event::Start(Tag::Emphasis) => {
502                     emphasis(parser, buffer, toc_builder, shorter, id);
503                 }
504                 Event::Start(Tag::Strong) => {
505                     strong(parser, buffer, toc_builder, shorter, id);
506                 }
507                 Event::Start(Tag::Rule) => {
508                     rule(parser, buffer, toc_builder, shorter, id);
509                 }
510                 Event::Start(Tag::FootnoteDefinition(ref def)) => {
511                     let mut content = String::new();
512                     let def = def.as_ref();
513                     footnote(parser, &mut content, toc_builder, shorter, id);
514                     let entry = parser.get_entry(def);
515                     let cur_id = (*entry).1;
516                     (*entry).0.push_str(&format!("<li id=\"ref{}\">{}&nbsp;<a href=\"#supref{0}\" \
517                                                   rev=\"footnote\">↩</a></p></li>",
518                                                  cur_id,
519                                                  if content.ends_with("</p>") {
520                                                      &content[..content.len() - 4]
521                                                  } else {
522                                                      &content
523                                                  }));
524                 }
525                 Event::FootnoteReference(ref reference) => {
526                     let entry = parser.get_entry(reference.as_ref());
527                     buffer.push_str(&format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}</a>\
528                                               </sup>",
529                                              (*entry).1));
530                 }
531                 Event::Html(h) | Event::InlineHtml(h) => {
532                     buffer.push_str(&*h);
533                 }
534                 _ => {}
535             }
536             shorter.is_fancy()
537         } else {
538             false
539         }
540     }
541
542     let mut toc_builder = if print_toc {
543         Some(TocBuilder::new())
544     } else {
545         None
546     };
547     let mut buffer = String::new();
548     let mut parser = ParserWrapper::new(s);
549     loop {
550         let next_event = parser.next();
551         if !looper(&mut parser, &mut buffer, next_event, &mut toc_builder, shorter, &mut None) {
552             break
553         }
554     }
555     if !parser.footnotes.is_empty() {
556         buffer.push_str(&format!("<div class=\"footnotes\"><hr><ol>{}</ol></div>",
557                                  parser.footnotes.values()
558                                                  .map(|&(ref s, _)| s.as_str())
559                                                  .collect::<Vec<_>>()
560                                                  .join("")));
561     }
562     let mut ret = toc_builder.map_or(Ok(()), |builder| {
563         write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
564     });
565
566     if ret.is_ok() {
567         ret = w.write_str(&buffer);
568     }
569     ret
570 }
571
572 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
573     tests.set_position(position);
574
575     let mut parser = Parser::new(doc);
576     let mut prev_offset = 0;
577     let mut nb_lines = 0;
578     let mut register_header = None;
579     'main: while let Some(event) = parser.next() {
580         match event {
581             Event::Start(Tag::CodeBlock(s)) => {
582                 let block_info = if s.is_empty() {
583                     LangString::all_false()
584                 } else {
585                     LangString::parse(&*s)
586                 };
587                 if !block_info.rust {
588                     continue
589                 }
590                 let mut test_s = String::new();
591                 let mut offset = None;
592                 loop {
593                     let event = parser.next();
594                     if let Some(event) = event {
595                         match event {
596                             Event::End(Tag::CodeBlock(_)) => break,
597                             Event::Text(ref s) => {
598                                 test_s.push_str(s);
599                                 if offset.is_none() {
600                                     offset = Some(parser.get_offset());
601                                 }
602                             }
603                             _ => {}
604                         }
605                     } else {
606                         break 'main;
607                     }
608                 }
609                 let offset = offset.unwrap_or(0);
610                 let lines = test_s.lines().map(|l| {
611                     stripped_filtered_line(l).unwrap_or(l)
612                 });
613                 let text = lines.collect::<Vec<&str>>().join("\n");
614                 nb_lines += doc[prev_offset..offset].lines().count();
615                 let line = tests.get_line() + (nb_lines - 1);
616                 let filename = tests.get_filename();
617                 tests.add_test(text.to_owned(),
618                                block_info.should_panic, block_info.no_run,
619                                block_info.ignore, block_info.test_harness,
620                                block_info.compile_fail, block_info.error_codes,
621                                line, filename);
622                 prev_offset = offset;
623             }
624             Event::Start(Tag::Header(level)) => {
625                 register_header = Some(level as u32);
626             }
627             Event::Text(ref s) if register_header.is_some() => {
628                 let level = register_header.unwrap();
629                 if s.is_empty() {
630                     tests.register_header("", level);
631                 } else {
632                     tests.register_header(s, level);
633                 }
634                 register_header = None;
635             }
636             _ => {}
637         }
638     }
639 }
640
641 #[derive(Eq, PartialEq, Clone, Debug)]
642 struct LangString {
643     original: String,
644     should_panic: bool,
645     no_run: bool,
646     ignore: bool,
647     rust: bool,
648     test_harness: bool,
649     compile_fail: bool,
650     error_codes: Vec<String>,
651 }
652
653 impl LangString {
654     fn all_false() -> LangString {
655         LangString {
656             original: String::new(),
657             should_panic: false,
658             no_run: false,
659             ignore: false,
660             rust: true,  // NB This used to be `notrust = false`
661             test_harness: false,
662             compile_fail: false,
663             error_codes: Vec::new(),
664         }
665     }
666
667     fn parse(string: &str) -> LangString {
668         let mut seen_rust_tags = false;
669         let mut seen_other_tags = false;
670         let mut data = LangString::all_false();
671         let mut allow_compile_fail = false;
672         let mut allow_error_code_check = false;
673         if UnstableFeatures::from_environment().is_nightly_build() {
674             allow_compile_fail = true;
675             allow_error_code_check = true;
676         }
677
678         data.original = string.to_owned();
679         let tokens = string.split(|c: char|
680             !(c == '_' || c == '-' || c.is_alphanumeric())
681         );
682
683         for token in tokens {
684             match token {
685                 "" => {},
686                 "should_panic" => { data.should_panic = true; seen_rust_tags = true; },
687                 "no_run" => { data.no_run = true; seen_rust_tags = true; },
688                 "ignore" => { data.ignore = true; seen_rust_tags = true; },
689                 "rust" => { data.rust = true; seen_rust_tags = true; },
690                 "test_harness" => { data.test_harness = true; seen_rust_tags = true; },
691                 "compile_fail" if allow_compile_fail => {
692                     data.compile_fail = true;
693                     seen_rust_tags = true;
694                     data.no_run = true;
695                 }
696                 x if allow_error_code_check && x.starts_with("E") && x.len() == 5 => {
697                     if let Ok(_) = x[1..].parse::<u32>() {
698                         data.error_codes.push(x.to_owned());
699                         seen_rust_tags = true;
700                     } else {
701                         seen_other_tags = true;
702                     }
703                 }
704                 _ => { seen_other_tags = true }
705             }
706         }
707
708         data.rust &= !seen_other_tags || seen_rust_tags;
709
710         data
711     }
712 }
713
714 impl<'a> fmt::Display for Markdown<'a> {
715     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
716         let Markdown(md, shorter) = *self;
717         // This is actually common enough to special-case
718         if md.is_empty() { return Ok(()) }
719         render(fmt, md, false, shorter)
720     }
721 }
722
723 impl<'a> fmt::Display for MarkdownWithToc<'a> {
724     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
725         let MarkdownWithToc(md) = *self;
726         render(fmt, md, true, MarkdownOutputStyle::Fancy)
727     }
728 }
729
730 impl<'a> fmt::Display for MarkdownHtml<'a> {
731     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
732         let MarkdownHtml(md) = *self;
733         // This is actually common enough to special-case
734         if md.is_empty() { return Ok(()) }
735         render(fmt, md, false, MarkdownOutputStyle::Fancy)
736     }
737 }
738
739 pub fn plain_summary_line(md: &str) -> String {
740     struct ParserWrapper<'a> {
741         inner: Parser<'a>,
742         is_in: isize,
743         is_first: bool,
744     }
745
746     impl<'a> Iterator for ParserWrapper<'a> {
747         type Item = String;
748
749         fn next(&mut self) -> Option<String> {
750             let next_event = self.inner.next();
751             if next_event.is_none() {
752                 return None
753             }
754             let next_event = next_event.unwrap();
755             let (ret, is_in) = match next_event {
756                 Event::Start(Tag::Paragraph) => (None, 1),
757                 Event::Start(Tag::Link(_, ref t)) if !self.is_first => {
758                     (Some(t.as_ref().to_owned()), 1)
759                 }
760                 Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
761                 Event::End(Tag::Code) => (Some("`".to_owned()), -1),
762                 Event::Start(Tag::Header(_)) => (None, 1),
763                 Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
764                 Event::End(Tag::Link(_, ref t)) => (Some(t.as_ref().to_owned()), -1),
765                 Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
766                 _ => (None, 0),
767             };
768             if is_in > 0 || (is_in < 0 && self.is_in > 0) {
769                 self.is_in += is_in;
770             }
771             if ret.is_some() {
772                 self.is_first = false;
773                 ret
774             } else {
775                 Some(String::new())
776             }
777         }
778     }
779     let mut s = String::with_capacity(md.len() * 3 / 2);
780     let mut p = ParserWrapper {
781         inner: Parser::new(md),
782         is_in: 0,
783         is_first: true,
784     };
785     while let Some(t) = p.next() {
786         if !t.is_empty() {
787             s.push_str(&t);
788         }
789     }
790     s
791 }
792
793 #[cfg(test)]
794 mod tests {
795     use super::{LangString, Markdown, MarkdownHtml, MarkdownOutputStyle};
796     use super::plain_summary_line;
797     use html::render::reset_ids;
798
799     #[test]
800     fn test_lang_string_parse() {
801         fn t(s: &str,
802             should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
803             compile_fail: bool, error_codes: Vec<String>) {
804             assert_eq!(LangString::parse(s), LangString {
805                 should_panic: should_panic,
806                 no_run: no_run,
807                 ignore: ignore,
808                 rust: rust,
809                 test_harness: test_harness,
810                 compile_fail: compile_fail,
811                 error_codes: error_codes,
812                 original: s.to_owned(),
813             })
814         }
815
816         // marker                | should_panic| no_run| ignore| rust | test_harness| compile_fail
817         //                       | error_codes
818         t("",                      false,        false,  false,  true,  false, false, Vec::new());
819         t("rust",                  false,        false,  false,  true,  false, false, Vec::new());
820         t("sh",                    false,        false,  false,  false, false, false, Vec::new());
821         t("ignore",                false,        false,  true,   true,  false, false, Vec::new());
822         t("should_panic",          true,         false,  false,  true,  false, false, Vec::new());
823         t("no_run",                false,        true,   false,  true,  false, false, Vec::new());
824         t("test_harness",          false,        false,  false,  true,  true,  false, Vec::new());
825         t("compile_fail",          false,        true,   false,  true,  false, true,  Vec::new());
826         t("{.no_run .example}",    false,        true,   false,  true,  false, false, Vec::new());
827         t("{.sh .should_panic}",   true,         false,  false,  true,  false, false, Vec::new());
828         t("{.example .rust}",      false,        false,  false,  true,  false, false, Vec::new());
829         t("{.test_harness .rust}", false,        false,  false,  true,  true,  false, Vec::new());
830     }
831
832     #[test]
833     fn issue_17736() {
834         let markdown = "# title";
835         format!("{}", Markdown(markdown, MarkdownOutputStyle::Fancy));
836         reset_ids(true);
837     }
838
839     #[test]
840     fn test_header() {
841         fn t(input: &str, expect: &str) {
842             let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
843             assert_eq!(output, expect, "original: {}", input);
844             reset_ids(true);
845         }
846
847         t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
848           <a href=\"#foo-bar\">Foo bar</a></h1>");
849         t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
850           header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
851         t("### **Foo** *bar* baz!?!& -_qux_-%",
852           "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
853           <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
854           <em>bar</em> baz!?!&amp; -<em>qux</em>-%</a></h3>");
855         t("#### **Foo?** & \\*bar?!*  _`baz`_ ❤ #qux",
856           "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
857           <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!*  \
858           <em><code>baz</code></em> ❤ #qux</a></h4>");
859     }
860
861     #[test]
862     fn test_header_ids_multiple_blocks() {
863         fn t(input: &str, expect: &str) {
864             let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
865             assert_eq!(output, expect, "original: {}", input);
866         }
867
868         let test = || {
869             t("# Example", "<h1 id=\"example\" class=\"section-header\">\
870               <a href=\"#example\">Example</a></h1>");
871             t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
872               <a href=\"#panics\">Panics</a></h1>");
873             t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
874               <a href=\"#example-1\">Example</a></h1>");
875             t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
876               <a href=\"#main-1\">Main</a></h1>");
877             t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
878               <a href=\"#example-2\">Example</a></h1>");
879             t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
880               <a href=\"#panics-1\">Panics</a></h1>");
881         };
882         test();
883         reset_ids(true);
884         test();
885     }
886
887     #[test]
888     fn test_plain_summary_line() {
889         fn t(input: &str, expect: &str) {
890             let output = plain_summary_line(input);
891             assert_eq!(output, expect, "original: {}", input);
892         }
893
894         t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
895         t("code `let x = i32;` ...", "code `let x = i32;` ...");
896         t("type `Type<'static>` ...", "type `Type<'static>` ...");
897         t("# top header", "top header");
898         t("## header", "header");
899     }
900
901     #[test]
902     fn test_markdown_html_escape() {
903         fn t(input: &str, expect: &str) {
904             let output = format!("{}", MarkdownHtml(input));
905             assert_eq!(output, expect, "original: {}", input);
906         }
907
908         t("`Struct<'a, T>`", "<p><code>Struct&lt;&#39;a, T&gt;</code></p>");
909         t("Struct<'a, T>", "<p>Struct&lt;&#39;a, T&gt;</p>");
910     }
911 }