Merge pull request #1785 from ehuss/summary-escape
Don't try to render summary links as markdown.
This commit is contained in:
commit
ae275ad1b1
|
@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
|
||||||
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||||
use crate::config::BuildConfig;
|
use crate::config::BuildConfig;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use crate::utils::bracket_escape;
|
||||||
|
|
||||||
/// Load a book into memory from its `src/` directory.
|
/// Load a book into memory from its `src/` directory.
|
||||||
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
|
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(|| {
|
let mut f = File::create(&filename).with_context(|| {
|
||||||
format!("Unable to create missing file: {}", filename.display())
|
format!("Unable to create missing file: {}", filename.display())
|
||||||
})?;
|
})?;
|
||||||
writeln!(f, "# {}", link.name)?;
|
writeln!(f, "# {}", bracket_escape(&link.name))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
use crate::utils::bracket_escape;
|
||||||
|
|
||||||
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
|
use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
|
||||||
use pulldown_cmark::{html, Event, Parser};
|
|
||||||
|
|
||||||
// Handlebars helper to construct TOC
|
// Handlebars helper to construct TOC
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -103,7 +102,7 @@ impl HelperDef for RenderToc {
|
||||||
// Part title
|
// Part title
|
||||||
if let Some(title) = item.get("part") {
|
if let Some(title) = item.get("part") {
|
||||||
out.write("<li class=\"part-title\">")?;
|
out.write("<li class=\"part-title\">")?;
|
||||||
write_escaped(out, title)?;
|
out.write(&bracket_escape(title))?;
|
||||||
out.write("</li>")?;
|
out.write("</li>")?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -148,20 +147,7 @@ impl HelperDef for RenderToc {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = item.get("name") {
|
if let Some(name) = item.get("name") {
|
||||||
// Render only inline code blocks
|
out.write(&bracket_escape(name))?
|
||||||
|
|
||||||
// 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)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if path_exists {
|
if path_exists {
|
||||||
|
@ -205,18 +191,3 @@ fn write_li_open_tag(
|
||||||
li.push_str("\">");
|
li.push_str("\">");
|
||||||
out.write(&li)
|
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("<")?,
|
|
||||||
b'>' => out.write(">")?,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
title = &title[next + 1..];
|
|
||||||
}
|
|
||||||
out.write(title)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -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("<"),
|
||||||
|
b'>' => escaped.push_str(">"),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
s = &s[next + 1..];
|
||||||
|
}
|
||||||
|
escaped.push_str(s);
|
||||||
|
escaped
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::bracket_escape;
|
||||||
|
|
||||||
mod render_markdown {
|
mod render_markdown {
|
||||||
use super::super::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");
|
assert_eq!(unique_id_from_content("## Über", &mut id_counter), "Über-2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escaped_brackets() {
|
||||||
|
assert_eq!(bracket_escape(""), "");
|
||||||
|
assert_eq!(bracket_escape("<"), "<");
|
||||||
|
assert_eq!(bracket_escape(">"), ">");
|
||||||
|
assert_eq!(bracket_escape("<>"), "<>");
|
||||||
|
assert_eq!(bracket_escape("<test>"), "<test>");
|
||||||
|
assert_eq!(bracket_escape("a<test>b"), "a<test>b");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Summary formatting tests
|
||||||
|
|
||||||
|
- [*Italic* `code` \*escape\* \`escape2\`](formatted-summary.md)
|
||||||
|
- [Soft
|
||||||
|
line break](soft.md)
|
||||||
|
- [\<escaped tag\>](escaped-tag.md)
|
|
@ -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> <escaped tag></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(),
|
||||||
|
"# <escaped tag>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "search")]
|
#[cfg(feature = "search")]
|
||||||
mod search {
|
mod search {
|
||||||
use crate::dummy_book::DummyBook;
|
use crate::dummy_book::DummyBook;
|
||||||
|
|
Loading…
Reference in New Issue