Skip to content

Commit a67370b

Browse files
committed
Update pulldown_cmark
1 parent 40d7208 commit a67370b

9 files changed

+90
-55
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## 0.16.0 (unreleased)
44

5+
### Breaking
6+
7+
- Switch to pulldown-cmark anchor rather than ours, some (very niche) edge cases are not supported anymore, you can
8+
also specify classes on headers now
9+
10+
### Other
11+
- Fix markup for fenced code with linenos
12+
- Make `ignored_content` work with nested paths and directories
13+
- `zola serve/build` can now run from anywhere in a zola directory
514

615
## 0.15.3 (2022-01-23)
716

Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/libs/Cargo.toml

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

components/rendering/src/markdown.rs

+63-34
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use libs::gh_emoji::Replacer as EmojiReplacer;
22
use libs::once_cell::sync::Lazy;
33
use libs::pulldown_cmark as cmark;
44
use libs::tera;
5+
use std::fmt::Write;
56

67
use crate::context::RenderContext;
78
use crate::table_of_contents::{make_table_of_contents, Heading};
89
use errors::{Error, Result};
910
use front_matter::InsertAnchor;
11+
use libs::pulldown_cmark::escape::escape_html;
1012
use utils::site::resolve_internal_link;
1113
use utils::slugs::slugify_anchors;
1214
use utils::vec::InsertMany;
@@ -37,11 +39,39 @@ struct HeadingRef {
3739
end_idx: usize,
3840
level: u32,
3941
id: Option<String>,
42+
classes: Vec<String>,
4043
}
4144

4245
impl HeadingRef {
43-
fn new(start: usize, level: u32) -> HeadingRef {
44-
HeadingRef { start_idx: start, end_idx: 0, level, id: None }
46+
fn new(start: usize, level: u32, anchor: Option<String>, classes: &[String]) -> HeadingRef {
47+
HeadingRef { start_idx: start, end_idx: 0, level, id: anchor, classes: classes.to_vec() }
48+
}
49+
50+
fn to_html(&self, id: &str) -> String {
51+
let mut buffer = String::with_capacity(100);
52+
buffer.write_str("<h").unwrap();
53+
buffer.write_str(&format!("{}", self.level)).unwrap();
54+
55+
buffer.write_str(" id=\"").unwrap();
56+
escape_html(&mut buffer, id).unwrap();
57+
buffer.write_str("\"").unwrap();
58+
59+
if !self.classes.is_empty() {
60+
buffer.write_str(" class=\"").unwrap();
61+
let num_classes = self.classes.len();
62+
63+
for (i, class) in self.classes.iter().enumerate() {
64+
escape_html(&mut buffer, class).unwrap();
65+
if i < num_classes - 1 {
66+
buffer.write_str(" ").unwrap();
67+
}
68+
}
69+
70+
buffer.write_str("\"").unwrap();
71+
}
72+
73+
buffer.write_str(">").unwrap();
74+
buffer
4575
}
4676
}
4777

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

132162
for (i, event) in events.iter().enumerate() {
133163
match event {
134-
Event::Start(Tag::Heading(level)) => {
135-
heading_refs.push(HeadingRef::new(i, *level));
164+
Event::Start(Tag::Heading(level, anchor, classes)) => {
165+
heading_refs.push(HeadingRef::new(
166+
i,
167+
*level as u32,
168+
anchor.map(|a| a.to_owned()),
169+
&classes.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
170+
));
136171
}
137-
Event::End(Tag::Heading(_)) => {
172+
Event::End(Tag::Heading(_, _, _)) => {
138173
heading_refs.last_mut().expect("Heading end before start?").end_idx = i;
139174
}
140175
_ => (),
@@ -161,7 +196,6 @@ pub fn markdown_to_html(
161196

162197
let mut code_block: Option<CodeBlock> = None;
163198

164-
let mut inserted_anchors: Vec<String> = vec![];
165199
let mut headings: Vec<Heading> = vec![];
166200
let mut internal_links = Vec::new();
167201
let mut external_links = Vec::new();
@@ -174,6 +208,7 @@ pub fn markdown_to_html(
174208
opts.insert(Options::ENABLE_FOOTNOTES);
175209
opts.insert(Options::ENABLE_STRIKETHROUGH);
176210
opts.insert(Options::ENABLE_TASKLISTS);
211+
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
177212

178213
if context.config.markdown.smart_punctuation {
179214
opts.insert(Options::ENABLE_SMART_PUNCTUATION);
@@ -389,45 +424,34 @@ pub fn markdown_to_html(
389424
})
390425
.collect();
391426

392-
let mut heading_refs = get_heading_refs(&events);
427+
let heading_refs = get_heading_refs(&events);
393428

394429
let mut anchors_to_insert = vec![];
395-
396-
// First heading pass: look for a manually-specified IDs, e.g. `# Heading text {#hash}`
397-
// (This is a separate first pass so that auto IDs can avoid collisions with manual IDs.)
398-
for heading_ref in heading_refs.iter_mut() {
399-
let end_idx = heading_ref.end_idx;
400-
if let Event::Text(ref mut text) = events[end_idx - 1] {
401-
if text.as_bytes().last() == Some(&b'}') {
402-
if let Some(mut i) = text.find("{#") {
403-
let id = text[i + 2..text.len() - 1].to_owned();
404-
inserted_anchors.push(id.clone());
405-
while i > 0 && text.as_bytes()[i - 1] == b' ' {
406-
i -= 1;
407-
}
408-
heading_ref.id = Some(id);
409-
*text = text[..i].to_owned().into();
410-
}
411-
}
430+
let mut inserted_anchors = vec![];
431+
for heading in &heading_refs {
432+
if let Some(s) = &heading.id {
433+
inserted_anchors.push(s.to_owned());
412434
}
413435
}
414436

415437
// Second heading pass: auto-generate remaining IDs, and emit HTML
416-
for heading_ref in heading_refs {
438+
for mut heading_ref in heading_refs {
417439
let start_idx = heading_ref.start_idx;
418440
let end_idx = heading_ref.end_idx;
419441
let title = get_text(&events[start_idx + 1..end_idx]);
420-
let id = heading_ref.id.unwrap_or_else(|| {
421-
find_anchor(
442+
443+
if heading_ref.id.is_none() {
444+
heading_ref.id = Some(find_anchor(
422445
&inserted_anchors,
423446
slugify_anchors(&title, context.config.slugify.anchors),
424447
0,
425-
)
426-
});
427-
inserted_anchors.push(id.clone());
448+
));
449+
}
428450

429-
// insert `id` to the tag
430-
let html = format!("<h{lvl} id=\"{id}\">", lvl = heading_ref.level, id = id);
451+
inserted_anchors.push(heading_ref.id.clone().unwrap());
452+
let id = inserted_anchors.last().unwrap();
453+
454+
let html = heading_ref.to_html(&id);
431455
events[start_idx] = Event::Html(html.into());
432456

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

455479
// record heading to make table of contents
456480
let permalink = format!("{}#{}", context.current_page_permalink, id);
457-
let h =
458-
Heading { level: heading_ref.level, id, permalink, title, children: Vec::new() };
481+
let h = Heading {
482+
level: heading_ref.level,
483+
id: id.to_owned(),
484+
permalink,
485+
title,
486+
children: Vec::new(),
487+
};
459488
headings.push(h);
460489
}
461490

components/rendering/tests/markdown.rs

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
use std::collections::HashMap;
2-
use std::path::PathBuf;
32

43
use libs::tera::Tera;
54

65
use config::Config;
7-
use errors::Result;
86
use front_matter::InsertAnchor;
9-
use rendering::{render_content, RenderContext, Rendered};
7+
use rendering::{render_content, RenderContext};
108
use templates::ZOLA_TERA;
119
use utils::slugs::SlugifyStrategy;
1210

@@ -47,11 +45,6 @@ fn can_make_zola_internal_links() {
4745
fn can_handle_heading_ids() {
4846
let mut config = Config::default_for_test();
4947

50-
// Tested things: manual IDs; whitespace flexibility; that automatic IDs avoid collision with
51-
// manual IDs; that duplicates are in fact permitted among manual IDs; that any non-plain-text
52-
// in the middle of `{#…}` will disrupt it from being acknowledged as a manual ID (that last
53-
// one could reasonably be considered a bug rather than a feature, but test it either way); one
54-
// workaround for the improbable case where you actually want `{#…}` at the end of a heading.
5548
let cases = vec![
5649
// Basic
5750
"# Hello",
@@ -79,6 +72,8 @@ fn can_handle_heading_ids() {
7972
"# **hi**",
8073
// See https://github.com/getzola/zola/issues/569
8174
"# text [^1] there\n[^1]: footnote",
75+
// Chosen slug that already exists with space
76+
"# Classes {#classes .bold .another}",
8277
];
8378
let body = common::render_with_config(&cases.join("\n"), config.clone()).unwrap().body;
8479
insta::assert_snapshot!(body);

components/rendering/tests/snapshots/markdown__all_markdown_features_integration.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ console.log(foo(5));
101101
<tr><td>ext</td><td>extension to be used for dest files.</td></tr>
102102
</tbody></table>
103103
<p>Right aligned columns</p>
104-
<table><thead><tr><th align="right">Option</th><th align="right">Description</th></tr></thead><tbody>
105-
<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>
106-
<tr><td align="right">engine</td><td align="right">engine to be used for processing templates. Handlebars is the default.</td></tr>
107-
<tr><td align="right">ext</td><td align="right">extension to be used for dest files.</td></tr>
104+
<table><thead><tr><th style="text-align: right">Option</th><th style="text-align: right">Description</th></tr></thead><tbody>
105+
<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>
106+
<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>
107+
<tr><td style="text-align: right">ext</td><td style="text-align: right">extension to be used for dest files.</td></tr>
108108
</tbody></table>
109109
<h2 id="links">Links</h2>
110110
<p><a href="http://duckduckgo.com">link text</a></p>

components/rendering/tests/snapshots/markdown__can_handle_heading_ids-2.snap

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
source: components/rendering/tests/markdown.rs
3-
assertion_line: 89
3+
assertion_line: 84
44
expression: body
55

66
---
@@ -11,7 +11,7 @@ expression: body
1111
<h1 id="hello">Hello</h1>
1212
<h1 id="Something_else">Hello</h1>
1313
<h1 id="Workaround_for_literal_{#…}">Workaround for literal {#…}</h1>
14-
<h1 id="Auto_{#matic}">Auto {#<em>matic</em>}</h1>
14+
<h1 id="*matic*">Auto</h1>
1515
<h1 id=""></h1>
1616
<h1 id="-1"></h1>
1717
<h1 id="About"><a href="https://getzola.org/about/">About</a></h1>
@@ -22,5 +22,6 @@ expression: body
2222
<h1 id="text__there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1>
2323
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
2424
<p>footnote</p>
25+
<h1 id="classes" class="bold another">Classes</h1>
2526
</div>
2627

components/rendering/tests/snapshots/markdown__can_handle_heading_ids.snap

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
source: components/rendering/tests/markdown.rs
3-
assertion_line: 84
3+
assertion_line: 79
44
expression: body
55

66
---
@@ -11,7 +11,7 @@ expression: body
1111
<h1 id="hello">Hello</h1>
1212
<h1 id="Something_else">Hello</h1>
1313
<h1 id="workaround-for-literal">Workaround for literal {#…}</h1>
14-
<h1 id="auto-matic">Auto {#<em>matic</em>}</h1>
14+
<h1 id="*matic*">Auto</h1>
1515
<h1 id=""></h1>
1616
<h1 id="-1"></h1>
1717
<h1 id="about"><a href="https://getzola.org/about/">About</a></h1>
@@ -22,5 +22,6 @@ expression: body
2222
<h1 id="text-there">text <sup class="footnote-reference"><a href="#1">1</a></sup> there</h1>
2323
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
2424
<p>footnote</p>
25+
<h1 id="classes" class="bold another">Classes</h1>
2526
</div>
2627

docs/content/documentation/content/linking.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ For example:
1919
## Example code <- example-code-1
2020
```
2121

22-
You can also manually specify an id with a `{#…}` suffix on the heading line:
22+
You can also manually specify an id with a `{#…}` suffix on the heading line as well as CSS classes:
2323

2424
```md
25-
# Something manual! {#manual}
25+
# Something manual! {#manual .header .bold}
2626
```
2727

2828
This is useful for making deep links robust, either proactively (so that you can later change the text of a heading

0 commit comments

Comments
 (0)