use std::cmp::Ordering;
use crate::document::Document;
use super::*;
use helpers::extract_categories;
use paginator::Paginator;
#[derive(Debug)]
struct Category<'a> {
cat_path: liquid::model::Array,
posts: Vec<&'a liquid::model::Value>,
sub_cats: Vec<Category<'a>>,
}
impl<'a> Category<'a> {
fn new() -> Self {
Category {
cat_path: vec![],
posts: vec![],
sub_cats: vec![],
}
}
fn with_path<'v>(path: impl Iterator<Item = &'v dyn liquid::ValueView>) -> Self {
Category {
cat_path: path.map(|v| v.to_value()).collect(),
posts: vec![],
sub_cats: vec![],
}
}
fn add_post(&mut self, post: &'a liquid::model::Value) {
self.posts.push(post)
}
}
fn compare_category_path<'a, C, S>(cur_path: C, seek: S) -> Ordering
where
C: Iterator<Item = &'a dyn liquid::ValueView>,
S: Iterator<Item = &'a dyn liquid::ValueView>,
{
cur_path
.map(liquid::model::ValueViewCmp::new)
.partial_cmp(seek.map(liquid::model::ValueViewCmp::new))
.expect("Arrays of same hierarchy level should be fully comparable")
}
fn is_leaf_category(cur_idx: usize, categories: &[&dyn liquid::ValueView]) -> bool {
cur_idx == categories.len()
}
fn construct_cat_full_path<'v>(
cur_idx: usize,
categories: &[&'v dyn liquid::model::ValueView],
) -> Vec<&'v dyn liquid::model::ValueView> {
categories[..cur_idx].to_vec()
}
fn next_category(cur_idx: usize) -> usize {
cur_idx + 1
}
fn parse_categories_list<'a, 'b>(
parent: &'b mut Category<'a>,
cur_idx: usize,
cur_post_categories: &[&dyn liquid::ValueView],
post: &'a liquid::model::Value,
) -> Result<()> {
if cur_idx <= cur_post_categories.len() {
let cat_full_path = construct_cat_full_path(cur_idx, cur_post_categories);
let cur_cat = if let Ok(idx) = parent.sub_cats.binary_search_by(|c| {
compare_category_path(
c.cat_path.iter().map(|v| v.as_view()),
cat_full_path.iter().copied(),
)
}) {
&mut parent.sub_cats[idx]
} else {
let last_idx = parent.sub_cats.len();
parent
.sub_cats
.push(Category::with_path(cat_full_path.into_iter()));
parent.sub_cats.sort_by(|c1, c2| {
compare_category_path(
c1.cat_path.iter().map(|v| v.as_view()),
c2.cat_path.iter().map(|v| v.as_view()),
)
});
&mut parent.sub_cats[last_idx]
};
if is_leaf_category(cur_idx, cur_post_categories) {
cur_cat.add_post(post);
} else {
parse_categories_list(cur_cat, next_category(cur_idx), cur_post_categories, post)?;
}
}
Ok(())
}
fn distribute_posts_by_categories<'a>(
all_posts: &[&'a liquid::model::Value],
) -> Result<Category<'a>> {
let mut root = Category::new();
for post in all_posts {
if let Some(categories) = extract_categories(post.as_view()) {
let categories: Vec<_> = categories.values().collect();
parse_categories_list(&mut root, 1, categories.as_slice(), post)?;
}
}
Ok(root)
}
fn walk_categories<'a, 'b>(
category: &'b mut Category<'a>,
config: &PaginationConfig,
doc: &Document,
) -> Result<Vec<Paginator>> {
let mut cur_cat_paginators_holder: Vec<Paginator> = vec![];
if !category.cat_path.is_empty() {
sort_posts(&mut category.posts, config);
let cur_cat_paginators = create_all_paginators(
&category.posts,
doc,
config,
Some(&liquid::model::Value::array(category.cat_path.clone())),
)?;
if !cur_cat_paginators.is_empty() {
cur_cat_paginators_holder.extend(cur_cat_paginators.into_iter());
} else {
let p = Paginator {
index_title: Some(liquid::model::Value::array(category.cat_path.clone())),
..Default::default()
};
cur_cat_paginators_holder.push(p);
}
} else {
cur_cat_paginators_holder.push(Paginator::default());
}
for c in &mut category.sub_cats {
let mut sub_paginators_holder = walk_categories(c, config, doc)?;
if let Some(indexes) = cur_cat_paginators_holder[0].indexes.as_mut() {
indexes.push(sub_paginators_holder[0].clone());
} else {
cur_cat_paginators_holder[0].indexes = Some(vec![sub_paginators_holder[0].clone()]);
}
cur_cat_paginators_holder.append(&mut sub_paginators_holder);
}
Ok(cur_cat_paginators_holder)
}
pub fn create_categories_paginators(
all_posts: &[&liquid::model::Value],
doc: &Document,
pagination_cfg: &PaginationConfig,
) -> Result<Vec<Paginator>> {
let mut root_cat = distribute_posts_by_categories(all_posts)?;
let paginators_holder = walk_categories(&mut root_cat, pagination_cfg, doc)?;
Ok(paginators_holder)
}
#[cfg(test)]
mod test {
use super::*;
use liquid::model::ArrayView;
#[test]
fn compare_category_path_test() {
let a = liquid::model::array!(["A"]);
let b = liquid::model::array!(["B"]);
assert_eq!(
Ordering::Less,
compare_category_path(a.values(), b.values())
);
assert_eq!(
Ordering::Greater,
compare_category_path(b.values(), a.values())
);
}
}