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.
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.
11 //! Markdown formatting for rustdoc
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:
19 //! #![feature(rustc_private)]
21 //! use rustdoc::html::markdown::{RenderType, Markdown};
23 //! let s = "My *markdown* _text_";
24 //! let html = format!("{}", Markdown(s, RenderType::Pulldown));
25 //! // ... something using html
28 #![allow(non_camel_case_types)]
33 use std
::cell
::RefCell
;
34 use std
::collections
::{HashMap, VecDeque}
;
35 use std
::default::Default
;
36 use std
::fmt
::{self, Write}
;
38 use syntax
::feature_gate
::UnstableFeatures
;
39 use syntax
::codemap
::Span
;
41 use html
::render
::derive_id
;
42 use html
::toc
::TocBuilder
;
44 use html
::escape
::Escape
;
47 use pulldown_cmark
::{html, Event, Tag, Parser}
;
48 use pulldown_cmark
::{Options, OPTION_ENABLE_FOOTNOTES, OPTION_ENABLE_TABLES}
;
50 #[derive(PartialEq, Debug, Clone, Copy)]
56 /// A unit struct which has the `fmt::Display` trait implemented. When
57 /// formatted, this struct will emit the HTML corresponding to the rendered
58 /// version of the contained markdown string.
59 // The second parameter is whether we need a shorter version or not.
60 pub struct Markdown
<'a
>(pub &'a
str, pub RenderType
);
61 /// A unit struct like `Markdown`, that renders the markdown with a
62 /// table of contents.
63 pub struct MarkdownWithToc
<'a
>(pub &'a
str, pub RenderType
);
64 /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
65 pub struct MarkdownHtml
<'a
>(pub &'a
str, pub RenderType
);
66 /// A unit struct like `Markdown`, that renders only the first paragraph.
67 pub struct MarkdownSummaryLine
<'a
>(pub &'a
str);
69 /// Controls whether a line will be hidden or shown in HTML output.
71 /// All lines are used in documentation tests.
78 fn for_html(self) -> Option
<&'a
str> {
80 Line
::Shown(l
) => Some(l
),
81 Line
::Hidden(_
) => None
,
85 fn for_code(self) -> &'a
str {
93 // FIXME: There is a minor inconsistency here. For lines that start with ##, we
94 // have no easy way of removing a potential single space after the hashes, which
95 // is done in the single # case. This inconsistency seems okay, if non-ideal. In
96 // order to fix it we'd have to iterate to find the first non-# character, and
97 // then reallocate to remove it; which would make us return a String.
98 fn map_line(s
: &str) -> Line
{
99 let trimmed
= s
.trim();
100 if trimmed
.starts_with("##") {
101 Line
::Shown(&trimmed
[1..])
102 } else if trimmed
.starts_with("# ") {
104 Line
::Hidden(&trimmed
[2..])
105 } else if trimmed
== "#" {
106 // We cannot handle '#text' because it could be #[attr].
113 /// Returns a new string with all consecutive whitespace collapsed into
116 /// Any leading or trailing whitespace will be trimmed.
117 fn collapse_whitespace(s
: &str) -> String
{
118 s
.split_whitespace().collect
::<Vec
<_
>>().join(" ")
121 /// Convert chars from a title for an id.
123 /// "Hello, world!" -> "hello-world"
124 fn slugify(c
: char) -> Option
<char> {
125 if c
.is_alphanumeric() || c
== '
-'
|| c
== '_'
{
127 Some(c
.to_ascii_lowercase())
131 } else if c
.is_whitespace() && c
.is_ascii() {
138 // Information about the playground if a URL has been specified, containing an
139 // optional crate name and the URL.
140 thread_local
!(pub static PLAYGROUND
: RefCell
<Option
<(Option
<String
>, String
)>> = {
144 /// Adds syntax highlighting and playground Run buttons to rust code blocks.
145 struct CodeBlocks
<'a
, I
: Iterator
<Item
= Event
<'a
>>> {
149 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> CodeBlocks
<'a
, I
> {
150 fn new(iter
: I
) -> Self {
157 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for CodeBlocks
<'a
, I
> {
158 type Item
= Event
<'a
>;
160 fn next(&mut self) -> Option
<Self::Item
> {
161 let event
= self.inner
.next();
164 if let Some(Event
::Start(Tag
::CodeBlock(lang
))) = event
{
165 let parse_result
= LangString
::parse(&lang
);
166 if !parse_result
.rust
{
167 return Some(Event
::Start(Tag
::CodeBlock(lang
)));
169 compile_fail
= parse_result
.compile_fail
;
170 ignore
= parse_result
.ignore
;
175 let mut origtext
= String
::new();
176 for event
in &mut self.inner
{
178 Event
::End(Tag
::CodeBlock(..)) => break,
179 Event
::Text(ref s
) => {
180 origtext
.push_str(s
);
185 let lines
= origtext
.lines().filter_map(|l
| map_line(l
).for_html());
186 let text
= lines
.collect
::<Vec
<&str>>().join("\n");
187 PLAYGROUND
.with(|play
| {
188 // insert newline to clearly separate it from the
189 // previous block so we can shorten the html output
190 let mut s
= String
::from("\n");
191 let playground_button
= play
.borrow().as_ref().and_then(|&(ref krate
, ref url
)| {
195 let test
= origtext
.lines()
196 .map(|l
| map_line(l
).for_code())
197 .collect
::<Vec
<&str>>().join("\n");
198 let krate
= krate
.as_ref().map(|s
| &**s
);
199 let test
= test
::make_test(&test
, krate
, false,
200 &Default
::default());
201 let channel
= if test
.contains("#![feature(") {
202 "&version=nightly"
206 // These characters don't need to be escaped in a URI.
207 // FIXME: use a library function for percent encoding.
208 fn dont_escape(c
: u8) -> bool
{
209 (b'a'
<= c
&& c
<= b'z'
) ||
210 (b'A'
<= c
&& c
<= b'Z'
) ||
211 (b'
0'
<= c
&& c
<= b'
9'
) ||
212 c
== b'
-'
|| c
== b'_'
|| c
== b'
.'
||
213 c
== b'
~'
|| c
== b'
!'
|| c
== b'
\''
||
214 c
== b'
('
|| c
== b'
)'
|| c
== b'
*'
216 let mut test_escaped
= String
::new();
217 for b
in test
.bytes() {
219 test_escaped
.push(char::from(b
));
221 write
!(test_escaped
, "%{:02X}", b
).unwrap();
225 r
#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
226 url
, test_escaped
, channel
229 let tooltip
= if ignore
{
230 Some(("This example is not tested", "ignore"))
231 } else if compile_fail
{
232 Some(("This example deliberately fails to compile", "compile_fail"))
236 s
.push_str(&highlight
::render_with_highlighting(
238 Some(&format
!("rust-example-rendered{}",
239 if ignore { " ignore" }
240 else if compile_fail { " compile_fail" }
243 playground_button
.as_ref().map(String
::as_str
),
245 Some(Event
::Html(s
.into()))
250 /// Make headings links with anchor ids and build up TOC.
251 struct HeadingLinks
<'a
, 'b
, I
: Iterator
<Item
= Event
<'a
>>> {
253 toc
: Option
<&'b
mut TocBuilder
>,
254 buf
: VecDeque
<Event
<'a
>>,
257 impl<'a
, 'b
, I
: Iterator
<Item
= Event
<'a
>>> HeadingLinks
<'a
, 'b
, I
> {
258 fn new(iter
: I
, toc
: Option
<&'b
mut TocBuilder
>) -> Self {
262 buf
: VecDeque
::new(),
267 impl<'a
, 'b
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for HeadingLinks
<'a
, 'b
, I
> {
268 type Item
= Event
<'a
>;
270 fn next(&mut self) -> Option
<Self::Item
> {
271 if let Some(e
) = self.buf
.pop_front() {
275 let event
= self.inner
.next();
276 if let Some(Event
::Start(Tag
::Header(level
))) = event
{
277 let mut id
= String
::new();
278 for event
in &mut self.inner
{
280 Event
::End(Tag
::Header(..)) => break,
281 Event
::Text(ref text
) => id
.extend(text
.chars().filter_map(slugify
)),
284 self.buf
.push_back(event
);
286 let id
= derive_id(id
);
288 if let Some(ref mut builder
) = self.toc
{
289 let mut html_header
= String
::new();
290 html
::push_html(&mut html_header
, self.buf
.iter().cloned());
291 let sec
= builder
.push(level
as u32, html_header
, id
.clone());
292 self.buf
.push_front(Event
::InlineHtml(format
!("{} ", sec
).into()));
295 self.buf
.push_back(Event
::InlineHtml(format
!("</a></h{}>", level
).into()));
297 let start_tags
= format
!("<h{level} id=\"{id}\" class=\"section-header\">\
301 return Some(Event
::InlineHtml(start_tags
.into()));
307 /// Extracts just the first paragraph.
308 struct SummaryLine
<'a
, I
: Iterator
<Item
= Event
<'a
>>> {
314 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> SummaryLine
<'a
, I
> {
315 fn new(iter
: I
) -> Self {
324 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for SummaryLine
<'a
, I
> {
325 type Item
= Event
<'a
>;
327 fn next(&mut self) -> Option
<Self::Item
> {
328 if self.started
&& self.depth
== 0 {
334 let event
= self.inner
.next();
336 Some(Event
::Start(..)) => self.depth
+= 1,
337 Some(Event
::End(..)) => self.depth
-= 1,
344 /// Moves all footnote definitions to the end and add back links to the
346 struct Footnotes
<'a
, I
: Iterator
<Item
= Event
<'a
>>> {
348 footnotes
: HashMap
<String
, (Vec
<Event
<'a
>>, u16)>,
351 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Footnotes
<'a
, I
> {
352 fn new(iter
: I
) -> Self {
355 footnotes
: HashMap
::new(),
358 fn get_entry(&mut self, key
: &str) -> &mut (Vec
<Event
<'a
>>, u16) {
359 let new_id
= self.footnotes
.keys().count() + 1;
360 let key
= key
.to_owned();
361 self.footnotes
.entry(key
).or_insert((Vec
::new(), new_id
as u16))
365 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for Footnotes
<'a
, I
> {
366 type Item
= Event
<'a
>;
368 fn next(&mut self) -> Option
<Self::Item
> {
370 match self.inner
.next() {
371 Some(Event
::FootnoteReference(ref reference
)) => {
372 let entry
= self.get_entry(&reference
);
373 let reference
= format
!("<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}\
376 return Some(Event
::Html(reference
.into()));
378 Some(Event
::Start(Tag
::FootnoteDefinition(def
))) => {
379 let mut content
= Vec
::new();
380 for event
in &mut self.inner
{
381 if let Event
::End(Tag
::FootnoteDefinition(..)) = event
{
386 let entry
= self.get_entry(&def
);
387 (*entry
).0 = content
;
389 Some(e
) => return Some(e
),
391 if !self.footnotes
.is_empty() {
392 let mut v
: Vec
<_
> = self.footnotes
.drain().map(|(_
, x
)| x
).collect();
393 v
.sort_by(|a
, b
| a
.1.cmp(&b
.1));
394 let mut ret
= String
::from("<div class=\"footnotes\"><hr><ol>");
395 for (mut content
, id
) in v
{
396 write
!(ret
, "<li id=\"fn{}\">", id
).unwrap();
397 let mut is_paragraph
= false;
398 if let Some(&Event
::End(Tag
::Paragraph
)) = content
.last() {
402 html
::push_html(&mut ret
, content
.into_iter());
404 " <a href=\"#fnref{}\" rev=\"footnote\">↩</a>",
407 ret
.push_str("</p>");
409 ret
.push_str("</li>");
411 ret
.push_str("</ol></div>");
412 return Some(Event
::Html(ret
.into()));
422 const DEF_OUNIT
: libc
::size_t
= 64;
423 const HOEDOWN_EXT_NO_INTRA_EMPHASIS
: libc
::c_uint
= 1 << 11;
424 const HOEDOWN_EXT_TABLES
: libc
::c_uint
= 1 << 0;
425 const HOEDOWN_EXT_FENCED_CODE
: libc
::c_uint
= 1 << 1;
426 const HOEDOWN_EXT_AUTOLINK
: libc
::c_uint
= 1 << 3;
427 const HOEDOWN_EXT_STRIKETHROUGH
: libc
::c_uint
= 1 << 4;
428 const HOEDOWN_EXT_SUPERSCRIPT
: libc
::c_uint
= 1 << 8;
429 const HOEDOWN_EXT_FOOTNOTES
: libc
::c_uint
= 1 << 2;
430 const HOEDOWN_HTML_ESCAPE
: libc
::c_uint
= 1 << 1;
432 const HOEDOWN_EXTENSIONS
: libc
::c_uint
=
433 HOEDOWN_EXT_NO_INTRA_EMPHASIS
| HOEDOWN_EXT_TABLES
|
434 HOEDOWN_EXT_FENCED_CODE
| HOEDOWN_EXT_AUTOLINK
|
435 HOEDOWN_EXT_STRIKETHROUGH
| HOEDOWN_EXT_SUPERSCRIPT
|
436 HOEDOWN_EXT_FOOTNOTES
;
438 enum hoedown_document {}
440 type blockcodefn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
441 *const hoedown_buffer
, *const hoedown_renderer_data
,
444 type blockquotefn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
445 *const hoedown_renderer_data
, libc
::size_t
);
447 type headerfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
448 libc
::c_int
, *const hoedown_renderer_data
,
451 type blockhtmlfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
452 *const hoedown_renderer_data
, libc
::size_t
);
454 type codespanfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
455 *const hoedown_renderer_data
, libc
::size_t
) -> libc
::c_int
;
457 type linkfn
= extern "C" fn (*mut hoedown_buffer
, *const hoedown_buffer
,
458 *const hoedown_buffer
, *const hoedown_buffer
,
459 *const hoedown_renderer_data
, libc
::size_t
) -> libc
::c_int
;
461 type entityfn
= extern "C" fn (*mut hoedown_buffer
, *const hoedown_buffer
,
462 *const hoedown_renderer_data
, libc
::size_t
);
464 type normaltextfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
465 *const hoedown_renderer_data
, libc
::size_t
);
468 struct hoedown_renderer_data
{
469 opaque
: *mut libc
::c_void
,
473 struct hoedown_renderer
{
474 opaque
: *mut libc
::c_void
,
476 blockcode
: Option
<blockcodefn
>,
477 blockquote
: Option
<blockquotefn
>,
478 header
: Option
<headerfn
>,
480 other_block_level_callbacks
: [libc
::size_t
; 11],
482 blockhtml
: Option
<blockhtmlfn
>,
484 /* span level callbacks - NULL or return 0 prints the span verbatim */
485 autolink
: libc
::size_t
, // unused
486 codespan
: Option
<codespanfn
>,
487 other_span_level_callbacks_1
: [libc
::size_t
; 7],
488 link
: Option
<linkfn
>,
489 other_span_level_callbacks_2
: [libc
::size_t
; 6],
491 /* low level callbacks - NULL copies input directly into the output */
492 entity
: Option
<entityfn
>,
493 normal_text
: Option
<normaltextfn
>,
495 /* header and footer */
496 other_callbacks
: [libc
::size_t
; 2],
500 struct hoedown_html_renderer_state
{
501 opaque
: *mut libc
::c_void
,
502 toc_data
: html_toc_data
,
504 link_attributes
: Option
<extern "C" fn(*mut hoedown_buffer
,
505 *const hoedown_buffer
,
506 *const hoedown_renderer_data
)>,
510 struct html_toc_data
{
511 header_count
: libc
::c_int
,
512 current_level
: libc
::c_int
,
513 level_offset
: libc
::c_int
,
514 nesting_level
: libc
::c_int
,
518 struct hoedown_buffer
{
526 dfltblk
: extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
527 *const hoedown_buffer
, *const hoedown_renderer_data
,
529 toc_builder
: Option
<TocBuilder
>,
533 fn hoedown_html_renderer_new(render_flags
: libc
::c_uint
,
534 nesting_level
: libc
::c_int
)
535 -> *mut hoedown_renderer
;
536 fn hoedown_html_renderer_free(renderer
: *mut hoedown_renderer
);
538 fn hoedown_document_new(rndr
: *const hoedown_renderer
,
539 extensions
: libc
::c_uint
,
540 max_nesting
: libc
::size_t
) -> *mut hoedown_document
;
541 fn hoedown_document_render(doc
: *mut hoedown_document
,
542 ob
: *mut hoedown_buffer
,
544 doc_size
: libc
::size_t
);
545 fn hoedown_document_free(md
: *mut hoedown_document
);
547 fn hoedown_buffer_new(unit
: libc
::size_t
) -> *mut hoedown_buffer
;
548 fn hoedown_buffer_free(b
: *mut hoedown_buffer
);
549 fn hoedown_buffer_put(b
: *mut hoedown_buffer
, c
: *const u8, len
: libc
::size_t
);
552 impl hoedown_buffer
{
553 fn as_bytes(&self) -> &[u8] {
554 unsafe { slice::from_raw_parts(self.data, self.size as usize) }
558 pub fn render(w
: &mut fmt
::Formatter
,
561 html_flags
: libc
::c_uint
) -> fmt
::Result
{
562 extern fn block(ob
: *mut hoedown_buffer
, orig_text
: *const hoedown_buffer
,
563 lang
: *const hoedown_buffer
, data
: *const hoedown_renderer_data
,
564 line
: libc
::size_t
) {
566 if orig_text
.is_null() { return }
568 let opaque
= (*data
).opaque
as *mut hoedown_html_renderer_state
;
569 let my_opaque
: &MyOpaque
= &*((*opaque
).opaque
as *const MyOpaque
);
570 let text
= (*orig_text
).as_bytes();
571 let origtext
= str::from_utf8(text
).unwrap();
572 let origtext
= origtext
.trim_left();
573 debug
!("docblock: ==============\n{:?}\n=======", text
);
574 let mut compile_fail
= false;
575 let mut ignore
= false;
577 let rendered
= if lang
.is_null() || origtext
.is_empty() {
580 let rlang
= (*lang
).as_bytes();
581 let rlang
= str::from_utf8(rlang
).unwrap();
582 let parse_result
= LangString
::parse(rlang
);
583 compile_fail
= parse_result
.compile_fail
;
584 ignore
= parse_result
.ignore
;
585 if !parse_result
.rust
{
586 (my_opaque
.dfltblk
)(ob
, orig_text
, lang
,
587 opaque
as *const hoedown_renderer_data
,
595 let lines
= origtext
.lines().filter_map(|l
| map_line(l
).for_html());
596 let text
= lines
.collect
::<Vec
<&str>>().join("\n");
597 if rendered { return }
598 PLAYGROUND
.with(|play
| {
599 // insert newline to clearly separate it from the
600 // previous block so we can shorten the html output
601 let mut s
= String
::from("\n");
602 let playground_button
= play
.borrow().as_ref().and_then(|&(ref krate
, ref url
)| {
606 let test
= origtext
.lines()
607 .map(|l
| map_line(l
).for_code())
608 .collect
::<Vec
<&str>>().join("\n");
609 let krate
= krate
.as_ref().map(|s
| &**s
);
610 let test
= test
::make_test(&test
, krate
, false,
611 &Default
::default());
612 let channel
= if test
.contains("#![feature(") {
613 "&version=nightly"
617 // These characters don't need to be escaped in a URI.
618 // FIXME: use a library function for percent encoding.
619 fn dont_escape(c
: u8) -> bool
{
620 (b'a'
<= c
&& c
<= b'z'
) ||
621 (b'A'
<= c
&& c
<= b'Z'
) ||
622 (b'
0'
<= c
&& c
<= b'
9'
) ||
623 c
== b'
-'
|| c
== b'_'
|| c
== b'
.'
||
624 c
== b'
~'
|| c
== b'
!'
|| c
== b'
\''
||
625 c
== b'
('
|| c
== b'
)'
|| c
== b'
*'
627 let mut test_escaped
= String
::new();
628 for b
in test
.bytes() {
630 test_escaped
.push(char::from(b
));
632 write
!(test_escaped
, "%{:02X}", b
).unwrap();
636 r
#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
637 url
, test_escaped
, channel
640 let tooltip
= if ignore
{
641 Some(("This example is not tested", "ignore"))
642 } else if compile_fail
{
643 Some(("This example deliberately fails to compile", "compile_fail"))
647 s
.push_str(&highlight
::render_with_highlighting(
649 Some(&format
!("rust-example-rendered{}",
650 if ignore { " ignore" }
651 else if compile_fail { " compile_fail" }
654 playground_button
.as_ref().map(String
::as_str
),
656 hoedown_buffer_put(ob
, s
.as_ptr(), s
.len());
661 extern fn header(ob
: *mut hoedown_buffer
, text
: *const hoedown_buffer
,
662 level
: libc
::c_int
, data
: *const hoedown_renderer_data
,
664 // hoedown does this, we may as well too
665 unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); }
667 // Extract the text provided
668 let s
= if text
.is_null() {
671 let s
= unsafe { (*text).as_bytes() }
;
672 str::from_utf8(&s
).unwrap().to_owned()
675 // Discard '<em>', '<code>' tags and some escaped characters,
676 // transform the contents of the header into a hyphenated string
677 // without non-alphanumeric characters other than '-' and '_'.
679 // This is a terrible hack working around how hoedown gives us rendered
680 // html for text rather than the raw text.
681 let mut id
= s
.clone();
682 let repl_sub
= vec
!["<em>", "</em>", "<code>", "</code>",
683 "<strong>", "</strong>",
684 "<", ">", "&", "'", """];
685 for sub
in repl_sub
{
686 id
= id
.replace(sub
, "");
688 let id
= id
.chars().filter_map(|c
| {
689 if c
.is_alphanumeric() || c
== '
-'
|| c
== '_'
{
691 Some(c
.to_ascii_lowercase())
695 } else if c
.is_whitespace() && c
.is_ascii() {
700 }).collect
::<String
>();
702 let opaque
= unsafe { (*data).opaque as *mut hoedown_html_renderer_state }
;
703 let opaque
= unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }
;
705 let id
= derive_id(id
);
707 let sec
= opaque
.toc_builder
.as_mut().map_or("".to_owned(), |builder
| {
708 format
!("{} ", builder
.push(level
as u32, s
.clone(), id
.clone()))
712 let text
= format
!("<h{lvl} id='{id}' class='section-header'>\
713 <a href='#{id}'>{sec}{}</a></h{lvl}>",
714 s
, lvl
= level
, id
= id
, sec
= sec
);
716 unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); }
720 ob
: *mut hoedown_buffer
,
721 text
: *const hoedown_buffer
,
722 _
: *const hoedown_renderer_data
,
725 let content
= if text
.is_null() {
728 let bytes
= unsafe { (*text).as_bytes() }
;
729 let s
= str::from_utf8(bytes
).unwrap();
730 collapse_whitespace(s
)
733 let content
= format
!("<code>{}</code>", Escape(&content
));
735 hoedown_buffer_put(ob
, content
.as_ptr(), content
.len());
737 // Return anything except 0, which would mean "also print the code span verbatim".
742 let ob
= hoedown_buffer_new(DEF_OUNIT
);
743 let renderer
= hoedown_html_renderer_new(html_flags
, 0);
744 let mut opaque
= MyOpaque
{
745 dfltblk
: (*renderer
).blockcode
.unwrap(),
746 toc_builder
: if print_toc {Some(TocBuilder::new())}
else {None}
748 (*((*renderer
).opaque
as *mut hoedown_html_renderer_state
)).opaque
749 = &mut opaque
as *mut _
as *mut libc
::c_void
;
750 (*renderer
).blockcode
= Some(block
);
751 (*renderer
).header
= Some(header
);
752 (*renderer
).codespan
= Some(codespan
);
754 let document
= hoedown_document_new(renderer
, HOEDOWN_EXTENSIONS
, 16);
755 hoedown_document_render(document
, ob
, s
.as_ptr(),
756 s
.len() as libc
::size_t
);
757 hoedown_document_free(document
);
759 hoedown_html_renderer_free(renderer
);
761 let mut ret
= opaque
.toc_builder
.map_or(Ok(()), |builder
| {
762 write
!(w
, "<nav id=\"TOC\">{}</nav>", builder
.into_toc())
766 let buf
= (*ob
).as_bytes();
767 ret
= w
.write_str(str::from_utf8(buf
).unwrap());
769 hoedown_buffer_free(ob
);
774 pub fn old_find_testable_code(doc
: &str, tests
: &mut ::test
::Collector
, position
: Span
) {
775 extern fn block(_ob
: *mut hoedown_buffer
,
776 text
: *const hoedown_buffer
,
777 lang
: *const hoedown_buffer
,
778 data
: *const hoedown_renderer_data
,
779 line
: libc
::size_t
) {
781 if text
.is_null() { return }
782 let block_info
= if lang
.is_null() {
783 LangString
::all_false()
785 let lang
= (*lang
).as_bytes();
786 let s
= str::from_utf8(lang
).unwrap();
789 if !block_info
.rust { return }
790 let text
= (*text
).as_bytes();
791 let opaque
= (*data
).opaque
as *mut hoedown_html_renderer_state
;
792 let tests
= &mut *((*opaque
).opaque
as *mut ::test
::Collector
);
793 let text
= str::from_utf8(text
).unwrap();
794 let lines
= text
.lines().map(|l
| map_line(l
).for_code());
795 let text
= lines
.collect
::<Vec
<&str>>().join("\n");
796 let filename
= tests
.get_filename();
798 if tests
.render_type
== RenderType
::Hoedown
{
799 let line
= tests
.get_line() + line
;
800 tests
.add_test(text
.to_owned(),
801 block_info
.should_panic
, block_info
.no_run
,
802 block_info
.ignore
, block_info
.test_harness
,
803 block_info
.compile_fail
, block_info
.error_codes
,
804 line
, filename
, block_info
.allow_fail
);
806 tests
.add_old_test(text
, filename
);
811 extern fn header(_ob
: *mut hoedown_buffer
,
812 text
: *const hoedown_buffer
,
813 level
: libc
::c_int
, data
: *const hoedown_renderer_data
,
816 let opaque
= (*data
).opaque
as *mut hoedown_html_renderer_state
;
817 let tests
= &mut *((*opaque
).opaque
as *mut ::test
::Collector
);
819 tests
.register_header("", level
as u32);
821 let text
= (*text
).as_bytes();
822 let text
= str::from_utf8(text
).unwrap();
823 tests
.register_header(text
, level
as u32);
828 tests
.set_position(position
);
830 let ob
= hoedown_buffer_new(DEF_OUNIT
);
831 let renderer
= hoedown_html_renderer_new(0, 0);
832 (*renderer
).blockcode
= Some(block
);
833 (*renderer
).header
= Some(header
);
834 (*((*renderer
).opaque
as *mut hoedown_html_renderer_state
)).opaque
835 = tests
as *mut _
as *mut libc
::c_void
;
837 let document
= hoedown_document_new(renderer
, HOEDOWN_EXTENSIONS
, 16);
838 hoedown_document_render(document
, ob
, doc
.as_ptr(),
839 doc
.len() as libc
::size_t
);
840 hoedown_document_free(document
);
842 hoedown_html_renderer_free(renderer
);
843 hoedown_buffer_free(ob
);
847 pub fn find_testable_code(doc
: &str, tests
: &mut ::test
::Collector
, position
: Span
) {
848 tests
.set_position(position
);
850 let mut parser
= Parser
::new(doc
);
851 let mut prev_offset
= 0;
852 let mut nb_lines
= 0;
853 let mut register_header
= None
;
854 'main
: while let Some(event
) = parser
.next() {
856 Event
::Start(Tag
::CodeBlock(s
)) => {
857 let block_info
= if s
.is_empty() {
858 LangString
::all_false()
860 LangString
::parse(&*s
)
862 if !block_info
.rust
{
865 let mut test_s
= String
::new();
866 let mut offset
= None
;
868 let event
= parser
.next();
869 if let Some(event
) = event
{
871 Event
::End(Tag
::CodeBlock(_
)) => break,
872 Event
::Text(ref s
) => {
874 if offset
.is_none() {
875 offset
= Some(parser
.get_offset());
884 let offset
= offset
.unwrap_or(0);
885 let lines
= test_s
.lines().map(|l
| map_line(l
).for_code());
886 let text
= lines
.collect
::<Vec
<&str>>().join("\n");
887 nb_lines
+= doc
[prev_offset
..offset
].lines().count();
888 let line
= tests
.get_line() + (nb_lines
- 1);
889 let filename
= tests
.get_filename();
890 tests
.add_test(text
.to_owned(),
891 block_info
.should_panic
, block_info
.no_run
,
892 block_info
.ignore
, block_info
.test_harness
,
893 block_info
.compile_fail
, block_info
.error_codes
,
894 line
, filename
, block_info
.allow_fail
);
895 prev_offset
= offset
;
897 Event
::Start(Tag
::Header(level
)) => {
898 register_header
= Some(level
as u32);
900 Event
::Text(ref s
) if register_header
.is_some() => {
901 let level
= register_header
.unwrap();
903 tests
.register_header("", level
);
905 tests
.register_header(s
, level
);
907 register_header
= None
;
914 #[derive(Eq, PartialEq, Clone, Debug)]
923 error_codes
: Vec
<String
>,
928 fn all_false() -> LangString
{
930 original
: String
::new(),
934 rust
: true, // NB This used to be `notrust = false`
937 error_codes
: Vec
::new(),
942 fn parse(string
: &str) -> LangString
{
943 let mut seen_rust_tags
= false;
944 let mut seen_other_tags
= false;
945 let mut data
= LangString
::all_false();
946 let mut allow_error_code_check
= false;
947 if UnstableFeatures
::from_environment().is_nightly_build() {
948 allow_error_code_check
= true;
951 data
.original
= string
.to_owned();
952 let tokens
= string
.split(|c
: char|
953 !(c
== '_'
|| c
== '
-'
|| c
.is_alphanumeric())
956 for token
in tokens
{
960 data
.should_panic
= true;
961 seen_rust_tags
= seen_other_tags
== false;
963 "no_run" => { data.no_run = true; seen_rust_tags = !seen_other_tags; }
964 "ignore" => { data.ignore = true; seen_rust_tags = !seen_other_tags; }
965 "allow_fail" => { data.allow_fail = true; seen_rust_tags = !seen_other_tags; }
966 "rust" => { data.rust = true; seen_rust_tags = true; }
968 data
.test_harness
= true;
969 seen_rust_tags
= !seen_other_tags
|| seen_rust_tags
;
972 data
.compile_fail
= true;
973 seen_rust_tags
= !seen_other_tags
|| seen_rust_tags
;
976 x
if allow_error_code_check
&& x
.starts_with("E") && x
.len() == 5 => {
977 if let Ok(_
) = x
[1..].parse
::<u32>() {
978 data
.error_codes
.push(x
.to_owned());
979 seen_rust_tags
= !seen_other_tags
|| seen_rust_tags
;
981 seen_other_tags
= true;
984 _
=> { seen_other_tags = true }
988 data
.rust
&= !seen_other_tags
|| seen_rust_tags
;
994 impl<'a
> fmt
::Display
for Markdown
<'a
> {
995 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
996 let Markdown(md
, render_type
) = *self;
998 // This is actually common enough to special-case
999 if md
.is_empty() { return Ok(()) }
1000 if render_type
== RenderType
::Hoedown
{
1001 render(fmt
, md
, false, 0)
1003 let mut opts
= Options
::empty();
1004 opts
.insert(OPTION_ENABLE_TABLES
);
1005 opts
.insert(OPTION_ENABLE_FOOTNOTES
);
1007 let p
= Parser
::new_ext(md
, opts
);
1009 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
1011 html
::push_html(&mut s
,
1012 Footnotes
::new(CodeBlocks
::new(HeadingLinks
::new(p
, None
))));
1019 impl<'a
> fmt
::Display
for MarkdownWithToc
<'a
> {
1020 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
1021 let MarkdownWithToc(md
, render_type
) = *self;
1023 if render_type
== RenderType
::Hoedown
{
1024 render(fmt
, md
, true, 0)
1026 let mut opts
= Options
::empty();
1027 opts
.insert(OPTION_ENABLE_TABLES
);
1028 opts
.insert(OPTION_ENABLE_FOOTNOTES
);
1030 let p
= Parser
::new_ext(md
, opts
);
1032 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
1034 let mut toc
= TocBuilder
::new();
1036 html
::push_html(&mut s
,
1037 Footnotes
::new(CodeBlocks
::new(HeadingLinks
::new(p
, Some(&mut toc
)))));
1039 write
!(fmt
, "<nav id=\"TOC\">{}</nav>", toc
.into_toc())?
;
1046 impl<'a
> fmt
::Display
for MarkdownHtml
<'a
> {
1047 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
1048 let MarkdownHtml(md
, render_type
) = *self;
1050 // This is actually common enough to special-case
1051 if md
.is_empty() { return Ok(()) }
1052 if render_type
== RenderType
::Hoedown
{
1053 render(fmt
, md
, false, HOEDOWN_HTML_ESCAPE
)
1055 let mut opts
= Options
::empty();
1056 opts
.insert(OPTION_ENABLE_TABLES
);
1057 opts
.insert(OPTION_ENABLE_FOOTNOTES
);
1059 let p
= Parser
::new_ext(md
, opts
);
1061 // Treat inline HTML as plain text.
1062 let p
= p
.map(|event
| match event
{
1063 Event
::Html(text
) | Event
::InlineHtml(text
) => Event
::Text(text
),
1067 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
1069 html
::push_html(&mut s
,
1070 Footnotes
::new(CodeBlocks
::new(HeadingLinks
::new(p
, None
))));
1077 impl<'a
> fmt
::Display
for MarkdownSummaryLine
<'a
> {
1078 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
1079 let MarkdownSummaryLine(md
) = *self;
1080 // This is actually common enough to special-case
1081 if md
.is_empty() { return Ok(()) }
1083 let p
= Parser
::new(md
);
1085 let mut s
= String
::new();
1087 html
::push_html(&mut s
, SummaryLine
::new(p
));
1093 pub fn plain_summary_line(md
: &str) -> String
{
1094 struct ParserWrapper
<'a
> {
1100 impl<'a
> Iterator
for ParserWrapper
<'a
> {
1103 fn next(&mut self) -> Option
<String
> {
1104 let next_event
= self.inner
.next();
1105 if next_event
.is_none() {
1108 let next_event
= next_event
.unwrap();
1109 let (ret
, is_in
) = match next_event
{
1110 Event
::Start(Tag
::Paragraph
) => (None
, 1),
1111 Event
::Start(Tag
::Code
) => (Some("`".to_owned()), 1),
1112 Event
::End(Tag
::Code
) => (Some("`".to_owned()), -1),
1113 Event
::Start(Tag
::Header(_
)) => (None
, 1),
1114 Event
::Text(ref s
) if self.is_in
> 0 => (Some(s
.as_ref().to_owned()), 0),
1115 Event
::End(Tag
::Paragraph
) | Event
::End(Tag
::Header(_
)) => (None
, -1),
1118 if is_in
> 0 || (is_in
< 0 && self.is_in
> 0) {
1119 self.is_in
+= is_in
;
1122 self.is_first
= false;
1129 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
1130 let mut p
= ParserWrapper
{
1131 inner
: Parser
::new(md
),
1135 while let Some(t
) = p
.next() {
1145 use super::{LangString, Markdown, MarkdownHtml}
;
1146 use super::plain_summary_line
;
1147 use super::RenderType
;
1148 use html
::render
::reset_ids
;
1151 fn test_lang_string_parse() {
1153 should_panic
: bool
, no_run
: bool
, ignore
: bool
, rust
: bool
, test_harness
: bool
,
1154 compile_fail
: bool
, allow_fail
: bool
, error_codes
: Vec
<String
>) {
1155 assert_eq
!(LangString
::parse(s
), LangString
{
1163 original
: s
.to_owned(),
1168 fn v() -> Vec
<String
> {
1172 // marker | should_panic| no_run| ignore| rust | test_harness| compile_fail
1173 // | allow_fail | error_codes
1174 t("", false, false, false, true, false, false, false, v());
1175 t("rust", false, false, false, true, false, false, false, v());
1176 t("sh", false, false, false, false, false, false, false, v());
1177 t("ignore", false, false, true, true, false, false, false, v());
1178 t("should_panic", true, false, false, true, false, false, false, v());
1179 t("no_run", false, true, false, true, false, false, false, v());
1180 t("test_harness", false, false, false, true, true, false, false, v());
1181 t("compile_fail", false, true, false, true, false, true, false, v());
1182 t("allow_fail", false, false, false, true, false, false, true, v());
1183 t("{.no_run .example}", false, true, false, true, false, false, false, v());
1184 t("{.sh .should_panic}", true, false, false, false, false, false, false, v());
1185 t("{.example .rust}", false, false, false, true, false, false, false, v());
1186 t("{.test_harness .rust}", false, false, false, true, true, false, false, v());
1187 t("text, no_run", false, true, false, false, false, false, false, v());
1188 t("text,no_run", false, true, false, false, false, false, false, v());
1193 let markdown
= "# title";
1194 format
!("{}", Markdown(markdown
, RenderType
::Pulldown
));
1200 fn t(input
: &str, expect
: &str) {
1201 let output
= format
!("{}", Markdown(input
, RenderType
::Pulldown
));
1202 assert_eq
!(output
, expect
, "original: {}", input
);
1206 t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
1207 <a href=\"#foo-bar\">Foo bar</a></h1>");
1208 t("## Foo-bar_baz qux", "<h2 id=\"foo-bar_baz-qux\" class=\"section-\
1209 header\"><a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>");
1210 t("### **Foo** *bar* baz!?!& -_qux_-%",
1211 "<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
1212 <a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
1213 <em>bar</em> baz!?!& -<em>qux</em>-%</a></h3>");
1214 t("#### **Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
1215 "<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
1216 <a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> & *bar?!* \
1217 <em><code>baz</code></em> ❤ #qux</a></h4>");
1221 fn test_header_ids_multiple_blocks() {
1222 fn t(input
: &str, expect
: &str) {
1223 let output
= format
!("{}", Markdown(input
, RenderType
::Pulldown
));
1224 assert_eq
!(output
, expect
, "original: {}", input
);
1228 t("# Example", "<h1 id=\"example\" class=\"section-header\">\
1229 <a href=\"#example\">Example</a></h1>");
1230 t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
1231 <a href=\"#panics\">Panics</a></h1>");
1232 t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
1233 <a href=\"#example-1\">Example</a></h1>");
1234 t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
1235 <a href=\"#main-1\">Main</a></h1>");
1236 t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
1237 <a href=\"#example-2\">Example</a></h1>");
1238 t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
1239 <a href=\"#panics-1\">Panics</a></h1>");
1247 fn test_plain_summary_line() {
1248 fn t(input
: &str, expect
: &str) {
1249 let output
= plain_summary_line(input
);
1250 assert_eq
!(output
, expect
, "original: {}", input
);
1253 t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
1254 t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
1255 t("code `let x = i32;` ...", "code `let x = i32;` ...");
1256 t("type `Type<'static>` ...", "type `Type<'static>` ...");
1257 t("# top header", "top header");
1258 t("## header", "header");
1262 fn test_markdown_html_escape() {
1263 fn t(input
: &str, expect
: &str) {
1264 let output
= format
!("{}", MarkdownHtml(input
, RenderType
::Pulldown
));
1265 assert_eq
!(output
, expect
, "original: {}", input
);
1268 t("`Struct<'a, T>`", "<p><code>Struct<'a, T></code></p>\n");
1269 t("Struct<'a, T>", "<p>Struct<'a, T></p>\n");
1270 t("Struct<br>", "<p>Struct<br></p>\n");