cobalt-bin 0.20.4

Static site generator written in Rust
Documentation
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.")
        }
    }
}

// sort posts by multiple criteria
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 => {
                // when built, order is set like this:
                // `order.unwrap_or(SortOrder::Desc);` so it's unreachable
                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() {
        // categories
        let mut s: String = index
            .values()
            .map(|i| {
                let mut s = slug::slugify(i.to_kstr().into_string());
                s.push('/');
                s
            })
            .collect();
        s.pop(); // remove last '/'
        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)
}