mirror of
https://github.com/getzola/zola
synced 2024-12-13 22:02:29 +00:00
parent
6153d20047
commit
5532f62c2d
6 changed files with 170 additions and 3 deletions
|
@ -12,6 +12,8 @@ accessible everywhere
|
||||||
- Add `total_pages` to paginator
|
- Add `total_pages` to paginator
|
||||||
- Do not prepend URL prefix to links that start with a scheme
|
- Do not prepend URL prefix to links that start with a scheme
|
||||||
- Allow skipping anchor checking in `zola check` for some URL prefixes
|
- Allow skipping anchor checking in `zola check` for some URL prefixes
|
||||||
|
- Allow skipping prefixes in `zola check`
|
||||||
|
- Check for path collisions when building the site
|
||||||
|
|
||||||
## 0.9.0 (2019-09-28)
|
## 0.9.0 (2019-09-28)
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,18 @@ impl Error {
|
||||||
pub fn chain(value: impl ToString, source: impl Into<Box<dyn StdError>>) -> Self {
|
pub fn chain(value: impl ToString, source: impl Into<Box<dyn StdError>>) -> Self {
|
||||||
Self { kind: ErrorKind::Msg(value.to_string()), source: Some(source.into()) }
|
Self { kind: ErrorKind::Msg(value.to_string()), source: Some(source.into()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an error from a list of path collisions, formatting the output
|
||||||
|
pub fn from_collisions(collisions: Vec<(&str, Vec<String>)>) -> Self {
|
||||||
|
let mut msg = String::from("Found path collisions:\n");
|
||||||
|
|
||||||
|
for (path, filepaths) in collisions {
|
||||||
|
let row = format!("- `{}` from files {:?}\n", path, filepaths);
|
||||||
|
msg.push_str(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { kind: ErrorKind::Msg(msg), source: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Error {
|
impl From<&str> for Error {
|
||||||
|
|
|
@ -263,7 +263,10 @@ mod tests {
|
||||||
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"),
|
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"),
|
||||||
&PathBuf::new(),
|
&PathBuf::new(),
|
||||||
);
|
);
|
||||||
assert_eq!(file.canonical, Path::new("/home/vincent/code/site/content/posts/tutorials/python/index"));
|
assert_eq!(
|
||||||
|
file.canonical,
|
||||||
|
Path::new("/home/vincent/code/site/content/posts/tutorials/python/index")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Regression test for https://github.com/getzola/zola/issues/854
|
/// Regression test for https://github.com/getzola/zola/issues/854
|
||||||
|
@ -277,6 +280,9 @@ mod tests {
|
||||||
);
|
);
|
||||||
let res = file.find_language(&config);
|
let res = file.find_language(&config);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert_eq!(file.canonical, Path::new("/home/vincent/code/site/content/posts/tutorials/python/index"));
|
assert_eq!(
|
||||||
|
file.canonical,
|
||||||
|
Path::new("/home/vincent/code/site/content/posts/tutorials/python/index")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ impl Section {
|
||||||
} else {
|
} else {
|
||||||
section.path = format!("{}/", path);
|
section.path = format!("{}/", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
section.components = section
|
section.components = section
|
||||||
.path
|
.path
|
||||||
.split('/')
|
.split('/')
|
||||||
|
@ -131,7 +132,7 @@ impl Section {
|
||||||
Ok(section)
|
Ok(section)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read and parse a .md file into a Page struct
|
/// Read and parse a .md file into a Section struct
|
||||||
pub fn from_file<P: AsRef<Path>>(
|
pub fn from_file<P: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
|
|
|
@ -9,6 +9,19 @@ use config::Config;
|
||||||
use content::{Page, Section};
|
use content::{Page, Section};
|
||||||
use sorting::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
use sorting::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
||||||
|
|
||||||
|
// Like vec! but for HashSet
|
||||||
|
macro_rules! set {
|
||||||
|
( $( $x:expr ),* ) => {
|
||||||
|
{
|
||||||
|
let mut s = HashSet::new();
|
||||||
|
$(
|
||||||
|
s.insert($x);
|
||||||
|
)*
|
||||||
|
s
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Houses everything about pages and sections
|
/// Houses everything about pages and sections
|
||||||
/// Think of it as a database where each page and section has an id (Key here)
|
/// Think of it as a database where each page and section has an id (Key here)
|
||||||
/// that can be used to find the actual value
|
/// that can be used to find the actual value
|
||||||
|
@ -398,4 +411,128 @@ impl Library {
|
||||||
pub fn contains_page<P: AsRef<Path>>(&self, path: P) -> bool {
|
pub fn contains_page<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||||
self.paths_to_pages.contains_key(path.as_ref())
|
self.paths_to_pages.contains_key(path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This will check every section/page paths + the aliases and ensure none of them
|
||||||
|
/// are colliding.
|
||||||
|
/// Returns (path colliding, [list of files causing that collision])
|
||||||
|
pub fn check_for_path_collisions(&self) -> Vec<(&str, Vec<String>)> {
|
||||||
|
let mut paths: HashMap<&str, HashSet<DefaultKey>> = HashMap::new();
|
||||||
|
|
||||||
|
for (key, page) in &self.pages {
|
||||||
|
paths
|
||||||
|
.entry(&page.path)
|
||||||
|
.and_modify(|s| {
|
||||||
|
s.insert(key);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| set!(key));
|
||||||
|
|
||||||
|
for alias in &page.meta.aliases {
|
||||||
|
paths
|
||||||
|
.entry(&alias)
|
||||||
|
.and_modify(|s| {
|
||||||
|
s.insert(key);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| set!(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, section) in &self.sections {
|
||||||
|
if !section.meta.render {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
paths
|
||||||
|
.entry(§ion.path)
|
||||||
|
.and_modify(|s| {
|
||||||
|
s.insert(key);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| set!(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut collisions = vec![];
|
||||||
|
for (p, keys) in paths {
|
||||||
|
if keys.len() > 1 {
|
||||||
|
let file_paths: Vec<String> = keys
|
||||||
|
.iter()
|
||||||
|
.map(|k| {
|
||||||
|
self.pages.get(*k).map(|p| p.file.relative.clone()).unwrap_or_else(|| {
|
||||||
|
self.sections.get(*k).map(|s| s.file.relative.clone()).unwrap()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
collisions.push((p, file_paths));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collisions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_find_no_collisions() {
|
||||||
|
let mut library = Library::new(10, 10, false);
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.path = "hello".to_string();
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
page2.path = "hello-world".to_string();
|
||||||
|
let mut section = Section::default();
|
||||||
|
section.path = "blog".to_string();
|
||||||
|
library.insert_page(page);
|
||||||
|
library.insert_page(page2);
|
||||||
|
library.insert_section(section);
|
||||||
|
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
assert_eq!(collisions.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_find_collisions_between_pages() {
|
||||||
|
let mut library = Library::new(10, 10, false);
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.path = "hello".to_string();
|
||||||
|
page.file.relative = "hello".to_string();
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
page2.path = "hello".to_string();
|
||||||
|
page2.file.relative = "hello-world".to_string();
|
||||||
|
let mut section = Section::default();
|
||||||
|
section.path = "blog".to_string();
|
||||||
|
section.file.relative = "hello-world".to_string();
|
||||||
|
library.insert_page(page.clone());
|
||||||
|
library.insert_page(page2.clone());
|
||||||
|
library.insert_section(section);
|
||||||
|
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
assert_eq!(collisions.len(), 1);
|
||||||
|
assert_eq!(collisions[0].0, page.path);
|
||||||
|
assert!(collisions[0].1.contains(&page.file.relative));
|
||||||
|
assert!(collisions[0].1.contains(&page2.file.relative));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_find_collisions_with_an_alias() {
|
||||||
|
let mut library = Library::new(10, 10, false);
|
||||||
|
let mut page = Page::default();
|
||||||
|
page.path = "hello".to_string();
|
||||||
|
page.file.relative = "hello".to_string();
|
||||||
|
let mut page2 = Page::default();
|
||||||
|
page2.path = "hello-world".to_string();
|
||||||
|
page2.file.relative = "hello-world".to_string();
|
||||||
|
page2.meta.aliases = vec!["hello".to_string()];
|
||||||
|
let mut section = Section::default();
|
||||||
|
section.path = "blog".to_string();
|
||||||
|
section.file.relative = "hello-world".to_string();
|
||||||
|
library.insert_page(page.clone());
|
||||||
|
library.insert_page(page2.clone());
|
||||||
|
library.insert_section(section);
|
||||||
|
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
assert_eq!(collisions.len(), 1);
|
||||||
|
assert_eq!(collisions[0].0, page.path);
|
||||||
|
assert!(collisions[0].1.contains(&page.file.relative));
|
||||||
|
assert!(collisions[0].1.contains(&page2.file.relative));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,6 +253,14 @@ impl Site {
|
||||||
self.add_page(p, false)?;
|
self.add_page(p, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let library = self.library.read().unwrap();
|
||||||
|
let collisions = library.check_for_path_collisions();
|
||||||
|
if !collisions.is_empty() {
|
||||||
|
return Err(Error::from_collisions(collisions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// taxonomy Tera fns are loaded in `register_early_global_fns`
|
// taxonomy Tera fns are loaded in `register_early_global_fns`
|
||||||
// so we do need to populate it first.
|
// so we do need to populate it first.
|
||||||
self.populate_taxonomies()?;
|
self.populate_taxonomies()?;
|
||||||
|
@ -465,6 +473,7 @@ impl Site {
|
||||||
index_path.file_name().unwrap().to_string_lossy().to_string();
|
index_path.file_name().unwrap().to_string_lossy().to_string();
|
||||||
if let Some(ref l) = lang {
|
if let Some(ref l) = lang {
|
||||||
index_section.file.name = format!("_index.{}", l);
|
index_section.file.name = format!("_index.{}", l);
|
||||||
|
index_section.path = format!("{}/", l);
|
||||||
index_section.permalink = self.config.make_permalink(l);
|
index_section.permalink = self.config.make_permalink(l);
|
||||||
let filename = format!("_index.{}.md", l);
|
let filename = format!("_index.{}.md", l);
|
||||||
index_section.file.path = self.content_path.join(&filename);
|
index_section.file.path = self.content_path.join(&filename);
|
||||||
|
|
Loading…
Reference in a new issue