Improved navigation sorting

This commit is contained in:
ef3d0c3e 2024-08-25 12:02:46 +02:00
parent 6eae5cd79b
commit 46d579247b

View file

@ -7,13 +7,20 @@ use super::compiler::CompiledDocument;
use super::compiler::Target; use super::compiler::Target;
use super::postprocess::PostProcess; use super::postprocess::PostProcess;
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct NavEntry { pub struct NavEntry {
pub(self) entries: Vec<(String, String, Option<String>)>, title: String,
pub(self) children: HashMap<String, NavEntry>, path: String,
previous: Option<String>,
} }
impl NavEntry { #[derive(Debug, Default)]
pub struct NavEntries {
pub(self) entries: Vec<NavEntry>,
pub(self) children: HashMap<String, NavEntries>,
}
impl NavEntries {
// FIXME: Sanitize // FIXME: Sanitize
pub fn compile(&self, target: Target, doc: &RefCell<CompiledDocument>) -> String { pub fn compile(&self, target: Target, doc: &RefCell<CompiledDocument>) -> String {
let doc_borrow = doc.borrow(); let doc_borrow = doc.borrow();
@ -36,16 +43,16 @@ impl NavEntry {
categories: &Vec<&str>, categories: &Vec<&str>,
did_match: bool, did_match: bool,
result: &mut String, result: &mut String,
entry: &NavEntry, entry: &NavEntries,
depth: usize, depth: usize,
) { ) {
// Orphans = Links // Orphans = Links
for (title, path, _) in &entry.entries { for entry in &entry.entries {
result.push_str( result.push_str(
format!( format!(
r#"<li><a href="{}">{}</a></li>"#, r#"<li><a href="{}">{}</a></li>"#,
Compiler::sanitize(target, path), Compiler::sanitize(target, entry.path.as_str()),
Compiler::sanitize(target, title) Compiler::sanitize(target, entry.title.as_str())
) )
.as_str(), .as_str(),
); );
@ -83,34 +90,46 @@ impl NavEntry {
} }
fn sort_entry( fn sort_entry(
left: &(String, String, Option<String>), entrymap: &HashMap<String, Option<String>>,
right: &(String, String, Option<String>), left_title: &str,
right_title: &str,
) -> std::cmp::Ordering { ) -> std::cmp::Ordering {
match (&left.2, &right.2) { let left_previous = entrymap.get(left_title).unwrap();
(Some(_), Some(_)) => left.0.cmp(&right.0), let right_previous = entrymap.get(right_title).unwrap();
match (left_previous, right_previous) {
(Some(lp), Some(rp)) => {
if lp.as_str() == right_title {
std::cmp::Ordering::Greater
} else if rp.as_str() == left_title {
std::cmp::Ordering::Less
} else {
Self::sort_entry(entrymap, lp.as_str(), rp.as_str())
}
}
(Some(lp), None) => { (Some(lp), None) => {
if &right.0 == lp { if right_title == lp.as_str() {
std::cmp::Ordering::Greater std::cmp::Ordering::Greater
} else { } else {
left.0.cmp(&right.0) left_title.cmp(right_title)
} }
} }
(None, Some(rp)) => { (None, Some(rp)) => {
if &left.0 == rp { if left_title == rp.as_str() {
std::cmp::Ordering::Less std::cmp::Ordering::Less
} else { } else {
left.0.cmp(&right.0) left_title.cmp(right_title)
} }
} }
(None, None) => left.0.cmp(&right.0), (None, None) => left_title.cmp(right_title),
} }
} }
} }
pub fn create_navigation( pub fn create_navigation(
docs: &Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, docs: &Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>,
) -> Result<NavEntry, String> { ) -> Result<NavEntries, String> {
let mut nav = NavEntry { let mut nav = NavEntries {
entries: vec![], entries: vec![],
children: HashMap::new(), children: HashMap::new(),
}; };
@ -153,7 +172,7 @@ pub fn create_navigation(
Some(cat_ent) => cat_ent, Some(cat_ent) => cat_ent,
None => { None => {
// Insert // Insert
nav.children.insert(cat.clone(), NavEntry::default()); nav.children.insert(cat.clone(), NavEntries::default());
nav.children.get_mut(cat.as_str()).unwrap() nav.children.get_mut(cat.as_str()).unwrap()
} }
}; };
@ -162,7 +181,9 @@ pub fn create_navigation(
Some(subcat_ent) => subcat_ent, Some(subcat_ent) => subcat_ent,
None => { None => {
// Insert // Insert
cat_ent.children.insert(subcat.clone(), NavEntry::default()); cat_ent
.children
.insert(subcat.clone(), NavEntries::default());
cat_ent.children.get_mut(subcat.as_str()).unwrap() cat_ent.children.get_mut(subcat.as_str()).unwrap()
} }
} }
@ -171,7 +192,7 @@ pub fn create_navigation(
Some(cat_ent) => cat_ent, Some(cat_ent) => cat_ent,
None => { None => {
// Insert // Insert
nav.children.insert(cat.clone(), NavEntry::default()); nav.children.insert(cat.clone(), NavEntries::default());
nav.children.get_mut(cat.as_str()).unwrap() nav.children.get_mut(cat.as_str()).unwrap()
} }
} }
@ -180,13 +201,13 @@ pub fn create_navigation(
}; };
// Find duplicates titles in current parent // Find duplicates titles in current parent
for (ent_title, _, _) in &pent.entries { for entry in &pent.entries {
if ent_title == title { if &entry.title == title {
return Err(format!( return Err(format!(
"Conflicting entry title `{title}` for entries with the same parent: ({})", "Conflicting entry title `{title}` for entries with the same parent: ({})",
pent.entries pent.entries
.iter() .iter()
.map(|(title, _, _)| title.clone()) .map(|entry| entry.title.clone())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
)); ));
@ -199,13 +220,22 @@ pub fn create_navigation(
} }
all_paths.insert(path.clone(), title.clone()); all_paths.insert(path.clone(), title.clone());
pent.entries.push((title.clone(), path.clone(), previous)); pent.entries.push(NavEntry {
title: title.clone(),
path: path.clone(),
previous,
});
} }
// Sort entries // Sort entries
fn sort_entries(nav: &mut NavEntry) { fn sort_entries(nav: &mut NavEntries) {
let mut entrymap = nav
.entries
.iter()
.map(|ent| (ent.title.clone(), ent.previous.clone()))
.collect::<HashMap<String, Option<String>>>();
nav.entries nav.entries
.sort_unstable_by(NavEntry::sort_entry); .sort_by(|l, r| NavEntries::sort_entry(&entrymap, l.title.as_str(), r.title.as_str()));
for (_, child) in &mut nav.children { for (_, child) in &mut nav.children {
sort_entries(child); sort_entries(child);
@ -227,12 +257,32 @@ mod tests {
#[test] #[test]
fn sort() { fn sort() {
let entries: Vec<(String, String, Option<String>)> = vec![ let entries: Vec<NavEntry> = vec![
("Index".into(), "".into(), None), NavEntry {
("AB".into(), "".into(), Some("Index".into())), title: "Index".into(),
("Getting Started".into(), "".into(), Some("Index".into())), path: "".into(),
("Sections".into(), "".into(), Some("Getting Started".into())), previous: None,
("Style".into(), "".into(), Some("Getting Started".into())), },
NavEntry {
title: "AB".into(),
path: "".into(),
previous: Some("Index".into()),
},
NavEntry {
title: "Getting Started".into(),
path: "".into(),
previous: Some("Index".into()),
},
NavEntry {
title: "Sections".into(),
path: "".into(),
previous: Some("Getting Started".into()),
},
NavEntry {
title: "Style".into(),
path: "".into(),
previous: Some("Getting Started".into()),
},
]; ];
let mut shuffled = entries.clone(); let mut shuffled = entries.clone();
for _ in 0..10 { for _ in 0..10 {
@ -241,7 +291,14 @@ mod tests {
shuffled.swap(i, pos as usize); shuffled.swap(i, pos as usize);
} }
shuffled.sort_by(|l, r| NavEntry::sort_entry(l, r)); let mut entrymap = shuffled
.iter()
.map(|ent| (ent.title.clone(), ent.previous.clone()))
.collect::<HashMap<String, Option<String>>>();
shuffled.sort_by(|l, r| {
NavEntries::sort_entry(&entrymap, l.title.as_str(), r.title.as_str())
});
assert_eq!(shuffled, entries); assert_eq!(shuffled, entries);
} }
@ -281,9 +338,21 @@ mod tests {
assert_eq!( assert_eq!(
nav.children.get("First").unwrap().entries, nav.children.get("First").unwrap().entries,
vec![ vec![
("A".to_string(), "1.html".to_string(), None,), NavEntry {
("B".to_string(), "2.html".to_string(), None,), title: "A".to_string(),
("C".to_string(), "0.html".to_string(), None,), path: "1.html".to_string(),
previous: None
},
NavEntry {
title: "B".to_string(),
path: "2.html".to_string(),
previous: None
},
NavEntry {
title: "C".to_string(),
path: "0.html".to_string(),
previous: None
},
] ]
); );
} }