Skip to content

Commit

Permalink
Update pulldown_cmark
Browse files Browse the repository at this point in the history
  • Loading branch information
Keats committed Mar 3, 2022
1 parent 40d7208 commit a67370b
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 55 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## 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)

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion components/libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ svg_metadata = "0.4"
slotmap = "1"
lexical-sort = "0.3"
walkdir = "2"
pulldown-cmark = { version = "0.8", default-features = false }
pulldown-cmark = { version = "0.9", default-features = false, features = ["simd"] }
gh-emoji = "1"
elasticlunr-rs = {version = "2", default-features = false, features = ["da", "no", "de", "du", "es", "fi", "fr", "it", "pt", "ro", "ru", "sv", "tr"] }
ammonia = "3"
Expand Down
97 changes: 63 additions & 34 deletions components/rendering/src/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use libs::gh_emoji::Replacer as EmojiReplacer;
use libs::once_cell::sync::Lazy;
use libs::pulldown_cmark as cmark;
use libs::tera;
use std::fmt::Write;

use crate::context::RenderContext;
use crate::table_of_contents::{make_table_of_contents, Heading};
use errors::{Error, Result};
use front_matter::InsertAnchor;
use libs::pulldown_cmark::escape::escape_html;
use utils::site::resolve_internal_link;
use utils::slugs::slugify_anchors;
use utils::vec::InsertMany;
Expand Down Expand Up @@ -37,11 +39,39 @@ struct HeadingRef {
end_idx: usize,
level: u32,
id: Option<String>,
classes: Vec<String>,
}

impl HeadingRef {
fn new(start: usize, level: u32) -> HeadingRef {
HeadingRef { start_idx: start, end_idx: 0, level, id: None }
fn new(start: usize, level: u32, anchor: Option<String>, classes: &[String]) -> HeadingRef {
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
}
}

Expand Down Expand Up @@ -131,10 +161,15 @@ fn get_heading_refs(events: &[Event]) -> Vec<HeadingRef> {

for (i, event) in events.iter().enumerate() {
match event {
Event::Start(Tag::Heading(level)) => {
heading_refs.push(HeadingRef::new(i, *level));
Event::Start(Tag::Heading(level, anchor, classes)) => {
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;
}
_ => (),
Expand All @@ -161,7 +196,6 @@ pub fn markdown_to_html(

let mut code_block: Option<CodeBlock> = None;

let mut inserted_anchors: Vec<String> = vec![];
let mut headings: Vec<Heading> = vec![];
let mut internal_links = Vec::new();
let mut external_links = Vec::new();
Expand All @@ -174,6 +208,7 @@ pub fn markdown_to_html(
opts.insert(Options::ENABLE_FOOTNOTES);
opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS);
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);

if context.config.markdown.smart_punctuation {
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
Expand Down Expand Up @@ -389,45 +424,34 @@ pub fn markdown_to_html(
})
.collect();

let mut heading_refs = get_heading_refs(&events);
let heading_refs = get_heading_refs(&events);

let mut anchors_to_insert = vec![];

// First heading pass: look for a manually-specified IDs, e.g. `# Heading text {#hash}`
// (This is a separate first pass so that auto IDs can avoid collisions with manual IDs.)
for heading_ref in heading_refs.iter_mut() {
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();
}
}
let mut inserted_anchors = vec![];
for heading in &heading_refs {
if let Some(s) = &heading.id {
inserted_anchors.push(s.to_owned());
}
}

// 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 end_idx = heading_ref.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,
slugify_anchors(&title, context.config.slugify.anchors),
0,
)
});
inserted_anchors.push(id.clone());
));
}

// insert `id` to the tag
let html = format!("<h{lvl} id=\"{id}\">", lvl = heading_ref.level, id = id);
inserted_anchors.push(heading_ref.id.clone().unwrap());
let id = inserted_anchors.last().unwrap();

let html = heading_ref.to_html(&id);
events[start_idx] = Event::Html(html.into());

// generate anchors and places to insert them
Expand All @@ -454,8 +478,13 @@ pub fn markdown_to_html(

// record heading to make table of contents
let permalink = format!("{}#{}", context.current_page_permalink, id);
let h =
Heading { level: heading_ref.level, id, permalink, title, children: Vec::new() };
let h = Heading {
level: heading_ref.level,
id: id.to_owned(),
permalink,
title,
children: Vec::new(),
};
headings.push(h);
}

Expand Down
11 changes: 3 additions & 8 deletions components/rendering/tests/markdown.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::collections::HashMap;
use std::path::PathBuf;

use libs::tera::Tera;

use config::Config;
use errors::Result;
use front_matter::InsertAnchor;
use rendering::{render_content, RenderContext, Rendered};
use rendering::{render_content, RenderContext};
use templates::ZOLA_TERA;
use utils::slugs::SlugifyStrategy;

Expand Down Expand Up @@ -47,11 +45,6 @@ fn can_make_zola_internal_links() {
fn can_handle_heading_ids() {
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![
// Basic
"# Hello",
Expand Down Expand Up @@ -79,6 +72,8 @@ fn can_handle_heading_ids() {
"# **hi**",
// See https://github.com/getzola/zola/issues/569
"# 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;
insta::assert_snapshot!(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ console.log(foo(5));
<tr><td>ext</td><td>extension to be used for dest files.</td></tr>
</tbody></table>
<p>Right aligned columns</p>
<table><thead><tr><th align="right">Option</th><th 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 align="right">engine</td><td 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>
<table><thead><tr><th style="text-align: right">Option</th><th style="text-align: right">Description</th></tr></thead><tbody>
<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 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 style="text-align: right">ext</td><td style="text-align: right">extension to be used for dest files.</td></tr>
</tbody></table>
<h2 id="links">Links</h2>
<p><a href="http://duckduckgo.com">link text</a></p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
source: components/rendering/tests/markdown.rs
assertion_line: 89
assertion_line: 84
expression: body

---
Expand All @@ -11,7 +11,7 @@ expression: body
<h1 id="hello">Hello</h1>
<h1 id="Something_else">Hello</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="-1"></h1>
<h1 id="About"><a href="https://getzola.org/about/">About</a></h1>
Expand All @@ -22,5 +22,6 @@ expression: body
<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>
<p>footnote</p>
<h1 id="classes" class="bold another">Classes</h1>
</div>

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
source: components/rendering/tests/markdown.rs
assertion_line: 84
assertion_line: 79
expression: body

---
Expand All @@ -11,7 +11,7 @@ expression: body
<h1 id="hello">Hello</h1>
<h1 id="Something_else">Hello</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="-1"></h1>
<h1 id="about"><a href="https://getzola.org/about/">About</a></h1>
Expand All @@ -22,5 +22,6 @@ expression: body
<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>
<p>footnote</p>
<h1 id="classes" class="bold another">Classes</h1>
</div>

4 changes: 2 additions & 2 deletions docs/content/documentation/content/linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ For example:
## 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
# 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
Expand Down

0 comments on commit a67370b

Please sign in to comment.