mirror of
https://github.com/getzola/zola
synced 2024-12-12 21:32: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
|
||||
- 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 prefixes in `zola check`
|
||||
- Check for path collisions when building the site
|
||||
|
||||
## 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 {
|
||||
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 {
|
||||
|
|
|
@ -263,7 +263,10 @@ mod tests {
|
|||
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.md"),
|
||||
&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
|
||||
|
@ -277,6 +280,9 @@ mod tests {
|
|||
);
|
||||
let res = file.find_language(&config);
|
||||
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 {
|
||||
section.path = format!("{}/", path);
|
||||
}
|
||||
|
||||
section.components = section
|
||||
.path
|
||||
.split('/')
|
||||
|
@ -131,7 +132,7 @@ impl 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>>(
|
||||
path: P,
|
||||
config: &Config,
|
||||
|
|
|
@ -9,6 +9,19 @@ use config::Config;
|
|||
use content::{Page, Section};
|
||||
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
|
||||
/// 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
|
||||
|
@ -398,4 +411,128 @@ impl Library {
|
|||
pub fn contains_page<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
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)?;
|
||||
}
|
||||
|
||||
{
|
||||
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`
|
||||
// so we do need to populate it first.
|
||||
self.populate_taxonomies()?;
|
||||
|
@ -465,6 +473,7 @@ impl Site {
|
|||
index_path.file_name().unwrap().to_string_lossy().to_string();
|
||||
if let Some(ref l) = lang {
|
||||
index_section.file.name = format!("_index.{}", l);
|
||||
index_section.path = format!("{}/", l);
|
||||
index_section.permalink = self.config.make_permalink(l);
|
||||
let filename = format!("_index.{}.md", l);
|
||||
index_section.file.path = self.content_path.join(&filename);
|
||||
|
|
Loading…
Reference in a new issue