cobalt/pagination/
categories.rs

1use std::collections::BTreeMap;
2
3use crate::document::Document;
4
5use super::{
6    PaginationConfig, Result, ValueView, create_all_paginators, helpers, paginator, sort_posts,
7};
8use helpers::extract_categories;
9use paginator::Paginator;
10
11pub(crate) fn create_categories_paginators(
12    all_posts: &[&liquid::model::Value],
13    doc: &Document,
14    pagination_cfg: &PaginationConfig,
15) -> Result<Vec<Paginator>> {
16    let mut root_cat = distribute_posts_by_categories(all_posts)?;
17    let paginators_holder = walk_categories(&mut root_cat, pagination_cfg, doc)?;
18    Ok(paginators_holder)
19}
20
21fn distribute_posts_by_categories<'a>(
22    all_posts: &[&'a liquid::model::Value],
23) -> Result<Category<'a>> {
24    let mut root = Category::new();
25    for post in all_posts {
26        if let Some(categories) = extract_categories(post.as_view()) {
27            let categories: Vec<_> = categories.values().collect();
28            parse_categories_list(&mut root, categories.as_slice(), post)?;
29        }
30    }
31    Ok(root)
32}
33
34/// construct a hierarchy of Categories with their posts from a list of categories
35fn parse_categories_list<'a>(
36    mut parent: &mut Category<'a>,
37    post_categories: &[&dyn ValueView],
38    post: &'a liquid::model::Value,
39) -> Result<()> {
40    for i in 0..post_categories.len() {
41        let cat_name = post_categories[i].to_kstr().to_string();
42        parent = parent
43            .sub_cats
44            .entry(cat_name)
45            .or_insert_with(|| Category::with_path(post_categories[0..=i].iter().copied()));
46    }
47    parent.add_post(post);
48    Ok(())
49}
50
51#[derive(Default, Debug)]
52struct Category<'a> {
53    cat_path: liquid::model::Array,
54    posts: Vec<&'a liquid::model::Value>,
55    sub_cats: BTreeMap<String, Category<'a>>,
56}
57
58impl<'a> Category<'a> {
59    fn new() -> Self {
60        Default::default()
61    }
62
63    fn with_path<'v>(path: impl Iterator<Item = &'v dyn ValueView>) -> Self {
64        let mut c = Self::new();
65        c.cat_path = path.map(|v| v.to_value()).collect();
66        c
67    }
68
69    fn add_post(&mut self, post: &'a liquid::model::Value) {
70        self.posts.push(post);
71    }
72}
73
74// walk the categories tree and construct Paginator for each node,
75// filling `pages` and `indexes` accordingly
76fn walk_categories(
77    category: &mut Category<'_>,
78    config: &PaginationConfig,
79    doc: &Document,
80) -> Result<Vec<Paginator>> {
81    let mut cur_cat_paginators_holder: Vec<Paginator> = vec![];
82    if !category.cat_path.is_empty() {
83        sort_posts(&mut category.posts, config);
84        let cur_cat_paginators = create_all_paginators(
85            &category.posts,
86            doc,
87            config,
88            Some(&liquid::model::Value::array(category.cat_path.clone())),
89        )?;
90        if !cur_cat_paginators.is_empty() {
91            cur_cat_paginators_holder.extend(cur_cat_paginators);
92        } else {
93            let p = Paginator {
94                index_title: Some(liquid::model::Value::array(category.cat_path.clone())),
95                ..Default::default()
96            };
97            cur_cat_paginators_holder.push(p);
98        }
99    } else {
100        cur_cat_paginators_holder.push(Paginator::default());
101    }
102    for c in category.sub_cats.values_mut() {
103        let mut sub_paginators_holder = walk_categories(c, config, doc)?;
104
105        if let Some(indexes) = cur_cat_paginators_holder[0].indexes.as_mut() {
106            indexes.push(sub_paginators_holder[0].clone());
107        } else {
108            cur_cat_paginators_holder[0].indexes = Some(vec![sub_paginators_holder[0].clone()]);
109        }
110        cur_cat_paginators_holder.append(&mut sub_paginators_holder);
111    }
112    Ok(cur_cat_paginators_holder)
113}