You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.9KB

  1. #[macro_use]
  2. extern crate serde_derive;
  3. extern crate tera;
  4. extern crate slug;
  5. extern crate errors;
  6. extern crate config;
  7. extern crate content;
  8. extern crate front_matter;
  9. extern crate utils;
  10. use std::collections::HashMap;
  11. use slug::slugify;
  12. use tera::{Context, Tera};
  13. use config::Config;
  14. use errors::{Result, ResultExt};
  15. use content::{Page, sort_pages};
  16. use front_matter::SortBy;
  17. use utils::templates::render_template;
  18. #[derive(Debug, Copy, Clone, PartialEq)]
  19. pub enum TaxonomyKind {
  20. Tags,
  21. Categories,
  22. }
  23. /// A tag or category
  24. #[derive(Debug, Clone, Serialize, PartialEq)]
  25. pub struct TaxonomyItem {
  26. pub name: String,
  27. pub slug: String,
  28. pub permalink: String,
  29. pub pages: Vec<Page>,
  30. }
  31. impl TaxonomyItem {
  32. pub fn new(name: &str, kind: TaxonomyKind, config: &Config, pages: Vec<Page>) -> TaxonomyItem {
  33. // Taxonomy are almost always used for blogs so we filter by dates
  34. // and it's not like we can sort things across sections by anything other
  35. // than dates
  36. let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date);
  37. let slug = slugify(name);
  38. let permalink = {
  39. let kind_path = if kind == TaxonomyKind::Tags { "tag" } else { "category" };
  40. config.make_permalink(&format!("/{}/{}", kind_path, slug))
  41. };
  42. // We still append pages without dates at the end
  43. pages.extend(ignored_pages);
  44. TaxonomyItem {
  45. name: name.to_string(),
  46. permalink,
  47. slug,
  48. pages,
  49. }
  50. }
  51. }
  52. /// All the tags or categories
  53. #[derive(Debug, Clone, PartialEq)]
  54. pub struct Taxonomy {
  55. pub kind: TaxonomyKind,
  56. // this vec is sorted by the count of item
  57. pub items: Vec<TaxonomyItem>,
  58. }
  59. impl Taxonomy {
  60. pub fn find_tags_and_categories(config: &Config, all_pages: &[Page]) -> (Taxonomy, Taxonomy) {
  61. let mut tags = HashMap::new();
  62. let mut categories = HashMap::new();
  63. // Find all the tags/categories first
  64. for page in all_pages {
  65. if let Some(ref category) = page.meta.category {
  66. categories
  67. .entry(category.to_string())
  68. .or_insert_with(|| vec![])
  69. .push(page.clone());
  70. }
  71. if let Some(ref t) = page.meta.tags {
  72. for tag in t {
  73. tags
  74. .entry(tag.to_string())
  75. .or_insert_with(|| vec![])
  76. .push(page.clone());
  77. }
  78. }
  79. }
  80. // Then make TaxonomyItem out of them, after sorting it
  81. let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, config, tags);
  82. let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, config, categories);
  83. (tags_taxonomy, categories_taxonomy)
  84. }
  85. fn new(kind: TaxonomyKind, config: &Config, items: HashMap<String, Vec<Page>>) -> Taxonomy {
  86. let mut sorted_items = vec![];
  87. for (name, pages) in &items {
  88. sorted_items.push(
  89. TaxonomyItem::new(name, kind, config, pages.clone())
  90. );
  91. }
  92. sorted_items.sort_by(|a, b| a.name.cmp(&b.name));
  93. Taxonomy {
  94. kind,
  95. items: sorted_items,
  96. }
  97. }
  98. pub fn len(&self) -> usize {
  99. self.items.len()
  100. }
  101. pub fn is_empty(&self) -> bool {
  102. self.len() == 0
  103. }
  104. pub fn get_single_item_name(&self) -> String {
  105. match self.kind {
  106. TaxonomyKind::Tags => "tag".to_string(),
  107. TaxonomyKind::Categories => "category".to_string(),
  108. }
  109. }
  110. pub fn get_list_name(&self) -> String {
  111. match self.kind {
  112. TaxonomyKind::Tags => "tags".to_string(),
  113. TaxonomyKind::Categories => "categories".to_string(),
  114. }
  115. }
  116. pub fn render_single_item(&self, item: &TaxonomyItem, tera: &Tera, config: &Config) -> Result<String> {
  117. let name = self.get_single_item_name();
  118. let mut context = Context::new();
  119. context.add("config", config);
  120. context.add(&name, item);
  121. context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug)));
  122. context.add("current_path", &format!("/{}/{}", name, item.slug));
  123. render_template(&format!("{}.html", name), tera, &context, config.theme.clone())
  124. .chain_err(|| format!("Failed to render {} page.", name))
  125. }
  126. pub fn render_list(&self, tera: &Tera, config: &Config) -> Result<String> {
  127. let name = self.get_list_name();
  128. let mut context = Context::new();
  129. context.add("config", config);
  130. context.add(&name, &self.items);
  131. context.add("current_url", &config.make_permalink(&name));
  132. context.add("current_path", &name);
  133. render_template(&format!("{}.html", name), tera, &context, config.theme.clone())
  134. .chain_err(|| format!("Failed to render {} page.", name))
  135. }
  136. }
  137. #[cfg(test)]
  138. mod tests {
  139. use super::*;
  140. use config::Config;
  141. use content::Page;
  142. #[test]
  143. fn can_make_taxonomies() {
  144. let config = Config::default();
  145. let mut page1 = Page::default();
  146. page1.meta.tags = Some(vec!["rust".to_string(), "db".to_string()]);
  147. page1.meta.category = Some("Programming tutorials".to_string());
  148. let mut page2 = Page::default();
  149. page2.meta.tags = Some(vec!["rust".to_string(), "js".to_string()]);
  150. page2.meta.category = Some("Other".to_string());
  151. let mut page3 = Page::default();
  152. page3.meta.tags = Some(vec!["js".to_string()]);
  153. let pages = vec![page1, page2, page3];
  154. let (tags, categories) = Taxonomy::find_tags_and_categories(&config, &pages);
  155. assert_eq!(tags.items.len(), 3);
  156. assert_eq!(categories.items.len(), 2);
  157. assert_eq!(tags.items[0].name, "db");
  158. assert_eq!(tags.items[0].slug, "db");
  159. assert_eq!(tags.items[0].permalink, "http://a-website.com/tag/db/");
  160. assert_eq!(tags.items[0].pages.len(), 1);
  161. assert_eq!(tags.items[1].name, "js");
  162. assert_eq!(tags.items[1].slug, "js");
  163. assert_eq!(tags.items[1].permalink, "http://a-website.com/tag/js/");
  164. assert_eq!(tags.items[1].pages.len(), 2);
  165. assert_eq!(tags.items[2].name, "rust");
  166. assert_eq!(tags.items[2].slug, "rust");
  167. assert_eq!(tags.items[2].permalink, "http://a-website.com/tag/rust/");
  168. assert_eq!(tags.items[2].pages.len(), 2);
  169. assert_eq!(categories.items[0].name, "Other");
  170. assert_eq!(categories.items[0].slug, "other");
  171. assert_eq!(categories.items[0].permalink, "http://a-website.com/category/other/");
  172. assert_eq!(categories.items[0].pages.len(), 1);
  173. assert_eq!(categories.items[1].name, "Programming tutorials");
  174. assert_eq!(categories.items[1].slug, "programming-tutorials");
  175. assert_eq!(categories.items[1].permalink, "http://a-website.com/category/programming-tutorials/");
  176. assert_eq!(categories.items[1].pages.len(), 1);
  177. }
  178. }