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
48fn 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 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 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(); 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}