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