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 categories;
16mod dates;
17mod helpers;
18mod paginator;
19mod tags;
20
21use paginator::Paginator;
22
23pub(crate) fn generate_paginators(
24 doc: &mut Document,
25 posts_data: &[liquid::model::Value],
26) -> Result<Vec<Paginator>> {
27 let config = doc
28 .front
29 .pagination
30 .as_ref()
31 .expect("Front should have pagination here.");
32 let mut all_posts: Vec<_> = posts_data.iter().collect();
33 match config.include {
34 Include::All => {
35 sort_posts(&mut all_posts, config);
36 create_all_paginators(&all_posts, doc, config, None)
37 }
38 Include::Tags => tags::create_tags_paginators(&all_posts, doc, config),
39 Include::Categories => categories::create_categories_paginators(&all_posts, doc, config),
40 Include::Dates => dates::create_dates_paginators(&all_posts, doc, config),
41 Include::None => {
42 unreachable!("PaginationConfigBuilder should have lead to a None for pagination.")
43 }
44 }
45}
46
47fn create_all_paginators(
48 all_posts: &[&liquid::model::Value],
49 doc: &Document,
50 pagination_cfg: &PaginationConfig,
51 index_title: Option<&liquid::model::Value>,
52) -> Result<Vec<Paginator>> {
53 let total_pages = all_posts.len();
54 let total_indexes = (total_pages as f32 / pagination_cfg.per_page as f32).ceil() as usize;
57 let paginators: Result<Vec<_>> = all_posts
58 .chunks(pagination_cfg.per_page as usize)
59 .enumerate()
60 .map(|(i, chunk)| {
61 paginator::create_paginator(
62 i,
63 total_indexes,
64 total_pages,
65 pagination_cfg,
66 doc,
67 chunk,
68 index_title,
69 )
70 })
71 .collect();
72 paginators
73}
74
75fn sort_posts(posts: &mut [&liquid::model::Value], config: &PaginationConfig) {
77 let order: fn(liquid::model::ScalarCow<'_>, liquid::model::ScalarCow<'_>) -> Ordering =
78 match config.order {
79 SortOrder::Desc => {
80 |a, b: liquid::model::ScalarCow<'_>| b.partial_cmp(&a).unwrap_or(Ordering::Equal)
81 }
82 SortOrder::Asc => {
83 |a: liquid::model::ScalarCow<'_>, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)
84 }
85 SortOrder::None => {
86 unreachable!(
89 "Sort order should have default value when constructing PaginationConfig"
90 )
91 }
92 };
93 posts.sort_by(|a, b| {
94 let keys = &config.sort_by;
95 let mut cmp = Ordering::Less;
96 for k in keys {
97 cmp = match (
98 helpers::extract_scalar(a.as_view(), k),
99 helpers::extract_scalar(b.as_view(), k),
100 ) {
101 (Some(a), Some(b)) => order(a, b),
102 (None, None) => Ordering::Equal,
103 (_, None) => Ordering::Greater,
104 (None, _) => Ordering::Less,
105 };
106 if cmp != Ordering::Equal {
107 return cmp;
108 }
109 }
110 cmp
111 });
112}
113
114fn pagination_attributes(page_num: i32) -> liquid::Object {
115 let attributes: liquid::Object = vec![("num".into(), liquid::model::Value::scalar(page_num))]
116 .into_iter()
117 .collect();
118 attributes
119}
120
121fn index_to_string(index: &liquid::model::Value) -> String {
122 if let Some(index) = index.as_array() {
123 let mut s: String = index
125 .values()
126 .map(|i| {
127 let mut s = slug::slugify(i.to_kstr().into_string());
128 s.push('/');
129 s
130 })
131 .collect();
132 s.pop(); s
134 } else {
135 slug::slugify(index.to_kstr().into_string())
136 }
137}
138
139fn interpret_permalink(
140 config: &PaginationConfig,
141 doc: &Document,
142 page_num: usize,
143 index: Option<&liquid::model::Value>,
144) -> Result<String> {
145 let mut attributes = document::permalink_attributes(&doc.front, &doc.file_path);
146 let permalink = permalink::explode_permalink(&config.front_permalink, &attributes)?;
147 let permalink_path = std::path::Path::new(&permalink);
148 let pagination_root = permalink_path
149 .extension()
150 .map(|os_str| {
151 permalink
152 .trim_end_matches(&format!(".{}", os_str.to_string_lossy()))
153 .to_string()
154 })
155 .unwrap_or_else(|| permalink.clone());
156 let interpreted_permalink = if page_num == 1 {
157 index
158 .map(|index| {
159 if pagination_root.is_empty() {
160 index_to_string(index)
161 } else {
162 format!("{}/{}", pagination_root, index_to_string(index))
163 }
164 })
165 .unwrap_or_else(|| doc.url_path.clone())
166 } else {
167 let pagination_attr = pagination_attributes(page_num as i32);
168 attributes.extend(pagination_attr);
169 let index = index.map(index_to_string).unwrap_or_else(|| {
170 if config.include != Include::All {
171 unreachable!("Include is not All and no index");
172 }
173 "all".to_string()
174 });
175 if pagination_root.is_empty() {
176 format!(
177 "{}/{}",
178 index,
179 permalink::explode_permalink(&config.permalink_suffix, &attributes)?
180 )
181 } else {
182 format!(
183 "{}/{}/{}",
184 pagination_root,
185 index,
186 permalink::explode_permalink(&config.permalink_suffix, &attributes)?
187 )
188 }
189 };
190 Ok(interpreted_permalink)
191}