Rollup merge of #41112 - ollie27:rustdoc_pull, r=GuillaumeGomez
rustdoc: Use pulldown-cmark for Markdown HTML rendering Instead of rendering all of the HTML in rustdoc this relies on pulldown-cmark's `push_html` to do most of the work. A few iterator adapters are used to make rustdoc specific modifications to the output. This also fixes MarkdownHtml and link titles in plain_summary_line. https://ollie27.github.io/rust_doc_test/ is the docs built with this change and #41111. Part of #40912. cc @GuillaumeGomez r? @steveklabnik
This commit is contained in:
commit
f129c0c62b
@ -16,10 +16,10 @@
|
||||
//! of `fmt::Display`. Example usage:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use rustdoc::html::markdown::{Markdown, MarkdownOutputStyle};
|
||||
//! use rustdoc::html::markdown::Markdown;
|
||||
//!
|
||||
//! let s = "My *markdown* _text_";
|
||||
//! let html = format!("{}", Markdown(s, MarkdownOutputStyle::Fancy));
|
||||
//! let html = format!("{}", Markdown(s));
|
||||
//! // ... something using html
|
||||
//! ```
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::default::Default;
|
||||
use std::fmt::{self, Write};
|
||||
use std::str;
|
||||
@ -37,43 +37,23 @@
|
||||
use html::render::derive_id;
|
||||
use html::toc::TocBuilder;
|
||||
use html::highlight;
|
||||
use html::escape::Escape;
|
||||
use test;
|
||||
|
||||
use pulldown_cmark::{self, Event, Parser, Tag};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MarkdownOutputStyle {
|
||||
Compact,
|
||||
Fancy,
|
||||
}
|
||||
|
||||
impl MarkdownOutputStyle {
|
||||
pub fn is_compact(&self) -> bool {
|
||||
match *self {
|
||||
MarkdownOutputStyle::Compact => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_fancy(&self) -> bool {
|
||||
match *self {
|
||||
MarkdownOutputStyle::Fancy => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
use pulldown_cmark::{html, Event, Tag, Parser};
|
||||
use pulldown_cmark::{Options, OPTION_ENABLE_FOOTNOTES, OPTION_ENABLE_TABLES};
|
||||
|
||||
/// A unit struct which has the `fmt::Display` trait implemented. When
|
||||
/// formatted, this struct will emit the HTML corresponding to the rendered
|
||||
/// version of the contained markdown string.
|
||||
// The second parameter is whether we need a shorter version or not.
|
||||
pub struct Markdown<'a>(pub &'a str, pub MarkdownOutputStyle);
|
||||
pub struct Markdown<'a>(pub &'a str);
|
||||
/// A unit struct like `Markdown`, that renders the markdown with a
|
||||
/// table of contents.
|
||||
pub struct MarkdownWithToc<'a>(pub &'a str);
|
||||
/// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
|
||||
pub struct MarkdownHtml<'a>(pub &'a str);
|
||||
/// A unit struct like `Markdown`, that renders only the first paragraph.
|
||||
pub struct MarkdownSummaryLine<'a>(pub &'a str);
|
||||
|
||||
/// Returns Some(code) if `s` is a line that should be stripped from
|
||||
/// documentation but used in example code. `code` is the portion of
|
||||
@ -90,12 +70,21 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new string with all consecutive whitespace collapsed into
|
||||
/// single spaces.
|
||||
/// Convert chars from a title for an id.
|
||||
///
|
||||
/// Any leading or trailing whitespace will be trimmed.
|
||||
fn collapse_whitespace(s: &str) -> String {
|
||||
s.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||
/// "Hello, world!" -> "hello-world"
|
||||
fn slugify(c: char) -> Option<char> {
|
||||
if c.is_alphanumeric() || c == '-' || c == '_' {
|
||||
if c.is_ascii() {
|
||||
Some(c.to_ascii_lowercase())
|
||||
} else {
|
||||
Some(c)
|
||||
}
|
||||
} else if c.is_whitespace() && c.is_ascii() {
|
||||
Some('-')
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Information about the playground if a URL has been specified, containing an
|
||||
@ -104,103 +93,50 @@ fn collapse_whitespace(s: &str) -> String {
|
||||
RefCell::new(None)
|
||||
});
|
||||
|
||||
macro_rules! event_loop_break {
|
||||
($parser:expr, $toc_builder:expr, $shorter:expr, $buf:expr, $escape:expr, $id:expr,
|
||||
$($end_event:pat)|*) => {{
|
||||
fn inner(id: &mut Option<&mut String>, s: &str) {
|
||||
if let Some(ref mut id) = *id {
|
||||
id.push_str(s);
|
||||
/// Adds syntax highlighting and playground Run buttons to rust code blocks.
|
||||
struct CodeBlocks<'a, I: Iterator<Item = Event<'a>>> {
|
||||
inner: I,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'a, I> {
|
||||
fn new(iter: I) -> Self {
|
||||
CodeBlocks {
|
||||
inner: iter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
|
||||
type Item = Event<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let event = self.inner.next();
|
||||
if let Some(Event::Start(Tag::CodeBlock(lang))) = event {
|
||||
if !LangString::parse(&lang).rust {
|
||||
return Some(Event::Start(Tag::CodeBlock(lang)));
|
||||
}
|
||||
} else {
|
||||
return event;
|
||||
}
|
||||
while let Some(event) = $parser.next() {
|
||||
match event {
|
||||
$($end_event)|* => break,
|
||||
Event::Text(ref s) => {
|
||||
debug!("Text");
|
||||
inner($id, s);
|
||||
if $escape {
|
||||
$buf.push_str(&format!("{}", Escape(s)));
|
||||
} else {
|
||||
$buf.push_str(s);
|
||||
}
|
||||
}
|
||||
Event::SoftBreak => {
|
||||
debug!("SoftBreak");
|
||||
if !$buf.is_empty() {
|
||||
$buf.push(' ');
|
||||
}
|
||||
}
|
||||
x => {
|
||||
looper($parser, &mut $buf, Some(x), $toc_builder, $shorter, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
struct ParserWrapper<'a> {
|
||||
parser: Parser<'a>,
|
||||
// The key is the footnote reference. The value is the footnote definition and the id.
|
||||
footnotes: HashMap<String, (String, u16)>,
|
||||
}
|
||||
|
||||
impl<'a> ParserWrapper<'a> {
|
||||
pub fn new(s: &'a str) -> ParserWrapper<'a> {
|
||||
ParserWrapper {
|
||||
parser: Parser::new_ext(s, pulldown_cmark::OPTION_ENABLE_TABLES |
|
||||
pulldown_cmark::OPTION_ENABLE_FOOTNOTES),
|
||||
footnotes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<Event<'a>> {
|
||||
self.parser.next()
|
||||
}
|
||||
|
||||
pub fn get_entry(&mut self, key: &str) -> &mut (String, u16) {
|
||||
let new_id = self.footnotes.keys().count() + 1;
|
||||
let key = key.to_owned();
|
||||
self.footnotes.entry(key).or_insert((String::new(), new_id as u16))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(w: &mut fmt::Formatter,
|
||||
s: &str,
|
||||
print_toc: bool,
|
||||
shorter: MarkdownOutputStyle) -> fmt::Result {
|
||||
fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
|
||||
debug!("CodeBlock");
|
||||
let mut origtext = String::new();
|
||||
while let Some(event) = parser.next() {
|
||||
for event in &mut self.inner {
|
||||
match event {
|
||||
Event::End(Tag::CodeBlock(_)) => break,
|
||||
Event::End(Tag::CodeBlock(..)) => break,
|
||||
Event::Text(ref s) => {
|
||||
origtext.push_str(s);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let origtext = origtext.trim_left();
|
||||
debug!("docblock: ==============\n{:?}\n=======", origtext);
|
||||
|
||||
let lines = origtext.lines().filter(|l| {
|
||||
stripped_filtered_line(*l).is_none()
|
||||
});
|
||||
let text = lines.collect::<Vec<&str>>().join("\n");
|
||||
let block_info = if lang.is_empty() {
|
||||
LangString::all_false()
|
||||
} else {
|
||||
LangString::parse(lang)
|
||||
};
|
||||
if !block_info.rust {
|
||||
buffer.push_str(&format!("<pre><code class=\"language-{}\">{}</code></pre>",
|
||||
lang, text));
|
||||
return
|
||||
}
|
||||
PLAYGROUND.with(|play| {
|
||||
// insert newline to clearly separate it from the
|
||||
// previous block so we can shorten the html output
|
||||
buffer.push('\n');
|
||||
let mut s = String::from("\n");
|
||||
let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
|
||||
if url.is_empty() {
|
||||
return None;
|
||||
@ -210,7 +146,7 @@ fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
|
||||
}).collect::<Vec<&str>>().join("\n");
|
||||
let krate = krate.as_ref().map(|s| &**s);
|
||||
let test = test::maketest(&test, krate, false,
|
||||
&Default::default());
|
||||
&Default::default());
|
||||
let channel = if test.contains("#![feature(") {
|
||||
"&version=nightly"
|
||||
} else {
|
||||
@ -239,376 +175,186 @@ fn dont_escape(c: u8) -> bool {
|
||||
url, test_escaped, channel
|
||||
))
|
||||
});
|
||||
buffer.push_str(&highlight::render_with_highlighting(
|
||||
&text,
|
||||
Some("rust-example-rendered"),
|
||||
None,
|
||||
playground_button.as_ref().map(String::as_str)));
|
||||
});
|
||||
s.push_str(&highlight::render_with_highlighting(
|
||||
&text,
|
||||
Some("rust-example-rendered"),
|
||||
None,
|
||||
playground_button.as_ref().map(String::as_str)));
|
||||
Some(Event::Html(s.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn heading(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle, level: i32) {
|
||||
debug!("Heading");
|
||||
let mut ret = String::new();
|
||||
let mut id = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, ret, true, &mut Some(&mut id),
|
||||
Event::End(Tag::Header(_)));
|
||||
ret = ret.trim_right().to_owned();
|
||||
/// Make headings links with anchor ids and build up TOC.
|
||||
struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
|
||||
inner: I,
|
||||
toc: Option<&'b mut TocBuilder>,
|
||||
buf: VecDeque<Event<'a>>,
|
||||
}
|
||||
|
||||
let id = id.chars().filter_map(|c| {
|
||||
if c.is_alphanumeric() || c == '-' || c == '_' {
|
||||
if c.is_ascii() {
|
||||
Some(c.to_ascii_lowercase())
|
||||
} else {
|
||||
Some(c)
|
||||
}
|
||||
} else if c.is_whitespace() && c.is_ascii() {
|
||||
Some('-')
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect::<String>();
|
||||
|
||||
let id = derive_id(id);
|
||||
|
||||
let sec = toc_builder.as_mut().map_or("".to_owned(), |builder| {
|
||||
format!("{} ", builder.push(level as u32, ret.clone(), id.clone()))
|
||||
});
|
||||
|
||||
// Render the HTML
|
||||
buffer.push_str(&format!("<h{lvl} id=\"{id}\" class=\"section-header\">\
|
||||
<a href=\"#{id}\">{sec}{}</a></h{lvl}>",
|
||||
ret, lvl = level, id = id, sec = sec));
|
||||
}
|
||||
|
||||
fn inline_code(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
|
||||
id: &mut Option<&mut String>) {
|
||||
debug!("InlineCode");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, false, id, Event::End(Tag::Code));
|
||||
buffer.push_str(&format!("<code>{}</code>",
|
||||
Escape(&collapse_whitespace(content.trim_right()))));
|
||||
}
|
||||
|
||||
fn link(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
|
||||
shorter: MarkdownOutputStyle, url: &str, title: &str,
|
||||
id: &mut Option<&mut String>) {
|
||||
debug!("Link");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, true, id,
|
||||
Event::End(Tag::Link(_, _)));
|
||||
if title.is_empty() {
|
||||
buffer.push_str(&format!("<a href=\"{}\">{}</a>", url, content));
|
||||
} else {
|
||||
buffer.push_str(&format!("<a href=\"{}\" title=\"{}\">{}</a>",
|
||||
url, Escape(title), content));
|
||||
impl<'a, 'b, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, I> {
|
||||
fn new(iter: I, toc: Option<&'b mut TocBuilder>) -> Self {
|
||||
HeadingLinks {
|
||||
inner: iter,
|
||||
toc: toc,
|
||||
buf: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn image(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
|
||||
shorter: MarkdownOutputStyle, url: &str, mut title: String,
|
||||
id: &mut Option<&mut String>) {
|
||||
debug!("Image");
|
||||
event_loop_break!(parser, toc_builder, shorter, title, true, id,
|
||||
Event::End(Tag::Image(_, _)));
|
||||
buffer.push_str(&format!("<img src=\"{}\" alt=\"{}\">", url, title));
|
||||
}
|
||||
impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, I> {
|
||||
type Item = Event<'a>;
|
||||
|
||||
fn paragraph(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
|
||||
id: &mut Option<&mut String>) {
|
||||
debug!("Paragraph");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, true, id,
|
||||
Event::End(Tag::Paragraph));
|
||||
buffer.push_str(&format!("<p>{}</p>", content.trim_right()));
|
||||
}
|
||||
|
||||
fn table_cell(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
|
||||
debug!("TableCell");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
|
||||
Event::End(Tag::TableHead) |
|
||||
Event::End(Tag::Table(_)) |
|
||||
Event::End(Tag::TableRow) |
|
||||
Event::End(Tag::TableCell));
|
||||
buffer.push_str(&format!("<td>{}</td>", content.trim()));
|
||||
}
|
||||
|
||||
fn table_row(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
|
||||
debug!("TableRow");
|
||||
let mut content = String::new();
|
||||
while let Some(event) = parser.next() {
|
||||
match event {
|
||||
Event::End(Tag::TableHead) |
|
||||
Event::End(Tag::Table(_)) |
|
||||
Event::End(Tag::TableRow) => break,
|
||||
Event::Start(Tag::TableCell) => {
|
||||
table_cell(parser, &mut content, toc_builder, shorter);
|
||||
}
|
||||
x => {
|
||||
looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
|
||||
}
|
||||
}
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(e) = self.buf.pop_front() {
|
||||
return Some(e);
|
||||
}
|
||||
buffer.push_str(&format!("<tr>{}</tr>", content));
|
||||
}
|
||||
|
||||
fn table_head(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
|
||||
debug!("TableHead");
|
||||
let mut content = String::new();
|
||||
while let Some(event) = parser.next() {
|
||||
match event {
|
||||
Event::End(Tag::TableHead) | Event::End(Tag::Table(_)) => break,
|
||||
Event::Start(Tag::TableCell) => {
|
||||
table_cell(parser, &mut content, toc_builder, shorter);
|
||||
}
|
||||
x => {
|
||||
looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
|
||||
let event = self.inner.next();
|
||||
if let Some(Event::Start(Tag::Header(level))) = event {
|
||||
let mut id = String::new();
|
||||
for event in &mut self.inner {
|
||||
match event {
|
||||
Event::End(Tag::Header(..)) => break,
|
||||
Event::Text(ref text) => id.extend(text.chars().filter_map(slugify)),
|
||||
_ => {},
|
||||
}
|
||||
self.buf.push_back(event);
|
||||
}
|
||||
let id = derive_id(id);
|
||||
|
||||
if let Some(ref mut builder) = self.toc {
|
||||
let mut html_header = String::new();
|
||||
html::push_html(&mut html_header, self.buf.iter().cloned());
|
||||
let sec = builder.push(level as u32, html_header, id.clone());
|
||||
self.buf.push_front(Event::InlineHtml(format!("{} ", sec).into()));
|
||||
}
|
||||
|
||||
self.buf.push_back(Event::InlineHtml(format!("</a></h{}>", level).into()));
|
||||
|
||||
let start_tags = format!("<h{level} id=\"{id}\" class=\"section-header\">\
|
||||
<a href=\"#{id}\">",
|
||||
id = id,
|
||||
level = level);
|
||||
return Some(Event::InlineHtml(start_tags.into()));
|
||||
}
|
||||
if !content.is_empty() {
|
||||
buffer.push_str(&format!("<thead><tr>{}</tr></thead>", content.replace("td>", "th>")));
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts just the first paragraph.
|
||||
struct SummaryLine<'a, I: Iterator<Item = Event<'a>>> {
|
||||
inner: I,
|
||||
started: bool,
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Event<'a>>> SummaryLine<'a, I> {
|
||||
fn new(iter: I) -> Self {
|
||||
SummaryLine {
|
||||
inner: iter,
|
||||
started: false,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn table(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
|
||||
shorter: MarkdownOutputStyle) {
|
||||
debug!("Table");
|
||||
let mut content = String::new();
|
||||
let mut rows = String::new();
|
||||
while let Some(event) = parser.next() {
|
||||
match event {
|
||||
Event::End(Tag::Table(_)) => break,
|
||||
Event::Start(Tag::TableHead) => {
|
||||
table_head(parser, &mut content, toc_builder, shorter);
|
||||
}
|
||||
Event::Start(Tag::TableRow) => {
|
||||
table_row(parser, &mut rows, toc_builder, shorter);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for SummaryLine<'a, I> {
|
||||
type Item = Event<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.started && self.depth == 0 {
|
||||
return None;
|
||||
}
|
||||
buffer.push_str(&format!("<table>{}{}</table>",
|
||||
content,
|
||||
if shorter.is_compact() || rows.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("<tbody>{}</tbody>", rows)
|
||||
}));
|
||||
}
|
||||
|
||||
fn blockquote(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
|
||||
debug!("BlockQuote");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
|
||||
Event::End(Tag::BlockQuote));
|
||||
buffer.push_str(&format!("<blockquote>{}</blockquote>", content.trim_right()));
|
||||
}
|
||||
|
||||
fn list_item(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
|
||||
debug!("ListItem");
|
||||
let mut content = String::new();
|
||||
while let Some(event) = parser.next() {
|
||||
match event {
|
||||
Event::End(Tag::Item) => break,
|
||||
Event::Text(ref s) => {
|
||||
content.push_str(&format!("{}", Escape(s)));
|
||||
}
|
||||
x => {
|
||||
looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
|
||||
}
|
||||
}
|
||||
if shorter.is_compact() {
|
||||
break
|
||||
}
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
}
|
||||
buffer.push_str(&format!("<li>{}</li>", content));
|
||||
}
|
||||
|
||||
fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
|
||||
shorter: MarkdownOutputStyle, is_sorted_list: bool) {
|
||||
debug!("List");
|
||||
let mut content = String::new();
|
||||
while let Some(event) = parser.next() {
|
||||
match event {
|
||||
Event::End(Tag::List(_)) => break,
|
||||
Event::Start(Tag::Item) => {
|
||||
list_item(parser, &mut content, toc_builder, shorter);
|
||||
}
|
||||
x => {
|
||||
looper(parser, &mut content, Some(x), toc_builder, shorter, &mut None);
|
||||
}
|
||||
}
|
||||
if shorter.is_compact() {
|
||||
break
|
||||
}
|
||||
let event = self.inner.next();
|
||||
match event {
|
||||
Some(Event::Start(..)) => self.depth += 1,
|
||||
Some(Event::End(..)) => self.depth -= 1,
|
||||
_ => {}
|
||||
}
|
||||
buffer.push_str(&format!("<{0}>{1}</{0}>",
|
||||
if is_sorted_list { "ol" } else { "ul" },
|
||||
content));
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
fn emphasis(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
|
||||
id: &mut Option<&mut String>) {
|
||||
debug!("Emphasis");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, false, id,
|
||||
Event::End(Tag::Emphasis));
|
||||
buffer.push_str(&format!("<em>{}</em>", content));
|
||||
/// Moves all footnote definitions to the end and add back links to the
|
||||
/// references.
|
||||
struct Footnotes<'a, I: Iterator<Item = Event<'a>>> {
|
||||
inner: I,
|
||||
footnotes: HashMap<String, (Vec<Event<'a>>, u16)>,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Event<'a>>> Footnotes<'a, I> {
|
||||
fn new(iter: I) -> Self {
|
||||
Footnotes {
|
||||
inner: iter,
|
||||
footnotes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
|
||||
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
|
||||
debug!("Strong");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, false, id,
|
||||
Event::End(Tag::Strong));
|
||||
buffer.push_str(&format!("<strong>{}</strong>", content));
|
||||
fn get_entry(&mut self, key: &str) -> &mut (Vec<Event<'a>>, u16) {
|
||||
let new_id = self.footnotes.keys().count() + 1;
|
||||
let key = key.to_owned();
|
||||
self.footnotes.entry(key).or_insert((Vec::new(), new_id as u16))
|
||||
}
|
||||
}
|
||||
|
||||
fn footnote(parser: &mut ParserWrapper, buffer: &mut String,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
|
||||
id: &mut Option<&mut String>) {
|
||||
debug!("FootnoteDefinition");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, true, id,
|
||||
Event::End(Tag::FootnoteDefinition(_)));
|
||||
buffer.push_str(&content);
|
||||
}
|
||||
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
|
||||
type Item = Event<'a>;
|
||||
|
||||
fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
|
||||
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
|
||||
debug!("Rule");
|
||||
let mut content = String::new();
|
||||
event_loop_break!(parser, toc_builder, shorter, content, true, id,
|
||||
Event::End(Tag::Rule));
|
||||
buffer.push_str("<hr>");
|
||||
}
|
||||
|
||||
fn looper<'a>(parser: &'a mut ParserWrapper, buffer: &mut String, next_event: Option<Event<'a>>,
|
||||
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
|
||||
id: &mut Option<&mut String>) -> bool {
|
||||
if let Some(event) = next_event {
|
||||
match event {
|
||||
Event::Start(Tag::CodeBlock(lang)) => {
|
||||
code_block(parser, buffer, &*lang);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
match self.inner.next() {
|
||||
Some(Event::FootnoteReference(ref reference)) => {
|
||||
let entry = self.get_entry(&reference);
|
||||
let reference = format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}\
|
||||
</a></sup>",
|
||||
(*entry).1);
|
||||
return Some(Event::Html(reference.into()));
|
||||
}
|
||||
Event::Start(Tag::Header(level)) => {
|
||||
heading(parser, buffer, toc_builder, shorter, level);
|
||||
Some(Event::Start(Tag::FootnoteDefinition(def))) => {
|
||||
let mut content = Vec::new();
|
||||
for event in &mut self.inner {
|
||||
if let Event::End(Tag::FootnoteDefinition(..)) = event {
|
||||
break;
|
||||
}
|
||||
content.push(event);
|
||||
}
|
||||
let entry = self.get_entry(&def);
|
||||
(*entry).0 = content;
|
||||
}
|
||||
Event::Start(Tag::Code) => {
|
||||
inline_code(parser, buffer, toc_builder, shorter, id);
|
||||
}
|
||||
Event::Start(Tag::Paragraph) => {
|
||||
paragraph(parser, buffer, toc_builder, shorter, id);
|
||||
}
|
||||
Event::Start(Tag::Link(ref url, ref t)) => {
|
||||
link(parser, buffer, toc_builder, shorter, url, t.as_ref(), id);
|
||||
}
|
||||
Event::Start(Tag::Image(ref url, ref t)) => {
|
||||
image(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
|
||||
}
|
||||
Event::Start(Tag::Table(_)) => {
|
||||
table(parser, buffer, toc_builder, shorter);
|
||||
}
|
||||
Event::Start(Tag::BlockQuote) => {
|
||||
blockquote(parser, buffer, toc_builder, shorter);
|
||||
}
|
||||
Event::Start(Tag::List(x)) => {
|
||||
list(parser, buffer, toc_builder, shorter, x.is_some());
|
||||
}
|
||||
Event::Start(Tag::Emphasis) => {
|
||||
emphasis(parser, buffer, toc_builder, shorter, id);
|
||||
}
|
||||
Event::Start(Tag::Strong) => {
|
||||
strong(parser, buffer, toc_builder, shorter, id);
|
||||
}
|
||||
Event::Start(Tag::Rule) => {
|
||||
rule(parser, buffer, toc_builder, shorter, id);
|
||||
}
|
||||
Event::Start(Tag::FootnoteDefinition(ref def)) => {
|
||||
debug!("FootnoteDefinition");
|
||||
let mut content = String::new();
|
||||
let def = def.as_ref();
|
||||
footnote(parser, &mut content, toc_builder, shorter, id);
|
||||
let entry = parser.get_entry(def);
|
||||
let cur_id = (*entry).1;
|
||||
(*entry).0.push_str(&format!("<li id=\"ref{}\">{} <a href=\"#supref{0}\" \
|
||||
rev=\"footnote\">↩</a></p></li>",
|
||||
cur_id,
|
||||
if content.ends_with("</p>") {
|
||||
&content[..content.len() - 4]
|
||||
} else {
|
||||
&content
|
||||
}));
|
||||
}
|
||||
Event::FootnoteReference(ref reference) => {
|
||||
debug!("FootnoteReference");
|
||||
let entry = parser.get_entry(reference.as_ref());
|
||||
buffer.push_str(&format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}</a>\
|
||||
</sup>",
|
||||
(*entry).1));
|
||||
}
|
||||
Event::HardBreak => {
|
||||
debug!("HardBreak");
|
||||
if shorter.is_fancy() {
|
||||
buffer.push_str("<br>");
|
||||
} else if !buffer.is_empty() {
|
||||
buffer.push(' ');
|
||||
Some(e) => return Some(e),
|
||||
None => {
|
||||
if !self.footnotes.is_empty() {
|
||||
let mut v: Vec<_> = self.footnotes.drain().map(|(_, x)| x).collect();
|
||||
v.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
let mut ret = String::from("<div class=\"footnotes\"><hr><ol>");
|
||||
for (mut content, id) in v {
|
||||
write!(ret, "<li id=\"ref{}\">", id).unwrap();
|
||||
let mut is_paragraph = false;
|
||||
if let Some(&Event::End(Tag::Paragraph)) = content.last() {
|
||||
content.pop();
|
||||
is_paragraph = true;
|
||||
}
|
||||
html::push_html(&mut ret, content.into_iter());
|
||||
write!(ret,
|
||||
" <a href=\"#supref{}\" rev=\"footnote\">↩</a>",
|
||||
id).unwrap();
|
||||
if is_paragraph {
|
||||
ret.push_str("</p>");
|
||||
}
|
||||
ret.push_str("</li>");
|
||||
}
|
||||
ret.push_str("</ol></div>");
|
||||
return Some(Event::Html(ret.into()));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Event::Html(h) | Event::InlineHtml(h) => {
|
||||
debug!("Html/InlineHtml");
|
||||
buffer.push_str(&*h);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
shorter.is_fancy()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let mut toc_builder = if print_toc {
|
||||
Some(TocBuilder::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut buffer = String::new();
|
||||
let mut parser = ParserWrapper::new(s);
|
||||
loop {
|
||||
let next_event = parser.next();
|
||||
if !looper(&mut parser, &mut buffer, next_event, &mut toc_builder, shorter, &mut None) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !parser.footnotes.is_empty() {
|
||||
let mut v: Vec<_> = parser.footnotes.values().collect();
|
||||
v.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
buffer.push_str(&format!("<div class=\"footnotes\"><hr><ol>{}</ol></div>",
|
||||
v.iter()
|
||||
.map(|s| s.0.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")));
|
||||
}
|
||||
let mut ret = toc_builder.map_or(Ok(()), |builder| {
|
||||
write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
|
||||
});
|
||||
|
||||
if ret.is_ok() {
|
||||
ret = w.write_str(&buffer);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, position: Span) {
|
||||
@ -755,17 +501,45 @@ fn parse(string: &str) -> LangString {
|
||||
|
||||
impl<'a> fmt::Display for Markdown<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let Markdown(md, shorter) = *self;
|
||||
let Markdown(md) = *self;
|
||||
// This is actually common enough to special-case
|
||||
if md.is_empty() { return Ok(()) }
|
||||
render(fmt, md, false, shorter)
|
||||
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(OPTION_ENABLE_TABLES);
|
||||
opts.insert(OPTION_ENABLE_FOOTNOTES);
|
||||
|
||||
let p = Parser::new_ext(md, opts);
|
||||
|
||||
let mut s = String::with_capacity(md.len() * 3 / 2);
|
||||
|
||||
html::push_html(&mut s,
|
||||
Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
|
||||
|
||||
fmt.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for MarkdownWithToc<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let MarkdownWithToc(md) = *self;
|
||||
render(fmt, md, true, MarkdownOutputStyle::Fancy)
|
||||
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(OPTION_ENABLE_TABLES);
|
||||
opts.insert(OPTION_ENABLE_FOOTNOTES);
|
||||
|
||||
let p = Parser::new_ext(md, opts);
|
||||
|
||||
let mut s = String::with_capacity(md.len() * 3 / 2);
|
||||
|
||||
let mut toc = TocBuilder::new();
|
||||
|
||||
html::push_html(&mut s,
|
||||
Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, Some(&mut toc)))));
|
||||
|
||||
write!(fmt, "<nav id=\"TOC\">{}</nav>", toc.into_toc())?;
|
||||
|
||||
fmt.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
@ -774,7 +548,41 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let MarkdownHtml(md) = *self;
|
||||
// This is actually common enough to special-case
|
||||
if md.is_empty() { return Ok(()) }
|
||||
render(fmt, md, false, MarkdownOutputStyle::Fancy)
|
||||
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(OPTION_ENABLE_TABLES);
|
||||
opts.insert(OPTION_ENABLE_FOOTNOTES);
|
||||
|
||||
let p = Parser::new_ext(md, opts);
|
||||
|
||||
// Treat inline HTML as plain text.
|
||||
let p = p.map(|event| match event {
|
||||
Event::Html(text) | Event::InlineHtml(text) => Event::Text(text),
|
||||
_ => event
|
||||
});
|
||||
|
||||
let mut s = String::with_capacity(md.len() * 3 / 2);
|
||||
|
||||
html::push_html(&mut s,
|
||||
Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
|
||||
|
||||
fmt.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let MarkdownSummaryLine(md) = *self;
|
||||
// This is actually common enough to special-case
|
||||
if md.is_empty() { return Ok(()) }
|
||||
|
||||
let p = Parser::new(md);
|
||||
|
||||
let mut s = String::new();
|
||||
|
||||
html::push_html(&mut s, SummaryLine::new(p));
|
||||
|
||||
fmt.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
@ -796,14 +604,10 @@ fn next(&mut self) -> Option<String> {
|
||||
let next_event = next_event.unwrap();
|
||||
let (ret, is_in) = match next_event {
|
||||
Event::Start(Tag::Paragraph) => (None, 1),
|
||||
Event::Start(Tag::Link(_, ref t)) if !self.is_first => {
|
||||
(Some(t.as_ref().to_owned()), 1)
|
||||
}
|
||||
Event::Start(Tag::Code) => (Some("`".to_owned()), 1),
|
||||
Event::End(Tag::Code) => (Some("`".to_owned()), -1),
|
||||
Event::Start(Tag::Header(_)) => (None, 1),
|
||||
Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
|
||||
Event::End(Tag::Link(_, ref t)) => (Some(t.as_ref().to_owned()), -1),
|
||||
Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1),
|
||||
_ => (None, 0),
|
||||
};
|
||||
@ -834,7 +638,7 @@ fn next(&mut self) -> Option<String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{LangString, Markdown, MarkdownHtml, MarkdownOutputStyle};
|
||||
use super::{LangString, Markdown, MarkdownHtml};
|
||||
use super::plain_summary_line;
|
||||
use html::render::reset_ids;
|
||||
|
||||
@ -874,14 +678,14 @@ fn t(s: &str,
|
||||
#[test]
|
||||
fn issue_17736() {
|
||||
let markdown = "# title";
|
||||
format!("{}", Markdown(markdown, MarkdownOutputStyle::Fancy));
|
||||
format!("{}", Markdown(markdown));
|
||||
reset_ids(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
fn t(input: &str, expect: &str) {
|
||||
let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
|
||||
let output = format!("{}", Markdown(input));
|
||||
assert_eq!(output, expect, "original: {}", input);
|
||||
reset_ids(true);
|
||||
}
|
||||
@ -903,7 +707,7 @@ fn t(input: &str, expect: &str) {
|
||||
#[test]
|
||||
fn test_header_ids_multiple_blocks() {
|
||||
fn t(input: &str, expect: &str) {
|
||||
let output = format!("{}", Markdown(input, MarkdownOutputStyle::Fancy));
|
||||
let output = format!("{}", Markdown(input));
|
||||
assert_eq!(output, expect, "original: {}", input);
|
||||
}
|
||||
|
||||
@ -934,6 +738,7 @@ fn t(input: &str, expect: &str) {
|
||||
}
|
||||
|
||||
t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
|
||||
t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
|
||||
t("code `let x = i32;` ...", "code `let x = i32;` ...");
|
||||
t("type `Type<'static>` ...", "type `Type<'static>` ...");
|
||||
t("# top header", "top header");
|
||||
@ -947,7 +752,8 @@ fn t(input: &str, expect: &str) {
|
||||
assert_eq!(output, expect, "original: {}", input);
|
||||
}
|
||||
|
||||
t("`Struct<'a, T>`", "<p><code>Struct<'a, T></code></p>");
|
||||
t("Struct<'a, T>", "<p>Struct<'a, T></p>");
|
||||
t("`Struct<'a, T>`", "<p><code>Struct<'a, T></code></p>\n");
|
||||
t("Struct<'a, T>", "<p>Struct<'a, T></p>\n");
|
||||
t("Struct<br>", "<p>Struct<br></p>\n");
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@
|
||||
use html::format::{VisSpace, Method, UnsafetySpace, MutableSpace};
|
||||
use html::format::fmt_impl_for_trait_page;
|
||||
use html::item_type::ItemType;
|
||||
use html::markdown::{self, Markdown, MarkdownHtml, MarkdownOutputStyle};
|
||||
use html::markdown::{self, Markdown, MarkdownHtml, MarkdownSummaryLine};
|
||||
use html::{highlight, layout};
|
||||
|
||||
/// A pair of name and its optional document.
|
||||
@ -1651,7 +1651,7 @@ fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLin
|
||||
format!("{}", &plain_summary_line(Some(s)))
|
||||
};
|
||||
write!(w, "<div class='docblock'>{}</div>",
|
||||
Markdown(&markdown, MarkdownOutputStyle::Fancy))?;
|
||||
Markdown(&markdown))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1684,8 +1684,7 @@ fn get_doc_value(item: &clean::Item) -> Option<&str> {
|
||||
fn document_full(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result {
|
||||
if let Some(s) = get_doc_value(item) {
|
||||
write!(w, "<div class='docblock'>{}</div>",
|
||||
Markdown(&format!("{}{}", md_render_assoc_item(item), s),
|
||||
MarkdownOutputStyle::Fancy))?;
|
||||
Markdown(&format!("{}{}", md_render_assoc_item(item), s)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1873,8 +1872,7 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering
|
||||
</tr>",
|
||||
name = *myitem.name.as_ref().unwrap(),
|
||||
stab_docs = stab_docs,
|
||||
docs = shorter(Some(&Markdown(doc_value,
|
||||
MarkdownOutputStyle::Compact).to_string())),
|
||||
docs = MarkdownSummaryLine(doc_value),
|
||||
class = myitem.type_(),
|
||||
stab = myitem.stability_class().unwrap_or("".to_string()),
|
||||
unsafety_flag = unsafety_flag,
|
||||
@ -2904,7 +2902,7 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
|
||||
write!(w, "</span>")?;
|
||||
write!(w, "</h3>\n")?;
|
||||
if let Some(ref dox) = i.impl_item.doc_value() {
|
||||
write!(w, "<div class='docblock'>{}</div>", Markdown(dox, MarkdownOutputStyle::Fancy))?;
|
||||
write!(w, "<div class='docblock'>{}</div>", Markdown(dox))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
use html::render::reset_ids;
|
||||
use html::escape::Escape;
|
||||
use html::markdown;
|
||||
use html::markdown::{Markdown, MarkdownWithToc, MarkdownOutputStyle, find_testable_code};
|
||||
use html::markdown::{Markdown, MarkdownWithToc, find_testable_code};
|
||||
use test::{TestOptions, Collector};
|
||||
|
||||
/// Separate any lines at the start of the file that begin with `# ` or `%`.
|
||||
@ -96,7 +96,7 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
|
||||
let rendered = if include_toc {
|
||||
format!("{}", MarkdownWithToc(text))
|
||||
} else {
|
||||
format!("{}", Markdown(text, MarkdownOutputStyle::Fancy))
|
||||
format!("{}", Markdown(text))
|
||||
};
|
||||
|
||||
let err = write!(
|
||||
|
@ -13,7 +13,8 @@
|
||||
// ignore-tidy-end-whitespace
|
||||
|
||||
// @has foo/fn.f.html
|
||||
// @has - '<p>hard break:<br>after hard break</p>'
|
||||
// @has - '<p>hard break:<br />'
|
||||
// @has - 'after hard break</p>'
|
||||
/// hard break:
|
||||
/// after hard break
|
||||
pub fn f() {}
|
||||
|
@ -13,16 +13,21 @@
|
||||
// ignore-tidy-linelength
|
||||
|
||||
// @has foo/fn.f.html
|
||||
// @has - '<p>markdown test</p><p>this is a <a href="https://example.com" title="this is a title">link</a>.</p><p>hard break: after hard break</p><hr><p>a footnote<sup id="supref1"><a href="#ref1">1</a></sup>.</p><p>another footnote<sup id="supref2"><a href="#ref2">2</a></sup>.</p><p><img src="https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png" alt="Rust"></p><div class="footnotes"><hr><ol><li id="ref1"><p>Thing <a href="#supref1" rev="footnote">↩</a></p></li><li id="ref2"><p>Another Thing <a href="#supref2" rev="footnote">↩</a></p></li></ol></div>'
|
||||
// @has - '<p>markdown test</p>'
|
||||
// @has - '<p>this is a <a href="https://example.com" title="this is a title">link</a>.</p>'
|
||||
// @has - '<hr />'
|
||||
// @has - '<p>a footnote<sup id="supref1"><a href="#ref1">1</a></sup>.</p>'
|
||||
// @has - '<p>another footnote<sup id="supref2"><a href="#ref2">2</a></sup>.</p>'
|
||||
// @has - '<p><img src="https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png" alt="Rust" /></p>'
|
||||
// @has - '<div class="footnotes"><hr><ol><li id="ref1">'
|
||||
// @has - '<p>Thing <a href="#supref1" rev="footnote">↩</a></p></li><li id="ref2">'
|
||||
// @has - '<p>Another Thing <a href="#supref2" rev="footnote">↩</a></p></li></ol></div>'
|
||||
/// markdown test
|
||||
///
|
||||
/// this is a [link].
|
||||
///
|
||||
/// [link]: https://example.com "this is a title"
|
||||
///
|
||||
/// hard break:
|
||||
/// after hard break
|
||||
///
|
||||
/// -----------
|
||||
///
|
||||
/// a footnote[^footnote].
|
||||
@ -36,5 +41,4 @@
|
||||
///
|
||||
///
|
||||
/// ![Rust](https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png)
|
||||
#[deprecated(note = "Struct<T>")]
|
||||
pub fn f() {}
|
||||
|
@ -10,10 +10,11 @@
|
||||
|
||||
#![crate_name = "foo"]
|
||||
|
||||
// ignore-tidy-linelength
|
||||
|
||||
// @has foo/fn.f.html
|
||||
// @has - "<pre class='rust fn'>pub fn f()</pre><div class='docblock'><ol><li>list<ol><li>fooooo</li><li>x</li></ol></li><li>foo</li></ol>"
|
||||
// @has - //ol/li "list"
|
||||
// @has - //ol/li/ol/li "fooooo"
|
||||
// @has - //ol/li/ol/li "x"
|
||||
// @has - //ol/li "foo"
|
||||
/// 1. list
|
||||
/// 1. fooooo
|
||||
/// 2. x
|
||||
@ -21,7 +22,10 @@
|
||||
pub fn f() {}
|
||||
|
||||
// @has foo/fn.foo2.html
|
||||
// @has - "<pre class='rust fn'>pub fn foo2()</pre><div class='docblock'><ul><li>normal list<ul><li><p>sub list</p></li><li><p>new elem still same elem</p><p>and again same elem!</p></li></ul></li><li>new big elem</li></ul>"
|
||||
// @has - //ul/li "normal list"
|
||||
// @has - //ul/li/ul/li "sub list"
|
||||
// @has - //ul/li/ul/li "new elem still same elem and again same elem!"
|
||||
// @has - //ul/li "new big elem"
|
||||
/// * normal list
|
||||
/// * sub list
|
||||
/// * new elem
|
||||
@ -29,4 +33,4 @@ pub fn f() {}
|
||||
///
|
||||
/// and again same elem!
|
||||
/// * new big elem
|
||||
pub fn foo2() {}
|
||||
pub fn foo2() {}
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
use syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap, ErrorMetadata};
|
||||
|
||||
use rustdoc::html::markdown::{Markdown, MarkdownOutputStyle, PLAYGROUND};
|
||||
use rustdoc::html::markdown::{Markdown, PLAYGROUND};
|
||||
use rustc_serialize::json;
|
||||
|
||||
enum OutputFormat {
|
||||
@ -100,7 +100,7 @@ fn error_code_block(&self, output: &mut Write, info: &ErrorMetadata,
|
||||
|
||||
// Description rendered as markdown.
|
||||
match info.description {
|
||||
Some(ref desc) => write!(output, "{}", Markdown(desc, MarkdownOutputStyle::Fancy))?,
|
||||
Some(ref desc) => write!(output, "{}", Markdown(desc))?,
|
||||
None => write!(output, "<p>No description.</p>\n")?,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user