cobalt/pagination/
mod.rs

1use std::cmp::Ordering;
2
3use liquid::ValueView;
4
5use crate::cobalt_model::SortOrder;
6use crate::cobalt_model::pagination::Include;
7use crate::cobalt_model::pagination::PaginationConfig;
8use crate::cobalt_model::permalink;
9use crate::cobalt_model::slug;
10
11use crate::document;
12use crate::document::Document;
13use crate::error::Result;
14
15mod all;
16mod categories;
17mod dates;
18mod helpers;
19mod paginator;
20mod tags;
21
22use paginator::Paginator;
23
24pub(crate) fn generate_paginators(
25    doc: &mut Document,
26    posts_data: &[liquid::model::Value],
27) -> Result<Vec<Paginator>> {
28    let config = doc
29        .front
30        .pagination
31        .as_ref()
32        .expect("Front should have pagination here.");
33    let mut all_posts: Vec<_> = posts_data.iter().collect();
34    match config.include {
35        Include::All => {
36            sort_posts(&mut all_posts, config);
37            all::create_all_paginators(&all_posts, doc, config, None)
38        }
39        Include::Tags => tags::create_tags_paginators(&all_posts, doc, config),
40        Include::Categories => categories::create_categories_paginators(&all_posts, doc, config),
41        Include::Dates => dates::create_dates_paginators(&all_posts, doc, config),
42        Include::None => {
43            unreachable!("PaginationConfigBuilder should have lead to a None for pagination.")
44        }
45    }
46}
47
48// sort posts by multiple criteria
49fn sort_posts(posts: &mut [&liquid::model::Value], config: &PaginationConfig) {
50    let order: fn(liquid::model::ScalarCow<'_>, liquid::model::ScalarCow<'_>) -> Ordering =
51        match config.order {
52            SortOrder::Desc => {
53                |a, b: liquid::model::ScalarCow<'_>| b.partial_cmp(&a).unwrap_or(Ordering::Equal)
54            }
55            SortOrder::Asc => {
56                |a: liquid::model::ScalarCow<'_>, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)
57            }
58            SortOrder::None => {
59                // when built, order is set like this:
60                // `order.unwrap_or(SortOrder::Desc);` so it's unreachable
61                unreachable!(
62                    "Sort order should have default value when constructing PaginationConfig"
63                )
64            }
65        };
66    posts.sort_by(|a, b| {
67        let keys = &config.sort_by;
68        let mut cmp = Ordering::Less;
69        for k in keys {
70            cmp = match (
71                helpers::extract_scalar(a.as_view(), k),
72                helpers::extract_scalar(b.as_view(), k),
73            ) {
74                (Some(a), Some(b)) => order(a, b),
75                (None, None) => Ordering::Equal,
76                (_, None) => Ordering::Greater,
77                (None, _) => Ordering::Less,
78            };
79            if cmp != Ordering::Equal {
80                return cmp;
81            }
82        }
83        cmp
84    });
85}
86
87fn pagination_attributes(page_num: i32) -> liquid::Object {
88    let attributes: liquid::Object = vec![("num".into(), liquid::model::Value::scalar(page_num))]
89        .into_iter()
90        .collect();
91    attributes
92}
93
94fn index_to_string(index: &liquid::model::Value) -> String {
95    if let Some(index) = index.as_array() {
96        // categories
97        let mut s: String = index
98            .values()
99            .map(|i| {
100                let mut s = slug::slugify(i.to_kstr().into_string());
101                s.push('/');
102                s
103            })
104            .collect();
105        s.pop(); // remove last '/'
106        s
107    } else {
108        slug::slugify(index.to_kstr().into_string())
109    }
110}
111
112fn interpret_permalink(
113    config: &PaginationConfig,
114    doc: &Document,
115    page_num: usize,
116    index: Option<&liquid::model::Value>,
117) -> Result<String> {
118    let mut attributes = document::permalink_attributes(&doc.front, &doc.file_path);
119    let permalink = permalink::explode_permalink(&config.front_permalink, &attributes)?;
120    let permalink_path = std::path::Path::new(&permalink);
121    let pagination_root = permalink_path
122        .extension()
123        .map(|os_str| {
124            permalink
125                .trim_end_matches(&format!(".{}", os_str.to_string_lossy()))
126                .to_string()
127        })
128        .unwrap_or_else(|| permalink.clone());
129    let interpreted_permalink = if page_num == 1 {
130        index
131            .map(|index| {
132                if pagination_root.is_empty() {
133                    index_to_string(index)
134                } else {
135                    format!("{}/{}", pagination_root, index_to_string(index))
136                }
137            })
138            .unwrap_or_else(|| doc.url_path.clone())
139    } else {
140        let pagination_attr = pagination_attributes(page_num as i32);
141        attributes.extend(pagination_attr);
142        let index = index.map(index_to_string).unwrap_or_else(|| {
143            if config.include != Include::All {
144                unreachable!("Include is not All and no index");
145            }
146            "all".to_string()
147        });
148        if pagination_root.is_empty() {
149            format!(
150                "{}/{}",
151                index,
152                permalink::explode_permalink(&config.permalink_suffix, &attributes)?
153            )
154        } else {
155            format!(
156                "{}/{}/{}",
157                pagination_root,
158                index,
159                permalink::explode_permalink(&config.permalink_suffix, &attributes)?
160            )
161        }
162    };
163    Ok(interpreted_permalink)
164}