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,
|
2017-10-01 05:04:30 +00:00
|
|
|
pub permalink: String,
|
2017-05-16 04:37:00 +00:00
|
|
|
pub pages: Vec<Page>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TaxonomyItem {
|
2017-10-01 05:04:30 +00:00
|
|
|
pub fn new(name: &str, kind: TaxonomyKind, config: &Config, pages: Vec<Page>) -> 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 = {
|
2017-11-16 17:08:06 +00:00
|
|
|
let kind_path = if kind == TaxonomyKind::Tags { "tags" } else { "categories" };
|
2017-10-01 05:04:30 +00:00
|
|
|
config.make_permalink(&format!("/{}/{}", kind_path, slug))
|
|
|
|
};
|
|
|
|
|
|
|
|
// We still append pages without dates at the end
|
|
|
|
pages.extend(ignored_pages);
|
2017-07-01 10:13:21 +00:00
|
|
|
|
2017-05-16 04:37:00 +00:00
|
|
|
TaxonomyItem {
|
|
|
|
name: name.to_string(),
|
2017-10-01 05:04:30 +00:00
|
|
|
permalink,
|
|
|
|
slug,
|
|
|
|
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 {
|
2017-10-01 05:04:30 +00:00
|
|
|
pub fn find_tags_and_categories(config: &Config, 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 {
|
|
|
|
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
|
2017-10-01 05:04:30 +00:00
|
|
|
let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, config, tags);
|
|
|
|
let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, config, categories);
|
2017-05-16 04:37:00 +00:00
|
|
|
|
|
|
|
(tags_taxonomy, categories_taxonomy)
|
|
|
|
}
|
|
|
|
|
2017-10-01 05:04:30 +00:00
|
|
|
fn new(kind: TaxonomyKind, config: &Config, items: HashMap<String, Vec<Page>>) -> Taxonomy {
|
2017-05-16 04:37:00 +00:00
|
|
|
let mut sorted_items = vec![];
|
|
|
|
for (name, pages) in &items {
|
|
|
|
sorted_items.push(
|
2017-10-01 05:04:30 +00:00
|
|
|
TaxonomyItem::new(name, kind, config, pages.clone())
|
2017-05-16 04:37:00 +00:00
|
|
|
);
|
|
|
|
}
|
2017-10-01 05:04:30 +00:00
|
|
|
sorted_items.sort_by(|a, b| a.name.cmp(&b.name));
|
2017-05-16 04:37:00 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
2017-10-01 05:04:30 +00:00
|
|
|
|
|
|
|
#[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);
|
|
|
|
}
|
|
|
|
}
|