Update pulldown_cmark

This commit is contained in:
Vincent Prouillet 2022-03-04 00:16:48 +01:00
parent 40d7208493
commit a67370b8d8
9 changed files with 90 additions and 55 deletions

View File

@ -2,6 +2,15 @@
## 0.16.0 (unreleased) ## 0.16.0 (unreleased)
### Breaking
- Switch to pulldown-cmark anchor rather than ours, some (very niche) edge cases are not supported anymore, you can
also specify classes on headers now
### Other
- Fix markup for fenced code with linenos
- Make `ignored_content` work with nested paths and directories
- `zola serve/build` can now run from anywhere in a zola directory
## 0.15.3 (2022-01-23) ## 0.15.3 (2022-01-23)

4
Cargo.lock generated
View File

@ -2372,9 +2372,9 @@ dependencies = [
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.8.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"memchr", "memchr",

View File

@ -25,7 +25,7 @@ svg_metadata = "0.4"
slotmap = "1" slotmap = "1"
lexical-sort = "0.3" lexical-sort = "0.3"
walkdir = "2" walkdir = "2"
pulldown-cmark = { version = "0.8", default-features = false } pulldown-cmark = { version = "0.9", default-features = false, features = ["simd"] }
gh-emoji = "1" gh-emoji = "1"
elasticlunr-rs = {version = "2", default-features = false, features = ["da", "no", "de", "du", "es", "fi", "fr", "it", "pt", "ro", "ru", "sv", "tr"] } elasticlunr-rs = {version = "2", default-features = false, features = ["da", "no", "de", "du", "es", "fi", "fr", "it", "pt", "ro", "ru", "sv", "tr"] }
ammonia = "3" ammonia = "3"

View File

@ -2,11 +2,13 @@ use libs::gh_emoji::Replacer as EmojiReplacer;
use libs::once_cell::sync::Lazy; use libs::once_cell::sync::Lazy;
use libs::pulldown_cmark as cmark; use libs::pulldown_cmark as cmark;
use libs::tera; use libs::tera;
use std::fmt::Write;
use crate::context::RenderContext; use crate::context::RenderContext;
use crate::table_of_contents::{make_table_of_contents, Heading}; use crate::table_of_contents::{make_table_of_contents, Heading};
use errors::{Error, Result}; use errors::{Error, Result};
use front_matter::InsertAnchor; use front_matter::InsertAnchor;
use libs::pulldown_cmark::escape::escape_html;
use utils::site::resolve_internal_link; use utils::site::resolve_internal_link;
use utils::slugs::slugify_anchors; use utils::slugs::slugify_anchors;
use utils::vec::InsertMany; use utils::vec::InsertMany;
@ -37,11 +39,39 @@ struct HeadingRef {
end_idx: usize, end_idx: usize,
level: u32, level: u32,
id: Option<String>, id: Option<String>,
classes: Vec<String>,
} }
impl HeadingRef { impl HeadingRef {
fn new(start: usize, level: u32) -> HeadingRef { fn new(start: usize, level: u32, anchor: Option<String>, classes: &[String]) -> HeadingRef {
HeadingRef { start_idx: start, end_idx: 0, level, id: None } HeadingRef { start_idx: start, end_idx: 0, level, id: anchor, classes: classes.to_vec() }
}
fn to_html(&self, id: &str) -> String {
let mut buffer = String::with_capacity(100);
buffer.write_str("<h").unwrap();
buffer.write_str(&format!("{}", self.level)).unwrap();
buffer.write_str(" id=\"").unwrap();
escape_html(&mut buffer, id).unwrap();
buffer.write_str("\"").unwrap();
if !self.classes.is_empty() {
buffer.write_str(" class=\"").unwrap();
let num_classes = self.classes.len();
for (i, class) in self.classes.iter().enumerate() {
escape_html(&mut buffer, class).unwrap();
if i < num_classes - 1 {
buffer.write_str(" ").unwrap();
}
}
buffer.write_str("\"").unwrap();
}
buffer.write_str(">").unwrap();
buffer
} }
} }
@ -131,10 +161,15 @@ fn get_heading_refs(events: &[Event]) -> Vec<HeadingRef> {
for (i, event) in events.iter().enumerate() { for (i, event) in events.iter().enumerate() {
match event { match event {
Event::Start(Tag::Heading(level)) => { Event::Start(Tag::Heading(level, anchor, classes)) => {
heading_refs.push(HeadingRef::new(i, *level)); heading_refs.push(HeadingRef::new(
i,
*level as u32,
anchor.map(|a| a.to_owned()),
&classes.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
));
} }
Event::End(Tag::Heading(_)) => { Event::End(Tag::Heading(_, _, _)) => {
heading_refs.last_mut().expect("Heading end before start?").end_idx = i; heading_refs.last_mut().expect("Heading end before start?").end_idx = i;
} }
_ => (), _ => (),
@ -161,7 +196,6 @@ pub fn markdown_to_html(
let mut code_block: Option<CodeBlock> = None; let mut code_block: Option<CodeBlock> = None;
let mut inserted_anchors: Vec<String> = vec![];
let mut headings: Vec<Heading> = vec![]; let mut headings: Vec<Heading> = vec![];
let mut internal_links = Vec::new(); let mut internal_links = Vec::new();
let mut external_links = Vec::new(); let mut external_links = Vec::new();
@ -174,6 +208,7 @@ pub fn markdown_to_html(
opts.insert(Options::ENABLE_FOOTNOTES); opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH); opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS); opts.insert(Options::ENABLE_TASKLISTS);
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
if context.config.markdown.smart_punctuation { if context.config.markdown.smart_punctuation {
opts.insert(Options::ENABLE_SMART_PUNCTUATION); opts.insert(Options::ENABLE_SMART_PUNCTUATION);
@ -389,45 +424,34 @@ pub fn markdown_to_html(
}) })
.collect(); .collect();
let mut heading_refs = get_heading_refs(&events); let heading_refs = get_heading_refs(&events);
let mut anchors_to_insert = vec![]; let mut anchors_to_insert = vec![];
let mut inserted_anchors = vec![];
// First heading pass: look for a manually-specified IDs, e.g. `# Heading text {#hash}` for heading in &heading_refs {
// (This is a separate first pass so that auto IDs can avoid collisions with manual IDs.) if let Some(s) = &heading.id {
for heading_ref in heading_refs.iter_mut() { inserted_anchors.push(s.to_owned());
let end_idx = heading_ref.end_idx;
if let Event::Text(ref mut text) = events[end_idx - 1] {
if text.as_bytes().last() == Some(&b'}') {
if let Some(mut i) = text.find("{#") {
let id = text[i + 2..text.len() - 1].to_owned();
inserted_anchors.push(id.clone());
while i > 0 && text.as_bytes()[i - 1] == b' ' {
i -= 1;
}
heading_ref.id = Some(id);
*text = text[..i].to_owned().into();
}
}
} }
} }
// Second heading pass: auto-generate remaining IDs, and emit HTML // Second heading pass: auto-generate remaining IDs, and emit HTML
for heading_ref in heading_refs { for mut heading_ref in heading_refs {
let start_idx = heading_ref.start_idx; let start_idx = heading_ref.start_idx;
let end_idx = heading_ref.end_idx; let end_idx = heading_ref.end_idx;
let title = get_text(&events[start_idx + 1..end_idx]); let title = get_text(&events[start_idx + 1..end_idx]);
let id = heading_ref.id.unwrap_or_else(|| {
find_anchor( if heading_ref.id.is_none() {
heading_ref.id = Some(find_anchor(
&inserted_anchors, &inserted_anchors,
slugify_anchors(&title, context.config.slugify.anchors), slugify_anchors(&title, context.config.slugify.anchors),
0, 0,
) ));
}); }
inserted_anchors.push(id.clone());
// insert `id` to the tag inserted_anchors.push(heading_ref.id.clone().unwrap());
let html = format!("<h{lvl} id=\"{id}\">", lvl = heading_ref.level, id = id); let id = inserted_anchors.last().unwrap();
let html = heading_ref.to_html(&id);
events[start_idx] = Event::Html(html.into()); events[start_idx] = Event::Html(html.into());
// generate anchors and places to insert them // generate anchors and places to insert them
@ -454,8 +478,13 @@ pub fn markdown_to_html(
// record heading to make table of contents // record heading to make table of contents
let permalink = format!("{}#{}", context.current_page_permalink, id); let permalink = format!("{}#{}", context.current_page_permalink, id);
let h = let h = Heading {
Heading { level: heading_ref.level, id, permalink, title, children: Vec::new() }; level: heading_ref.level,
id: id.to_owned(),
permalink,
title,
children: Vec::new(),
};
headings.push(h); headings.push(h);
} }

View File

@ -1,12 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf;
use libs::tera::Tera; use libs::tera::Tera;
use config::Config; use config::Config;
use errors::Result;
use front_matter::InsertAnchor; use front_matter::InsertAnchor;
use rendering::{render_content, RenderContext, Rendered}; use rendering::{render_content, RenderContext};
use templates::ZOLA_TERA; use templates::ZOLA_TERA;
use utils::slugs::SlugifyStrategy; use utils::slugs::SlugifyStrategy;
@ -47,11 +45,6 @@ fn can_make_zola_internal_links() {
fn can_handle_heading_ids() { fn can_handle_heading_ids() {
let mut config = Config::default_for_test(); let mut config = Config::default_for_test();
// Tested things: manual IDs; whitespace flexibility; that automatic IDs avoid collision with
// manual IDs; that duplicates are in fact permitted among manual IDs; that any non-plain-text
// in the middle of `{#…}` will disrupt it from being acknowledged as a manual ID (that last
// one could reasonably be considered a bug rather than a feature, but test it either way); one
// workaround for the improbable case where you actually want `{#…}` at the end of a heading.
let cases = vec![ let cases = vec![
// Basic // Basic
"# Hello", "# Hello",
@ -79,6 +72,8 @@ fn can_handle_heading_ids() {
"# **hi**", "# **hi**",
// See https://github.com/getzola/zola/issues/569 // See https://github.com/getzola/zola/issues/569
"# text [^1] there\n[^1]: footnote", "# text [^1] there\n[^1]: footnote",
// Chosen slug that already exists with space
"# Classes {#classes .bold .another}",
]; ];
let body = common::render_with_config(&cases.join("\n"), config.clone()).unwrap().body; let body = common::render_with_config(&cases.join("\n"), config.clone()).unwrap().body;
insta::assert_snapshot!(body); insta::assert_snapshot!(body);

View File

@ -101,10 +101,10 @@ console.log(foo(5));
<tr><td>ext</td><td>extension to be used for dest files.</td></tr> <tr><td>ext</td><td>extension to be used for dest files.</td></tr>
</tbody></table> </tbody></table>
<p>Right aligned columns</p> <p>Right aligned columns</p>
<table><thead><tr><th align="right">Option</th><th align="right">Description</th></tr></thead><tbody> <table><thead><tr><th style="text-align: right">Option</th><th style="text-align: right">Description</th></tr></thead><tbody>
<tr><td align="right">data</td><td align="right">path to data files to supply the data that will be passed into templates.</td></tr> <tr><td style="text-align: right">data</td><td style="text-align: right">path to data files to supply the data that will be passed into templates.</td></tr>
<tr><td align="right">engine</td><td align="right">engine to be used for processing templates. Handlebars is the default.</td></tr> <tr><td style="text-align: right">engine</td><td style="text-align: right">engine to be used for processing templates. Handlebars is the default.</td></tr>
<tr><td align="right">ext</td><td align="right">extension to be used for dest files.</td></tr> <tr><td style="text-align: right">ext</td><td style="text-align: right">extension to be used for dest files.</td></tr>
</tbody></table> </tbody></table>
<h2 id="links">Links</h2> <h2 id="links">Links</h2>
<p><a href="http://duckduckgo.com">link text</a></p> <p><a href="http://duckduckgo.com">link text</a></p>

View File

@ -1,6 +1,6 @@
--- ---
source: components/rendering/tests/markdown.rs source: components/rendering/tests/markdown.rs
assertion_line: 89 assertion_line: 84
expression: body expression: body
--- ---
@ -11,7 +11,7 @@ expression: body
<h1 id="hello">Hello</h1> <h1 id="hello">Hello</h1>
<h1 id="Something_else">Hello</h1> <h1 id="Something_else">Hello</h1>
<h1 id="Workaround_for_literal_{#…}">Workaround for literal {#…}</h1> <h1 id="Workaround_for_literal_{#…}">Workaround for literal {#…}</h1>
<h1 id="Auto_{#matic}">Auto {#<em>matic</em>}</h1> <h1 id="*matic*">Auto</h1>
<h1 id=""></h1> <h1 id=""></h1>
<h1 id="-1"></h1> <h1 id="-1"></h1>
<h1 id="About"><a href="https://getzola.org/about/">About</a></h1> <h1 id="About"><a href="https://getzola.org/about/">About</a></h1>
@ -22,5 +22,6 @@ expression: body
<h1 id="text__there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1> <h1 id="text__there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> <div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>footnote</p> <p>footnote</p>
<h1 id="classes" class="bold another">Classes</h1>
</div> </div>

View File

@ -1,6 +1,6 @@
--- ---
source: components/rendering/tests/markdown.rs source: components/rendering/tests/markdown.rs
assertion_line: 84 assertion_line: 79
expression: body expression: body
--- ---
@ -11,7 +11,7 @@ expression: body
<h1 id="hello">Hello</h1> <h1 id="hello">Hello</h1>
<h1 id="Something_else">Hello</h1> <h1 id="Something_else">Hello</h1>
<h1 id="workaround-for-literal">Workaround for literal {#…}</h1> <h1 id="workaround-for-literal">Workaround for literal {#…}</h1>
<h1 id="auto-matic">Auto {#<em>matic</em>}</h1> <h1 id="*matic*">Auto</h1>
<h1 id=""></h1> <h1 id=""></h1>
<h1 id="-1"></h1> <h1 id="-1"></h1>
<h1 id="about"><a href="https://getzola.org/about/">About</a></h1> <h1 id="about"><a href="https://getzola.org/about/">About</a></h1>
@ -22,5 +22,6 @@ expression: body
<h1 id="text-there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1> <h1 id="text-there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> <div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>footnote</p> <p>footnote</p>
<h1 id="classes" class="bold another">Classes</h1>
</div> </div>

View File

@ -19,10 +19,10 @@ For example:
## Example code <- example-code-1 ## Example code <- example-code-1
``` ```
You can also manually specify an id with a `{#…}` suffix on the heading line: You can also manually specify an id with a `{#…}` suffix on the heading line as well as CSS classes:
```md ```md
# Something manual! {#manual} # Something manual! {#manual .header .bold}
``` ```
This is useful for making deep links robust, either proactively (so that you can later change the text of a heading This is useful for making deep links robust, either proactively (so that you can later change the text of a heading