use std::cmp::Ordering;
use liquid;
use cobalt_model::pagination_config::Include;
use cobalt_model::pagination_config::PaginationConfig;
use cobalt_model::permalink;
use cobalt_model::SortOrder;
use document;
use document::Document;
use error::*;
pub struct Paginator {
pub pages: Option<Vec<liquid::value::Value>>,
pub indexes: Option<Vec<String>>,
pub index: usize,
pub index_permalink: String,
pub previous_index: usize,
pub previous_index_permalink: Option<String>,
pub next_index: usize,
pub next_index_permalink: Option<String>,
pub first_index_permalink: String,
pub last_index_permalink: String,
pub total_indexes: usize,
pub total_pages: usize,
}
impl Paginator {
pub fn new(total_indexes: usize, total_pages: usize) -> Paginator {
Paginator {
pages: None, indexes: None, index: 0,
index_permalink: String::new(),
previous_index: 0,
previous_index_permalink: None,
next_index: 0,
next_index_permalink: None,
first_index_permalink: String::new(),
last_index_permalink: String::new(),
total_indexes,
total_pages,
}
}
pub fn set_first_last(
&mut self,
doc: &Document,
config: &PaginationConfig,
total_pages: usize,
) -> Result<()> {
self.first_index_permalink = doc.url_path.to_string();
self.last_index_permalink = interpret_permalink(&config, &doc, total_pages)?;
Ok(())
}
fn set_current_index_info(
&mut self,
index: usize,
all_pages: &[&liquid::value::Value],
config: &PaginationConfig,
doc: &Document,
) -> Result<()> {
self.index = index;
self.pages = Some(all_pages.into_iter().map(|p| (*p).clone()).collect());
self.index_permalink = interpret_permalink(&config, &doc, index)?;
Ok(())
}
fn set_previous_next_info(
&mut self,
index: usize,
total_indexes: usize,
doc: &Document,
config: &PaginationConfig,
) -> Result<()> {
if index > 1 {
self.previous_index_permalink = Some(interpret_permalink(&config, &doc, index - 1)?);
self.previous_index = index - 1;
}
if index < total_indexes {
self.next_index = index + 1;
self.next_index_permalink = Some(interpret_permalink(&config, &doc, index + 1)?);
}
Ok(())
}
}
impl Into<liquid::value::Object> for Paginator {
fn into(self) -> liquid::value::Object {
let mut object = liquid::value::Object::new();
if let Some(pages) = self.pages {
object.insert("pages".into(), liquid::value::Value::Array(pages));
}
if let Some(indexes) = self.indexes {
object.insert(
"indexes".into(),
liquid::value::Value::Array(
indexes
.iter()
.cloned()
.map(liquid::value::Value::scalar)
.collect(),
),
);
}
object.insert(
"index".into(),
liquid::value::Value::scalar(self.index as i32),
);
object.insert(
"index_permalink".into(),
liquid::value::Value::scalar(self.index_permalink),
);
if let Some(previous_index_permalink) = self.previous_index_permalink {
object.insert(
"previous_index".into(),
liquid::value::Value::scalar(self.previous_index as i32),
);
object.insert(
"previous_index_permalink".into(),
liquid::value::Value::scalar(previous_index_permalink),
);
}
if let Some(next_index_permalink) = self.next_index_permalink {
object.insert(
"next_index".into(),
liquid::value::Value::scalar(self.next_index as i32),
);
object.insert(
"next_index_permalink".into(),
liquid::value::Value::scalar(next_index_permalink),
);
}
object.insert(
"first_index_permalink".into(),
liquid::value::Value::scalar(self.first_index_permalink),
);
object.insert(
"last_index_permalink".into(),
liquid::value::Value::scalar(self.last_index_permalink),
);
object.insert(
"total_indexes".into(),
liquid::value::Value::scalar(self.total_indexes as i32),
);
object.insert(
"total_pages".into(),
liquid::value::Value::scalar(self.total_pages as i32),
);
object
}
}
pub fn generate_paginators(
doc: &mut Document,
posts_data: &[liquid::value::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);
create_all_paginators(&all_posts, &doc, &config)
}
Include::None => {
unreachable!("PaginationConfigBuilder should have lead to a None for pagination.")
}
}
}
fn extract_value<'a>(a: &'a liquid::value::Value, key: &str) -> Option<&'a liquid::value::Scalar> {
let key = liquid::value::Scalar::new(key.to_owned());
a.get(&key).and_then(|sort_key| sort_key.as_scalar())
}
fn sort_posts(posts: &mut Vec<&liquid::value::Value>, config: &PaginationConfig) {
let order: fn(&liquid::value::Scalar, &liquid::value::Scalar) -> Ordering = match config.order {
SortOrder::Desc => {
|a, b: &liquid::value::Scalar| b.partial_cmp(a).unwrap_or(Ordering::Equal)
}
SortOrder::Asc => {
|a: &liquid::value::Scalar, 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 (extract_value(a, &k), extract_value(b, &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 create_all_paginators(
all_posts: &[&liquid::value::Value],
doc: &Document,
pagination_cfg: &PaginationConfig,
) -> Result<Vec<Paginator>> {
let total_pages = all_posts.len();
let total_indexes = (total_pages as f32 / pagination_cfg.per_page as f32).ceil() as usize;
let paginators: Result<Vec<_>> = all_posts
.chunks(pagination_cfg.per_page as usize)
.enumerate()
.map(|(i, chunk)| {
create_paginator(i, total_indexes, total_pages, &pagination_cfg, &doc, &chunk)
})
.collect();
paginators
}
fn create_paginator(
i: usize,
total_indexes: usize,
total_pages: usize,
config: &PaginationConfig,
doc: &Document,
all_posts: &[&liquid::value::Value],
) -> Result<Paginator> {
let index = i + 1;
let mut paginator = Paginator::new(total_indexes, total_pages);
paginator.set_first_last(&doc, &config, total_indexes)?;
paginator.set_current_index_info(index, &all_posts, &config, &doc)?;
paginator.set_previous_next_info(index, total_indexes, &doc, &config)?;
Ok(paginator)
}
fn pagination_attributes(index_num: i32, include: Include) -> liquid::value::Object {
let i: &str = include.into();
let attributes: liquid::value::Object = vec![
("num".into(), liquid::value::Value::scalar(index_num)),
("include".into(), liquid::value::Value::scalar(i)),
]
.into_iter()
.collect();
attributes
}
fn interpret_permalink(
config: &PaginationConfig,
doc: &Document,
index_num: usize,
) -> Result<String> {
Ok(if index_num == 1 {
doc.url_path.clone()
} else {
let mut attributes = document::permalink_attributes(&doc.front, &doc.file_path);
let pagination_attr = pagination_attributes(index_num as i32, config.include);
attributes.extend(pagination_attr.into_iter());
permalink::explode_permalink(&config.permalink, &attributes)?
})
}