cobalt/pagination/
dates.rs

1use crate::cobalt_model::DateTime;
2use crate::cobalt_model::pagination::DateIndex;
3use crate::document::Document;
4
5use super::{
6    PaginationConfig, Result, ValueView, create_all_paginators, helpers, paginator, sort_posts,
7};
8use helpers::extract_scalar;
9use paginator::Paginator;
10
11pub(crate) fn create_dates_paginators(
12    all_posts: &[&liquid::model::Value],
13    doc: &Document,
14    pagination_cfg: &PaginationConfig,
15) -> Result<Vec<Paginator>> {
16    let mut root_date = distribute_posts_by_dates(all_posts, pagination_cfg)?;
17    walk_dates(&mut root_date, pagination_cfg, doc, None)
18}
19
20fn distribute_posts_by_dates<'a>(
21    all_posts: &[&'a liquid::model::Value],
22    pagination_cfg: &PaginationConfig,
23) -> Result<DateIndexHolder<'a>> {
24    let date_index = &pagination_cfg.date_index;
25    let mut root = DateIndexHolder::new(0u32, None);
26    for post in all_posts {
27        if let Some(published_date) = extract_published_date(post.as_view()) {
28            for idx in date_index {
29                find_or_create_date_holder_and_put_post(&mut root, &published_date, *idx, post);
30            }
31        }
32    }
33    Ok(root)
34}
35#[derive(Debug, Clone)]
36struct DateIndexHolder<'a> {
37    value: u32,
38    field: Option<DateIndex>,
39    posts: Vec<&'a liquid::model::Value>,
40    sub_date: Vec<DateIndexHolder<'a>>,
41}
42
43impl DateIndexHolder<'_> {
44    fn new(value: u32, field: Option<DateIndex>) -> Self {
45        DateIndexHolder {
46            value,
47            field,
48            posts: vec![],
49            sub_date: vec![],
50        }
51    }
52}
53
54fn extract_published_date(value: &'_ dyn ValueView) -> Option<DateTime> {
55    let published_date = extract_scalar(value, "published_date")?;
56    published_date.to_date_time()
57}
58
59fn format_date_holder(d: &DateIndexHolder<'_>) -> liquid::model::Value {
60    let field = d
61        .field
62        .expect("Should not be called with the root DateIndexHolder");
63    let formatted = match field {
64        DateIndex::Year => d.value.to_string(),
65        _ => format!("{:02}", d.value),
66    };
67    liquid::model::Value::scalar(formatted)
68}
69
70fn date_fields_to_array(date: &[DateIndexHolder<'_>]) -> liquid::model::Array {
71    date.iter().map(format_date_holder).collect()
72}
73
74fn find_or_create_date_holder_and_put_post<'a>(
75    date_holder: &mut DateIndexHolder<'a>,
76    published_date: &DateTime,
77    wanted_field: DateIndex,
78    post: &'a liquid::model::Value,
79) {
80    let value = get_date_field_value(published_date, wanted_field);
81    let mut not_found = true;
82    for dh in date_holder.sub_date.iter_mut() {
83        let dh_field = dh
84            .field
85            .expect("Only root has None, we should always have a field");
86        if dh_field < wanted_field {
87            // not at the level we want but still need to find the correct parent
88            // parent should have been created in a previous loop
89            let parent_value = get_date_field_value(published_date, dh_field);
90            if dh.value == parent_value {
91                find_or_create_date_holder_and_put_post(dh, published_date, wanted_field, post);
92                not_found = false;
93            }
94        } else if dh_field == wanted_field && dh.value == value {
95            dh.posts.push(post);
96            not_found = false;
97        }
98    }
99    // not found create one
100    if not_found {
101        let mut holder = DateIndexHolder::new(value, Some(wanted_field));
102        holder.posts.push(post);
103        date_holder.sub_date.push(holder);
104    }
105}
106
107fn get_date_field_value(date: &DateTime, field: DateIndex) -> u32 {
108    match field {
109        DateIndex::Year => {
110            if date.year() < 0 {
111                panic!("Negative year is not supported");
112            }
113            date.year() as u32
114        }
115        DateIndex::Month => date.month() as u32,
116        DateIndex::Day => date.day() as u32,
117        DateIndex::Hour => date.hour() as u32,
118        DateIndex::Minute => date.minute() as u32,
119    }
120}
121
122fn walk_dates(
123    date_holder: &mut DateIndexHolder<'_>,
124    config: &PaginationConfig,
125    doc: &Document,
126    parent_dates: Option<Vec<DateIndexHolder<'_>>>,
127) -> Result<Vec<Paginator>> {
128    let mut cur_date_holder_paginators: Vec<Paginator> = vec![];
129    let mut current_date = parent_dates.unwrap_or_default();
130    if let Some(_field) = date_holder.field {
131        sort_posts(&mut date_holder.posts, config);
132        current_date.push(date_holder.clone());
133        let index_title = liquid::model::Value::array(date_fields_to_array(&current_date));
134        let cur_date_paginators =
135            create_all_paginators(&date_holder.posts, doc, config, Some(&index_title))?;
136        if !cur_date_paginators.is_empty() {
137            cur_date_holder_paginators.extend(cur_date_paginators);
138        } else {
139            let p = Paginator {
140                index_title: Some(index_title),
141                ..Default::default()
142            };
143            cur_date_holder_paginators.push(p);
144        }
145    } else {
146        cur_date_holder_paginators.push(Paginator::default());
147    }
148    for dh in &mut date_holder.sub_date {
149        let mut sub_paginators_holder = walk_dates(dh, config, doc, Some(current_date.clone()))?;
150
151        if let Some(indexes) = cur_date_holder_paginators[0].indexes.as_mut() {
152            indexes.push(sub_paginators_holder[0].clone());
153        } else {
154            cur_date_holder_paginators[0].indexes = Some(vec![sub_paginators_holder[0].clone()]);
155        }
156        cur_date_holder_paginators.append(&mut sub_paginators_holder);
157    }
158    Ok(cur_date_holder_paginators)
159}