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