zola/components/taxonomies/src/lib.rs

160 lines
4.8 KiB
Rust
Raw Normal View History

2017-07-01 07:47:41 +00:00
#[macro_use]
extern crate serde_derive;
extern crate tera;
extern crate slug;
extern crate errors;
extern crate config;
extern crate content;
extern crate front_matter;
2017-08-23 10:17:24 +00:00
extern crate utils;
2017-07-01 07:47:41 +00:00
2017-05-16 04:37:00 +00:00
use std::collections::HashMap;
use slug::slugify;
use tera::{Context, Tera};
use config::Config;
use errors::{Result, ResultExt};
2017-07-01 07:47:41 +00:00
use content::{Page, sort_pages};
use front_matter::SortBy;
2017-08-23 10:17:24 +00:00
use utils::templates::render_template;
2017-05-16 04:37:00 +00:00
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TaxonomyKind {
Tags,
Categories,
}
/// A tag or category
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct TaxonomyItem {
pub name: String,
pub slug: String,
pub pages: Vec<Page>,
}
impl TaxonomyItem {
pub fn new(name: &str, pages: Vec<Page>) -> TaxonomyItem {
2017-05-30 10:23:07 +00:00
// We shouldn't have any pages without dates there
let (sorted_pages, _) = sort_pages(pages, SortBy::Date);
2017-07-01 10:13:21 +00:00
2017-05-16 04:37:00 +00:00
TaxonomyItem {
name: name.to_string(),
slug: slugify(name),
2017-05-30 10:23:07 +00:00
pages: sorted_pages,
2017-05-16 04:37:00 +00:00
}
}
}
/// All the tags or categories
#[derive(Debug, Clone, PartialEq)]
pub struct Taxonomy {
pub kind: TaxonomyKind,
// this vec is sorted by the count of item
pub items: Vec<TaxonomyItem>,
}
impl Taxonomy {
// TODO: take a Vec<&'a Page> if it makes a difference in terms of perf for actual sites
2017-07-01 10:13:21 +00:00
pub fn find_tags_and_categories(all_pages: &[Page]) -> (Taxonomy, Taxonomy) {
2017-05-16 04:37:00 +00:00
let mut tags = HashMap::new();
let mut categories = HashMap::new();
// Find all the tags/categories first
for page in all_pages {
2017-05-30 10:23:07 +00:00
// Don't consider pages without pages for tags/categories as that's the only thing
// we can sort pages with across sections
// If anyone sees that comment and wonder wtf, please open an issue as I can't think of
// usecases other than blog posts for built-in taxonomies
if page.meta.date.is_none() {
continue;
}
2017-05-16 04:37:00 +00:00
if let Some(ref category) = page.meta.category {
categories
.entry(category.to_string())
.or_insert_with(|| vec![])
.push(page.clone());
}
if let Some(ref t) = page.meta.tags {
for tag in t {
tags
.entry(tag.to_string())
.or_insert_with(|| vec![])
.push(page.clone());
}
}
}
// Then make TaxonomyItem out of them, after sorting it
let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, tags);
let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, categories);
(tags_taxonomy, categories_taxonomy)
}
fn new(kind: TaxonomyKind, items: HashMap<String, Vec<Page>>) -> Taxonomy {
let mut sorted_items = vec![];
for (name, pages) in &items {
sorted_items.push(
TaxonomyItem::new(name, pages.clone())
);
}
sorted_items.sort_by(|a, b| b.pages.len().cmp(&a.pages.len()));
Taxonomy {
kind,
items: sorted_items,
}
}
pub fn len(&self) -> usize {
self.items.len()
}
2017-07-11 13:51:02 +00:00
pub fn is_empty(&self) -> bool {
self.len() == 0
}
2017-05-16 04:37:00 +00:00
pub fn get_single_item_name(&self) -> String {
match self.kind {
TaxonomyKind::Tags => "tag".to_string(),
TaxonomyKind::Categories => "category".to_string(),
}
}
pub fn get_list_name(&self) -> String {
match self.kind {
TaxonomyKind::Tags => "tags".to_string(),
TaxonomyKind::Categories => "categories".to_string(),
}
}
pub fn render_single_item(&self, item: &TaxonomyItem, tera: &Tera, config: &Config) -> Result<String> {
let name = self.get_single_item_name();
let mut context = Context::new();
context.add("config", config);
context.add(&name, item);
context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug)));
context.add("current_path", &format!("/{}/{}", name, item.slug));
2017-08-23 10:17:24 +00:00
render_template(&format!("{}.html", name), tera, &context, config.theme.clone())
2017-05-16 04:37:00 +00:00
.chain_err(|| format!("Failed to render {} page.", name))
}
pub fn render_list(&self, tera: &Tera, config: &Config) -> Result<String> {
let name = self.get_list_name();
let mut context = Context::new();
context.add("config", config);
context.add(&name, &self.items);
context.add("current_url", &config.make_permalink(&name));
context.add("current_path", &name);
2017-08-23 10:17:24 +00:00
render_template(&format!("{}.html", name), tera, &context, config.theme.clone())
2017-05-16 04:37:00 +00:00
.chain_err(|| format!("Failed to render {} page.", name))
}
}