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.

143 lines
4.4KB

  1. use std::collections::HashMap;
  2. use slug::slugify;
  3. use tera::{Context, Tera};
  4. use config::Config;
  5. use errors::{Result, ResultExt};
  6. use content::Page;
  7. use content::sorting::{SortBy, sort_pages};
  8. #[derive(Debug, Copy, Clone, PartialEq)]
  9. pub enum TaxonomyKind {
  10. Tags,
  11. Categories,
  12. }
  13. /// A tag or category
  14. #[derive(Debug, Clone, Serialize, PartialEq)]
  15. pub struct TaxonomyItem {
  16. pub name: String,
  17. pub slug: String,
  18. pub pages: Vec<Page>,
  19. }
  20. impl TaxonomyItem {
  21. pub fn new(name: &str, pages: Vec<Page>) -> TaxonomyItem {
  22. // We shouldn't have any pages without dates there
  23. let (sorted_pages, _) = sort_pages(pages, SortBy::Date);
  24. TaxonomyItem {
  25. name: name.to_string(),
  26. slug: slugify(name),
  27. pages: sorted_pages,
  28. }
  29. }
  30. }
  31. /// All the tags or categories
  32. #[derive(Debug, Clone, PartialEq)]
  33. pub struct Taxonomy {
  34. pub kind: TaxonomyKind,
  35. // this vec is sorted by the count of item
  36. pub items: Vec<TaxonomyItem>,
  37. }
  38. impl Taxonomy {
  39. // TODO: take a Vec<&'a Page> if it makes a difference in terms of perf for actual sites
  40. pub fn find_tags_and_categories(all_pages: Vec<Page>) -> (Taxonomy, Taxonomy) {
  41. let mut tags = HashMap::new();
  42. let mut categories = HashMap::new();
  43. // Find all the tags/categories first
  44. for page in all_pages {
  45. // Don't consider pages without pages for tags/categories as that's the only thing
  46. // we can sort pages with across sections
  47. // If anyone sees that comment and wonder wtf, please open an issue as I can't think of
  48. // usecases other than blog posts for built-in taxonomies
  49. if page.meta.date.is_none() {
  50. continue;
  51. }
  52. if let Some(ref category) = page.meta.category {
  53. categories
  54. .entry(category.to_string())
  55. .or_insert_with(|| vec![])
  56. .push(page.clone());
  57. }
  58. if let Some(ref t) = page.meta.tags {
  59. for tag in t {
  60. tags
  61. .entry(tag.to_string())
  62. .or_insert_with(|| vec![])
  63. .push(page.clone());
  64. }
  65. }
  66. }
  67. // Then make TaxonomyItem out of them, after sorting it
  68. let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, tags);
  69. let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, categories);
  70. (tags_taxonomy, categories_taxonomy)
  71. }
  72. fn new(kind: TaxonomyKind, items: HashMap<String, Vec<Page>>) -> Taxonomy {
  73. let mut sorted_items = vec![];
  74. for (name, pages) in &items {
  75. sorted_items.push(
  76. TaxonomyItem::new(name, pages.clone())
  77. );
  78. }
  79. sorted_items.sort_by(|a, b| b.pages.len().cmp(&a.pages.len()));
  80. Taxonomy {
  81. kind,
  82. items: sorted_items,
  83. }
  84. }
  85. pub fn len(&self) -> usize {
  86. self.items.len()
  87. }
  88. pub fn get_single_item_name(&self) -> String {
  89. match self.kind {
  90. TaxonomyKind::Tags => "tag".to_string(),
  91. TaxonomyKind::Categories => "category".to_string(),
  92. }
  93. }
  94. pub fn get_list_name(&self) -> String {
  95. match self.kind {
  96. TaxonomyKind::Tags => "tags".to_string(),
  97. TaxonomyKind::Categories => "categories".to_string(),
  98. }
  99. }
  100. pub fn render_single_item(&self, item: &TaxonomyItem, tera: &Tera, config: &Config) -> Result<String> {
  101. let name = self.get_single_item_name();
  102. let mut context = Context::new();
  103. context.add("config", config);
  104. context.add(&name, item);
  105. context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug)));
  106. context.add("current_path", &format!("/{}/{}", name, item.slug));
  107. tera.render(&format!("{}.html", name), &context)
  108. .chain_err(|| format!("Failed to render {} page.", name))
  109. }
  110. pub fn render_list(&self, tera: &Tera, config: &Config) -> Result<String> {
  111. let name = self.get_list_name();
  112. let mut context = Context::new();
  113. context.add("config", config);
  114. context.add(&name, &self.items);
  115. context.add("current_url", &config.make_permalink(&name));
  116. context.add("current_path", &name);
  117. tera.render(&format!("{}.html", name), &context)
  118. .chain_err(|| format!("Failed to render {} page.", name))
  119. }
  120. }