Skip to content

Commit ac3ced8

Browse files
authored
Multiple slugification strategies (#929)
1 parent e9eb0e1 commit ac3ced8

File tree

15 files changed

+300
-244
lines changed

15 files changed

+300
-244
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
### Breaking
66
- Remove `toc` variable in section/page context and pass it to `page.toc` and `section.toc` instead so they are
77
accessible everywhere
8-
- [Slugification](https://en.wikipedia.org/wiki/Slug_(web_publishing)#Slug) of page paths is now optional. By default, every path will be slugified as it is happening right now.
9-
To keep non-ASCII characters, set `slugify_paths = true` in your config.
8+
- [Slugification](https://en.wikipedia.org/wiki/Slug_(web_publishing)#Slug) of paths, taxonomies and anchors is now optional. By default, everything will still be slugified like in previous versions.
9+
See documentation for information on how to disable it.
1010

1111
### Other
1212
- Add zenburn syntax highlighting theme

Cargo.lock

Lines changed: 78 additions & 75 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/config/src/config.rs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::highlighting::THEME_SET;
1212
use crate::theme::Theme;
1313
use errors::{bail, Error, Result};
1414
use utils::fs::read_file_with_error;
15+
use utils::slugs::SlugifyStrategy;
1516

1617
// We want a default base url for tests
1718
static DEFAULT_BASE_URL: &str = "http://a-website.com";
@@ -23,6 +24,24 @@ pub enum Mode {
2324
Check,
2425
}
2526

27+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
28+
#[serde(default)]
29+
pub struct Slugify {
30+
pub paths: SlugifyStrategy,
31+
pub taxonomies: SlugifyStrategy,
32+
pub anchors: SlugifyStrategy,
33+
}
34+
35+
impl Default for Slugify {
36+
fn default() -> Self {
37+
Slugify {
38+
paths: SlugifyStrategy::On,
39+
taxonomies: SlugifyStrategy::On,
40+
anchors: SlugifyStrategy::On,
41+
}
42+
}
43+
}
44+
2645
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
2746
#[serde(default)]
2847
pub struct Language {
@@ -35,7 +54,7 @@ pub struct Language {
3554
}
3655

3756
impl Default for Language {
38-
fn default() -> Language {
57+
fn default() -> Self {
3958
Language { code: String::new(), rss: false, search: false }
4059
}
4160
}
@@ -75,7 +94,7 @@ impl Taxonomy {
7594
}
7695

7796
impl Default for Taxonomy {
78-
fn default() -> Taxonomy {
97+
fn default() -> Self {
7998
Taxonomy {
8099
name: String::new(),
81100
paginate_by: None,
@@ -130,8 +149,6 @@ pub struct Config {
130149
/// key into different language.
131150
translations: HashMap<String, TranslateTerm>,
132151

133-
/// Whether to slugify page and taxonomy URLs (disable for UTF-8 URLs)
134-
pub slugify_paths: bool,
135152
/// Whether to highlight all code blocks found in markdown files. Defaults to false
136153
pub highlight_code: bool,
137154
/// Which themes to use for code highlighting. See Readme for supported themes
@@ -171,6 +188,9 @@ pub struct Config {
171188

172189
pub link_checker: LinkChecker,
173190

191+
/// The setup for which slugification strategies to use for paths, taxonomies and anchors
192+
pub slugify: Slugify,
193+
174194
/// All user params set in [extra] in the config
175195
pub extra: HashMap<String, Toml>,
176196

@@ -356,7 +376,6 @@ impl Default for Config {
356376
title: None,
357377
description: None,
358378
theme: None,
359-
slugify_paths: true,
360379
highlight_code: false,
361380
highlight_theme: "base16-ocean-dark".to_string(),
362381
default_language: "en".to_string(),
@@ -374,6 +393,7 @@ impl Default for Config {
374393
extra_syntaxes: Vec::new(),
375394
extra_syntax_set: None,
376395
link_checker: LinkChecker::default(),
396+
slugify: Slugify::default(),
377397
extra: HashMap::new(),
378398
build_timestamp: Some(1),
379399
}
@@ -382,7 +402,7 @@ impl Default for Config {
382402

383403
#[cfg(test)]
384404
mod tests {
385-
use super::{Config, Theme};
405+
use super::{Config, SlugifyStrategy, Theme};
386406

387407
#[test]
388408
fn can_import_valid_config() {
@@ -619,4 +639,22 @@ skip_prefixes = [
619639
vec!["http://[2001:db8::]/", "https://www.example.com/path",]
620640
);
621641
}
642+
643+
#[test]
644+
fn slugify_strategies() {
645+
let config_str = r#"
646+
title = "My site"
647+
base_url = "example.com"
648+
649+
[slugify]
650+
paths = "on"
651+
taxonomies = "safe"
652+
anchors = "off"
653+
"#;
654+
655+
let config = Config::parse(config_str).unwrap();
656+
assert_eq!(config.slugify.paths, SlugifyStrategy::On);
657+
assert_eq!(config.slugify.taxonomies, SlugifyStrategy::Safe);
658+
assert_eq!(config.slugify.anchors, SlugifyStrategy::Off);
659+
}
622660
}

components/library/src/content/page.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use utils::templates::render_template;
1919
use crate::content::file_info::FileInfo;
2020
use crate::content::has_anchor;
2121
use crate::content::ser::SerializingPage;
22-
use utils::slugs::maybe_slugify_paths;
22+
use utils::slugs::slugify_paths;
2323

2424
lazy_static! {
2525
// Based on https://regex101.com/r/H2n38Z/1/tests
@@ -161,24 +161,24 @@ impl Page {
161161

162162
page.slug = {
163163
if let Some(ref slug) = page.meta.slug {
164-
maybe_slugify_paths(&slug.trim(), config.slugify_paths)
164+
slugify_paths(slug, config.slugify.paths)
165165
} else if page.file.name == "index" {
166166
if let Some(parent) = page.file.path.parent() {
167167
if let Some(slug) = slug_from_dated_filename {
168-
maybe_slugify_paths(&slug, config.slugify_paths)
168+
slugify_paths(&slug, config.slugify.paths)
169169
} else {
170-
maybe_slugify_paths(
170+
slugify_paths(
171171
parent.file_name().unwrap().to_str().unwrap(),
172-
config.slugify_paths,
172+
config.slugify.paths,
173173
)
174174
}
175175
} else {
176-
maybe_slugify_paths(&page.file.name, config.slugify_paths)
176+
slugify_paths(&page.file.name, config.slugify.paths)
177177
}
178178
} else if let Some(slug) = slug_from_dated_filename {
179-
maybe_slugify_paths(&slug, config.slugify_paths)
179+
slugify_paths(&slug, config.slugify.paths)
180180
} else {
181-
maybe_slugify_paths(&page.file.name, config.slugify_paths)
181+
slugify_paths(&page.file.name, config.slugify.paths)
182182
}
183183
};
184184

@@ -379,6 +379,7 @@ mod tests {
379379
use super::Page;
380380
use config::{Config, Language};
381381
use front_matter::InsertAnchor;
382+
use utils::slugs::SlugifyStrategy;
382383

383384
#[test]
384385
fn test_can_parse_a_valid_page() {
@@ -448,7 +449,7 @@ Hello world"#;
448449
+++
449450
Hello world"#;
450451
let mut config = Config::default();
451-
config.slugify_paths = true;
452+
config.slugify.paths = SlugifyStrategy::On;
452453
let res = Page::parse(Path::new("start.md"), content, &config, &PathBuf::new());
453454
assert!(res.is_ok());
454455
let page = res.unwrap();
@@ -465,7 +466,7 @@ Hello world"#;
465466
+++
466467
Hello world"#;
467468
let mut config = Config::default();
468-
config.slugify_paths = false;
469+
config.slugify.paths = SlugifyStrategy::Safe;
469470
let res = Page::parse(Path::new("start.md"), content, &config, &PathBuf::new());
470471
assert!(res.is_ok());
471472
let page = res.unwrap();
@@ -531,7 +532,7 @@ Hello world"#;
531532
#[test]
532533
fn can_make_slug_from_non_slug_filename() {
533534
let mut config = Config::default();
534-
config.slugify_paths = true;
535+
config.slugify.paths = SlugifyStrategy::On;
535536
let res =
536537
Page::parse(Path::new(" file with space.md"), "+++\n+++", &config, &PathBuf::new());
537538
assert!(res.is_ok());
@@ -543,7 +544,7 @@ Hello world"#;
543544
#[test]
544545
fn can_make_path_from_utf8_filename() {
545546
let mut config = Config::default();
546-
config.slugify_paths = false;
547+
config.slugify.paths = SlugifyStrategy::Safe;
547548
let res = Page::parse(Path::new("日本.md"), "+++\n++++", &config, &PathBuf::new());
548549
assert!(res.is_ok());
549550
let page = res.unwrap();

components/library/src/taxonomies/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use utils::templates::render_template;
1111
use crate::content::SerializingPage;
1212
use crate::library::Library;
1313
use crate::sorting::sort_pages_by_date;
14-
use utils::slugs::maybe_slugify_paths;
14+
use utils::slugs::slugify_paths;
1515

1616
#[derive(Debug, Clone, PartialEq, Serialize)]
1717
pub struct SerializedTaxonomyItem<'a> {
@@ -70,7 +70,7 @@ impl TaxonomyItem {
7070
})
7171
.collect();
7272
let (mut pages, ignored_pages) = sort_pages_by_date(data);
73-
let slug = maybe_slugify_paths(name, config.slugify_paths);
73+
let slug = slugify_paths(name, config.slugify.taxonomies);
7474
let permalink = if taxonomy.lang != config.default_language {
7575
config.make_permalink(&format!("/{}/{}/{}", taxonomy.lang, taxonomy.name, slug))
7676
} else {
@@ -235,6 +235,7 @@ mod tests {
235235
use crate::content::Page;
236236
use crate::library::Library;
237237
use config::{Config, Language, Taxonomy as TaxonomyConfig};
238+
use utils::slugs::SlugifyStrategy;
238239

239240
#[test]
240241
fn can_make_taxonomies() {
@@ -565,7 +566,7 @@ mod tests {
565566
#[test]
566567
fn can_make_utf8_taxonomies() {
567568
let mut config = Config::default();
568-
config.slugify_paths = false;
569+
config.slugify.taxonomies = SlugifyStrategy::Safe;
569570
config.languages.push(Language {
570571
rss: false,
571572
code: "fr".to_string(),
@@ -598,7 +599,7 @@ mod tests {
598599
#[test]
599600
fn can_make_slugified_taxonomies_in_multiple_languages() {
600601
let mut config = Config::default();
601-
config.slugify_paths = true;
602+
config.slugify.taxonomies = SlugifyStrategy::On;
602603
config.languages.push(Language {
603604
rss: false,
604605
code: "fr".to_string(),

components/rendering/benches/all.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ fn bench_render_content_with_highlighting(b: &mut test::Bencher) {
8686
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
8787
let permalinks_ctx = HashMap::new();
8888
let config = Config::default();
89-
let context =
90-
RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
89+
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
9190
b.iter(|| render_content(CONTENT, &context).unwrap());
9291
}
9392

@@ -98,8 +97,7 @@ fn bench_render_content_without_highlighting(b: &mut test::Bencher) {
9897
let permalinks_ctx = HashMap::new();
9998
let mut config = Config::default();
10099
config.highlight_code = false;
101-
let context =
102-
RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
100+
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
103101
b.iter(|| render_content(CONTENT, &context).unwrap());
104102
}
105103

@@ -110,8 +108,7 @@ fn bench_render_content_no_shortcode(b: &mut test::Bencher) {
110108
let mut config = Config::default();
111109
config.highlight_code = false;
112110
let permalinks_ctx = HashMap::new();
113-
let context =
114-
RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
111+
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
115112

116113
b.iter(|| render_content(&content2, &context).unwrap());
117114
}
@@ -122,8 +119,7 @@ fn bench_render_shortcodes_one_present(b: &mut test::Bencher) {
122119
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
123120
let config = Config::default();
124121
let permalinks_ctx = HashMap::new();
125-
let context =
126-
RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
122+
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
127123

128124
b.iter(|| render_shortcodes(CONTENT, &context));
129125
}

components/rendering/src/markdown.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use config::highlighting::{get_highlighter, SYNTAX_SET, THEME_SET};
1212
use errors::{Error, Result};
1313
use front_matter::InsertAnchor;
1414
use utils::site::resolve_internal_link;
15-
use utils::slugs::maybe_slugify_anchors;
15+
use utils::slugs::slugify_anchors;
1616
use utils::vec::InsertMany;
1717

1818
use self::cmark::{Event, LinkType, Options, Parser, Tag};
@@ -305,7 +305,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
305305
let id = heading_ref.id.unwrap_or_else(|| {
306306
find_anchor(
307307
&inserted_anchors,
308-
maybe_slugify_anchors(&title, context.config.slugify_paths),
308+
slugify_anchors(&title, context.config.slugify.anchors),
309309
0,
310310
)
311311
});

components/rendering/tests/markdown.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use config::Config;
66
use front_matter::InsertAnchor;
77
use rendering::{render_content, RenderContext};
88
use templates::ZOLA_TERA;
9+
use utils::slugs::SlugifyStrategy;
910

1011
#[test]
1112
fn can_do_render_content_simple() {
@@ -350,7 +351,7 @@ fn can_add_non_slug_id_to_headings() {
350351
let tera_ctx = Tera::default();
351352
let permalinks_ctx = HashMap::new();
352353
let mut config = Config::default();
353-
config.slugify_paths = false;
354+
config.slugify.anchors = SlugifyStrategy::Safe;
354355
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None);
355356
let res = render_content(r#"# L'écologie et vous"#, &context).unwrap();
356357
assert_eq!(res.body, "<h1 id=\"L'écologie_et_vous\">L'écologie et vous</h1>\n");
@@ -880,4 +881,4 @@ fn stops_with_an_error_on_an_empty_link() {
880881

881882
assert!(res.is_err());
882883
assert_eq!(res.unwrap_err().to_string(), expected);
883-
}
884+
}

components/site/tests/site.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -686,14 +686,14 @@ fn can_ignore_markdown_content() {
686686

687687
#[test]
688688
fn check_site() {
689-
let (mut site, _tmp_dir, _public) = build_site("test_site");
689+
let (mut site, _tmp_dir, _public) = build_site("test_site");
690690

691-
assert_eq!(
692-
site.config.link_checker.skip_anchor_prefixes,
693-
vec!["https://github.com/rust-lang/rust/blob/"]
694-
);
695-
assert_eq!(site.config.link_checker.skip_prefixes, vec!["http://[2001:db8::]/"]);
691+
assert_eq!(
692+
site.config.link_checker.skip_anchor_prefixes,
693+
vec!["https://github.com/rust-lang/rust/blob/"]
694+
);
695+
assert_eq!(site.config.link_checker.skip_prefixes, vec!["http://[2001:db8::]/"]);
696696

697-
site.config.enable_check_mode();
698-
site.load().expect("link check test_site");
697+
site.config.enable_check_mode();
698+
site.load().expect("link check test_site");
699699
}

components/templates/src/global_fns/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ mod tests {
346346

347347
use config::{Config, Taxonomy as TaxonomyConfig};
348348
use library::{Library, Taxonomy, TaxonomyItem};
349+
use utils::slugs::SlugifyStrategy;
349350

350351
#[test]
351352
fn can_add_cachebust_to_url() {
@@ -390,7 +391,7 @@ mod tests {
390391
#[test]
391392
fn can_get_taxonomy() {
392393
let mut config = Config::default();
393-
config.slugify_paths = true;
394+
config.slugify.taxonomies = SlugifyStrategy::On;
394395
let taxo_config = TaxonomyConfig {
395396
name: "tags".to_string(),
396397
lang: config.default_language.clone(),
@@ -468,7 +469,7 @@ mod tests {
468469
#[test]
469470
fn can_get_taxonomy_url() {
470471
let mut config = Config::default();
471-
config.slugify_paths = true;
472+
config.slugify.taxonomies = SlugifyStrategy::On;
472473
let taxo_config = TaxonomyConfig {
473474
name: "tags".to_string(),
474475
lang: config.default_language.clone(),

0 commit comments

Comments
 (0)