use std::cmp::Ordering;
use liquid::ValueView;
use crate::cobalt_model::SortOrder;
use crate::cobalt_model::pagination::Include;
use crate::cobalt_model::pagination::PaginationConfig;
use crate::cobalt_model::permalink;
use crate::cobalt_model::slug;
use crate::document;
use crate::document::Document;
use crate::error::Result;
mod all;
mod categories;
mod dates;
mod helpers;
mod paginator;
mod tags;
use paginator::Paginator;
pub(crate) fn generate_paginators(
doc: &mut Document,
posts_data: &[liquid::model::Value],
) -> Result<Vec<Paginator>> {
let config = doc
.front
.pagination
.as_ref()
.expect("Front should have pagination here.");
let mut all_posts: Vec<_> = posts_data.iter().collect();
match config.include {
Include::All => {
sort_posts(&mut all_posts, config);
all::create_all_paginators(&all_posts, doc, config, None)
}
Include::Tags => tags::create_tags_paginators(&all_posts, doc, config),
Include::Categories => categories::create_categories_paginators(&all_posts, doc, config),
Include::Dates => dates::create_dates_paginators(&all_posts, doc, config),
Include::None => {
unreachable!("PaginationConfigBuilder should have lead to a None for pagination.")
}
}
}
fn sort_posts(posts: &mut [&liquid::model::Value], config: &PaginationConfig) {
let order: fn(liquid::model::ScalarCow<'_>, liquid::model::ScalarCow<'_>) -> Ordering =
match config.order {
SortOrder::Desc => {
|a, b: liquid::model::ScalarCow<'_>| b.partial_cmp(&a).unwrap_or(Ordering::Equal)
}
SortOrder::Asc => {
|a: liquid::model::ScalarCow<'_>, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)
}
SortOrder::None => {
unreachable!(
"Sort order should have default value when constructing PaginationConfig"
)
}
};
posts.sort_by(|a, b| {
let keys = &config.sort_by;
let mut cmp = Ordering::Less;
for k in keys {
cmp = match (
helpers::extract_scalar(a.as_view(), k),
helpers::extract_scalar(b.as_view(), k),
) {
(Some(a), Some(b)) => order(a, b),
(None, None) => Ordering::Equal,
(_, None) => Ordering::Greater,
(None, _) => Ordering::Less,
};
if cmp != Ordering::Equal {
return cmp;
}
}
cmp
});
}
fn pagination_attributes(page_num: i32) -> liquid::Object {
let attributes: liquid::Object = vec![("num".into(), liquid::model::Value::scalar(page_num))]
.into_iter()
.collect();
attributes
}
fn index_to_string(index: &liquid::model::Value) -> String {
if let Some(index) = index.as_array() {
let mut s: String = index
.values()
.map(|i| {
let mut s = slug::slugify(i.to_kstr().into_string());
s.push('/');
s
})
.collect();
s.pop(); s
} else {
slug::slugify(index.to_kstr().into_string())
}
}
fn interpret_permalink(
config: &PaginationConfig,
doc: &Document,
page_num: usize,
index: Option<&liquid::model::Value>,
) -> Result<String> {
let mut attributes = document::permalink_attributes(&doc.front, &doc.file_path);
let permalink = permalink::explode_permalink(&config.front_permalink, &attributes)?;
let permalink_path = std::path::Path::new(&permalink);
let pagination_root = permalink_path
.extension()
.map(|os_str| {
permalink
.trim_end_matches(&format!(".{}", os_str.to_string_lossy()))
.to_string()
})
.unwrap_or_else(|| permalink.clone());
let interpreted_permalink = if page_num == 1 {
index
.map(|index| {
if pagination_root.is_empty() {
index_to_string(index)
} else {
format!("{}/{}", pagination_root, index_to_string(index))
}
})
.unwrap_or_else(|| doc.url_path.clone())
} else {
let pagination_attr = pagination_attributes(page_num as i32);
attributes.extend(pagination_attr);
let index = index.map(index_to_string).unwrap_or_else(|| {
if config.include != Include::All {
unreachable!("Include is not All and no index");
}
"all".to_string()
});
if pagination_root.is_empty() {
format!(
"{}/{}",
index,
permalink::explode_permalink(&config.permalink_suffix, &attributes)?
)
} else {
format!(
"{}/{}/{}",
pagination_root,
index,
permalink::explode_permalink(&config.permalink_suffix, &attributes)?
)
}
};
Ok(interpreted_permalink)
}