#[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; extern crate utils; use std::collections::HashMap; use slug::slugify; use tera::{Context, Tera}; use config::Config; use errors::{Result, ResultExt}; use content::{Page, sort_pages}; use front_matter::SortBy; use utils::templates::render_template; #[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 permalink: String, pub pages: Vec, } impl TaxonomyItem { pub fn new(name: &str, kind: TaxonomyKind, config: &Config, pages: Vec) -> TaxonomyItem { // Taxonomy are almost always used for blogs so we filter by dates // and it's not like we can sort things across sections by anything other // than dates let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date); let slug = slugify(name); let permalink = { let kind_path = if kind == TaxonomyKind::Tags { "tag" } else { "category" }; config.make_permalink(&format!("/{}/{}", kind_path, slug)) }; // We still append pages without dates at the end pages.extend(ignored_pages); TaxonomyItem { name: name.to_string(), permalink, slug, pages, } } } /// 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, } impl Taxonomy { pub fn find_tags_and_categories(config: &Config, all_pages: &[Page]) -> (Taxonomy, Taxonomy) { let mut tags = HashMap::new(); let mut categories = HashMap::new(); // Find all the tags/categories first for page in all_pages { 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, config, tags); let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, config, categories); (tags_taxonomy, categories_taxonomy) } fn new(kind: TaxonomyKind, config: &Config, items: HashMap>) -> Taxonomy { let mut sorted_items = vec![]; for (name, pages) in &items { sorted_items.push( TaxonomyItem::new(name, kind, config, pages.clone()) ); } sorted_items.sort_by(|a, b| a.name.cmp(&b.name)); Taxonomy { kind, items: sorted_items, } } pub fn len(&self) -> usize { self.items.len() } pub fn is_empty(&self) -> bool { self.len() == 0 } 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 { 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)); render_template(&format!("{}.html", name), tera, &context, config.theme.clone()) .chain_err(|| format!("Failed to render {} page.", name)) } pub fn render_list(&self, tera: &Tera, config: &Config) -> Result { 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); render_template(&format!("{}.html", name), tera, &context, config.theme.clone()) .chain_err(|| format!("Failed to render {} page.", name)) } } #[cfg(test)] mod tests { use super::*; use config::Config; use content::Page; #[test] fn can_make_taxonomies() { let config = Config::default(); let mut page1 = Page::default(); page1.meta.tags = Some(vec!["rust".to_string(), "db".to_string()]); page1.meta.category = Some("Programming tutorials".to_string()); let mut page2 = Page::default(); page2.meta.tags = Some(vec!["rust".to_string(), "js".to_string()]); page2.meta.category = Some("Other".to_string()); let mut page3 = Page::default(); page3.meta.tags = Some(vec!["js".to_string()]); let pages = vec![page1, page2, page3]; let (tags, categories) = Taxonomy::find_tags_and_categories(&config, &pages); assert_eq!(tags.items.len(), 3); assert_eq!(categories.items.len(), 2); assert_eq!(tags.items[0].name, "db"); assert_eq!(tags.items[0].slug, "db"); assert_eq!(tags.items[0].permalink, "http://a-website.com/tag/db/"); assert_eq!(tags.items[0].pages.len(), 1); assert_eq!(tags.items[1].name, "js"); assert_eq!(tags.items[1].slug, "js"); assert_eq!(tags.items[1].permalink, "http://a-website.com/tag/js/"); assert_eq!(tags.items[1].pages.len(), 2); assert_eq!(tags.items[2].name, "rust"); assert_eq!(tags.items[2].slug, "rust"); assert_eq!(tags.items[2].permalink, "http://a-website.com/tag/rust/"); assert_eq!(tags.items[2].pages.len(), 2); assert_eq!(categories.items[0].name, "Other"); assert_eq!(categories.items[0].slug, "other"); assert_eq!(categories.items[0].permalink, "http://a-website.com/category/other/"); assert_eq!(categories.items[0].pages.len(), 1); assert_eq!(categories.items[1].name, "Programming tutorials"); assert_eq!(categories.items[1].slug, "programming-tutorials"); assert_eq!(categories.items[1].permalink, "http://a-website.com/category/programming-tutorials/"); assert_eq!(categories.items[1].pages.len(), 1); } }