Merge pull request #1785 from ehuss/summary-escape

Don't try to render summary links as markdown.
This commit is contained in:
Eric Huss 2022-04-15 11:20:17 -07:00 committed by GitHub
commit ae275ad1b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 33 deletions

View File

@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use crate::config::BuildConfig;
use crate::errors::*;
use crate::utils::bracket_escape;
/// Load a book into memory from its `src/` directory.
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
@ -53,7 +54,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
let mut f = File::create(&filename).with_context(|| {
format!("Unable to create missing file: {}", filename.display())
})?;
writeln!(f, "# {}", link.name)?;
writeln!(f, "# {}", bracket_escape(&link.name))?;
}
}

View File

@ -1,11 +1,10 @@
use std::collections::BTreeMap;
use std::io;
use std::path::Path;
use crate::utils;
use crate::utils::bracket_escape;
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
use pulldown_cmark::{html, Event, Parser};
// Handlebars helper to construct TOC
#[derive(Clone, Copy)]
@ -103,7 +102,7 @@ impl HelperDef for RenderToc {
// Part title
if let Some(title) = item.get("part") {
out.write("<li class=\"part-title\">")?;
write_escaped(out, title)?;
out.write(&bracket_escape(title))?;
out.write("</li>")?;
continue;
}
@ -148,20 +147,7 @@ impl HelperDef for RenderToc {
}
if let Some(name) = item.get("name") {
// Render only inline code blocks
// filter all events that are not inline code blocks
let parser = Parser::new(name).filter(|event| match *event {
Event::Code(_) | Event::Html(_) | Event::Text(_) => true,
_ => false,
});
// render markdown to html
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
write_escaped(out, &markdown_parsed_name)?;
out.write(&bracket_escape(name))?
}
if path_exists {
@ -205,18 +191,3 @@ fn write_li_open_tag(
li.push_str("\">");
out.write(&li)
}
fn write_escaped(out: &mut dyn Output, mut title: &str) -> io::Result<()> {
let needs_escape: &[char] = &['<', '>'];
while let Some(next) = title.find(needs_escape) {
out.write(&title[..next])?;
match title.as_bytes()[next] {
b'<' => out.write("&lt;")?,
b'>' => out.write("&gt;")?,
_ => unreachable!(),
}
title = &title[next + 1..];
}
out.write(title)?;
Ok(())
}

View File

@ -233,8 +233,26 @@ pub fn log_backtrace(e: &Error) {
}
}
pub(crate) fn bracket_escape(mut s: &str) -> String {
let mut escaped = String::with_capacity(s.len());
let needs_escape: &[char] = &['<', '>'];
while let Some(next) = s.find(needs_escape) {
escaped.push_str(&s[..next]);
match s.as_bytes()[next] {
b'<' => escaped.push_str("&lt;"),
b'>' => escaped.push_str("&gt;"),
_ => unreachable!(),
}
s = &s[next + 1..];
}
escaped.push_str(s);
escaped
}
#[cfg(test)]
mod tests {
use super::bracket_escape;
mod render_markdown {
use super::super::render_markdown;
@ -431,4 +449,14 @@ more text with spaces
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
}
}
#[test]
fn escaped_brackets() {
assert_eq!(bracket_escape(""), "");
assert_eq!(bracket_escape("<"), "&lt;");
assert_eq!(bracket_escape(">"), "&gt;");
assert_eq!(bracket_escape("<>"), "&lt;&gt;");
assert_eq!(bracket_escape("<test>"), "&lt;test&gt;");
assert_eq!(bracket_escape("a<test>b"), "a&lt;test&gt;b");
}
}

View File

@ -0,0 +1,6 @@
# Summary formatting tests
- [*Italic* `code` \*escape\* \`escape2\`](formatted-summary.md)
- [Soft
line break](soft.md)
- [\<escaped tag\>](escaped-tag.md)

View File

@ -621,6 +621,42 @@ fn remove_absolute_components(path: &Path) -> impl Iterator<Item = Component> +
})
}
/// Checks formatting of summary names with inline elements.
#[test]
fn summary_with_markdown_formatting() {
let temp = DummyBook::new().build().unwrap();
let mut cfg = Config::default();
cfg.set("book.src", "summary-formatting").unwrap();
let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
md.build().unwrap();
let rendered_path = temp.path().join("book/formatted-summary.html");
assert_contains_strings(
rendered_path,
&[
r#"<a href="formatted-summary.html" class="active"><strong aria-hidden="true">1.</strong> Italic code *escape* `escape2`</a>"#,
r#"<a href="soft.html"><strong aria-hidden="true">2.</strong> Soft line break</a>"#,
r#"<a href="escaped-tag.html"><strong aria-hidden="true">3.</strong> &lt;escaped tag&gt;</a>"#,
],
);
let generated_md = temp.path().join("summary-formatting/formatted-summary.md");
assert_eq!(
fs::read_to_string(generated_md).unwrap(),
"# Italic code *escape* `escape2`\n"
);
let generated_md = temp.path().join("summary-formatting/soft.md");
assert_eq!(
fs::read_to_string(generated_md).unwrap(),
"# Soft line break\n"
);
let generated_md = temp.path().join("summary-formatting/escaped-tag.md");
assert_eq!(
fs::read_to_string(generated_md).unwrap(),
"# &lt;escaped tag&gt;\n"
);
}
#[cfg(feature = "search")]
mod search {
use crate::dummy_book::DummyBook;