use std::collections::BTreeMap;
use scraps_libs::model::{page_path::PagePath, tag::Tag};
use crate::usecase::build::model::{scope_counts::TypeCounts, site_nav::SiteNav};
#[derive(serde::Serialize, Clone, PartialEq, Debug)]
pub struct NavScopeTera {
title: String,
name: String,
depth: usize,
url_path: String,
counts: TypeCounts,
active: bool,
}
#[derive(serde::Serialize, PartialEq, Debug)]
pub struct NavScopesTera(Vec<NavScopeTera>);
impl NavScopesTera {
pub fn new(site_nav: &SiteNav, active: Option<&Tag>, is_list: bool) -> NavScopesTera {
let counts = &site_nav.scope_counts;
let mut children: BTreeMap<Option<Tag>, Vec<&Tag>> = BTreeMap::new();
for tag in site_nav.tags.iter() {
let parent = tag.ancestors().pop();
children.entry(parent).or_default().push(tag);
}
for siblings in children.values_mut() {
siblings.sort_by(|a, b| {
let (a_total, b_total) = (counts.of(Some(a)).total, counts.of(Some(b)).total);
b_total.cmp(&a_total).then_with(|| a.cmp(b))
});
}
let mut scopes = vec![NavScopeTera {
title: String::new(),
name: "all scraps".to_string(),
depth: 0,
url_path: String::new(),
counts: counts.of(None).clone(),
active: is_list && active.is_none(),
}];
let mut stack: Vec<&Tag> = children.get(&None).cloned().unwrap_or_default();
stack.reverse();
while let Some(tag) = stack.pop() {
scopes.push(NavScopeTera {
title: tag.to_string(),
name: tag.name().to_string(),
depth: tag.depth() - 1,
url_path: PagePath::of_tag(tag).to_url_path(),
counts: counts.of(Some(tag)).clone(),
active: is_list && active == Some(tag),
});
if let Some(kids) = children.get(&Some(tag.clone())) {
stack.extend(kids.iter().rev());
}
}
NavScopesTera(scopes)
}
}
#[cfg(test)]
mod tests {
use scraps_libs::model::scrap::Scrap;
use super::*;
use crate::usecase::build::model::backlinks_map::BacklinksMap;
fn nav(
scraps: &[Scrap],
active: Option<&Tag>,
is_list: bool,
) -> Vec<(String, usize, usize, bool)> {
let site_nav = SiteNav::new(
scraps,
&BacklinksMap::new(scraps),
false,
chrono_tz::UTC,
false,
);
NavScopesTera::new(&site_nav, active, is_list)
.0
.into_iter()
.map(|s| (s.name, s.depth, s.counts.total, s.active))
.collect()
}
#[test]
fn it_lists_the_whole_wiki_then_tags_as_a_tree() {
let scraps = [
Scrap::new("a", &None, "#[[Security/Auth]] #[[Agile]]"),
Scrap::new("b", &None, "#[[Security/Crypto]]"),
Scrap::new("c", &None, "#[[Security/Crypto]]"),
Scrap::new("d", &None, "#[[Agile]]"),
];
assert_eq!(
nav(&scraps, None, true),
vec![
("all scraps".to_string(), 0, 4, true),
("Security".to_string(), 0, 3, false),
("Crypto".to_string(), 1, 2, false),
("Auth".to_string(), 1, 1, false),
("Agile".to_string(), 0, 2, false),
]
);
}
#[test]
fn it_marks_the_scope_a_page_lists() {
let scraps = [Scrap::new("a", &None, "#[[ai/ml]]")];
let ai_ml = Tag::from("ai/ml");
let marked = |active, is_list| -> Vec<bool> {
nav(&scraps, active, is_list)
.into_iter()
.map(|(_, _, _, active)| active)
.collect()
};
assert_eq!(marked(Some(&ai_ml), true), vec![false, false, true]);
assert_eq!(marked(None, false), vec![false, false, false]);
}
}