cobalt/pagination/
categories.rs

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