Add LinkReplacer pass for pulldown

This commit is contained in:
Manish Goregaokar 2017-12-28 16:29:20 +05:30
parent dae2e22e81
commit e8dd5df69b
5 changed files with 69 additions and 16 deletions

View File

@ -308,6 +308,11 @@ impl Item {
pub fn collapsed_doc_value(&self) -> Option<String> {
self.attrs.collapsed_doc_value()
}
pub fn links(&self) -> Vec<(String, String)> {
self.attrs.links()
}
pub fn is_crate(&self) -> bool {
match self.inner {
StrippedItem(box ModuleItem(Module { is_crate: true, ..})) |
@ -791,6 +796,20 @@ impl Attributes {
None
}
}
/// Get links as a vector
///
/// Cache must be populated before call
pub fn links(&self) -> Vec<(String, String)> {
use html::format::href;
self.links.iter().filter_map(|&(ref s, did)| {
if let Some((href, ..)) = href(did) {
Some((s.clone(), href))
} else {
None
}
}).collect()
}
}
impl AttributesExt for Attributes {

View File

@ -37,7 +37,7 @@ impl ExternalHtml {
)
.and_then(|(ih, bc)|
load_external_files(md_before_content)
.map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, render))))
.map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, &[], render))))
)
.and_then(|(ih, bc)|
load_external_files(after_content)
@ -45,7 +45,7 @@ impl ExternalHtml {
)
.and_then(|(ih, bc, ac)|
load_external_files(md_after_content)
.map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, render))))
.map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, &[], render))))
)
.map(|(ih, bc, ac)|
ExternalHtml {

View File

@ -56,15 +56,16 @@ pub enum RenderType {
/// 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 RenderType);
/// The second parameter is a list of link replacements
// The third parameter is whether we need a shorter version or not.
pub struct Markdown<'a>(pub &'a str, pub &'a [(String, String)], pub RenderType);
/// A unit struct like `Markdown`, that renders the markdown with a
/// table of contents.
pub struct MarkdownWithToc<'a>(pub &'a str, pub RenderType);
/// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
pub struct MarkdownHtml<'a>(pub &'a str, pub RenderType);
/// A unit struct like `Markdown`, that renders only the first paragraph.
pub struct MarkdownSummaryLine<'a>(pub &'a str);
pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]);
/// Controls whether a line will be hidden or shown in HTML output.
///
@ -247,6 +248,38 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
}
}
/// Make headings links with anchor ids and build up TOC.
struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
inner: I,
links: &'b [(String, String)]
}
impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
fn new(iter: I, links: &'b [(String, String)]) -> Self {
LinkReplacer {
inner: iter,
links
}
}
}
impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
type Item = Event<'a>;
fn next(&mut self) -> Option<Self::Item> {
let event = self.inner.next();
if let Some(Event::Start(Tag::Link(dest, text))) = event {
if let Some(&(_, ref replace)) = self.links.into_iter().find(|link| &*link.0 == &*dest) {
Some(Event::Start(Tag::Link(replace.to_owned().into(), text)))
} else {
Some(Event::Start(Tag::Link(dest, text)))
}
} else {
event
}
}
}
/// Make headings links with anchor ids and build up TOC.
struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
inner: I,
@ -996,7 +1029,7 @@ impl LangString {
impl<'a> fmt::Display for Markdown<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let Markdown(md, render_type) = *self;
let Markdown(md, links, render_type) = *self;
// This is actually common enough to special-case
if md.is_empty() { return Ok(()) }
@ -1012,7 +1045,7 @@ impl<'a> fmt::Display for Markdown<'a> {
let mut s = String::with_capacity(md.len() * 3 / 2);
html::push_html(&mut s,
Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None))));
Footnotes::new(CodeBlocks::new(LinkReplacer::new(HeadingLinks::new(p, None), links))));
fmt.write_str(&s)
}
@ -1079,7 +1112,7 @@ impl<'a> fmt::Display for MarkdownHtml<'a> {
impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let MarkdownSummaryLine(md) = *self;
let MarkdownSummaryLine(md, links) = *self;
// This is actually common enough to special-case
if md.is_empty() { return Ok(()) }
@ -1087,7 +1120,7 @@ impl<'a> fmt::Display for MarkdownSummaryLine<'a> {
let mut s = String::new();
html::push_html(&mut s, SummaryLine::new(p));
html::push_html(&mut s, LinkReplacer::new(SummaryLine::new(p), links));
fmt.write_str(&s)
}

View File

@ -1861,12 +1861,13 @@ fn document(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Re
/// rendering between Pulldown and Hoedown.
fn render_markdown(w: &mut fmt::Formatter,
md_text: &str,
links: Vec<(String, String)>,
span: Span,
render_type: RenderType,
prefix: &str,
scx: &SharedContext)
-> fmt::Result {
let (hoedown_output, pulldown_output) = render_text(|ty| format!("{}", Markdown(md_text, ty)));
let (hoedown_output, pulldown_output) = render_text(|ty| format!("{}", Markdown(md_text, &links, ty)));
let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output);
differences.retain(|s| {
match *s {
@ -1898,7 +1899,7 @@ fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLin
} else {
format!("{}", &plain_summary_line(Some(s)))
};
render_markdown(w, &markdown, item.source.clone(), cx.render_type, prefix, &cx.shared)?;
render_markdown(w, &markdown, item.links(), item.source.clone(), cx.render_type, prefix, &cx.shared)?;
} else if !prefix.is_empty() {
write!(w, "<div class='docblock'>{}</div>", prefix)?;
}
@ -1924,7 +1925,7 @@ fn document_full(w: &mut fmt::Formatter, item: &clean::Item,
cx: &Context, prefix: &str) -> fmt::Result {
if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) {
debug!("Doc block: =====\n{}\n=====", s);
render_markdown(w, &*s, item.source.clone(), cx.render_type, prefix, &cx.shared)?;
render_markdown(w, &*s, item.links(), item.source.clone(), cx.render_type, prefix, &cx.shared)?;
} else if !prefix.is_empty() {
write!(w, "<div class='docblock'>{}</div>", prefix)?;
}
@ -2146,10 +2147,10 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
stab_docs = stab_docs,
docs = if cx.render_type == RenderType::Hoedown {
format!("{}",
shorter(Some(&Markdown(doc_value,
shorter(Some(&Markdown(doc_value, &item.links(),
RenderType::Hoedown).to_string())))
} else {
format!("{}", MarkdownSummaryLine(doc_value))
format!("{}", MarkdownSummaryLine(doc_value, &item.links()))
},
class = myitem.type_(),
stab = myitem.stability_class().unwrap_or("".to_string()),
@ -3338,7 +3339,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) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) {
write!(w, "<div class='docblock'>{}</div>", Markdown(&*dox, cx.render_type))?;
write!(w, "<div class='docblock'>{}</div>", Markdown(&*dox, &i.impl_item.links(), cx.render_type))?;
}
}

View File

@ -104,7 +104,7 @@ pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches,
} else {
// Save the state of USED_ID_MAP so it only gets updated once even
// though we're rendering twice.
render_text(|ty| format!("{}", Markdown(text, ty)))
render_text(|ty| format!("{}", Markdown(text, &[], ty)))
};
let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output);