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.

136 lines
4.1KB

  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. #[derive(Debug, Copy, Clone, PartialEq)]
  8. pub enum TaxonomyKind {
  9. Tags,
  10. Categories,
  11. }
  12. /// A tag or category
  13. #[derive(Debug, Clone, Serialize, PartialEq)]
  14. pub struct TaxonomyItem {
  15. pub name: String,
  16. pub slug: String,
  17. pub pages: Vec<Page>,
  18. }
  19. impl TaxonomyItem {
  20. pub fn new(name: &str, pages: Vec<Page>) -> TaxonomyItem {
  21. TaxonomyItem {
  22. name: name.to_string(),
  23. slug: slugify(name),
  24. pages,
  25. }
  26. }
  27. }
  28. /// All the tags or categories
  29. #[derive(Debug, Clone, PartialEq)]
  30. pub struct Taxonomy {
  31. pub kind: TaxonomyKind,
  32. // this vec is sorted by the count of item
  33. pub items: Vec<TaxonomyItem>,
  34. }
  35. impl Taxonomy {
  36. // TODO: take a Vec<&'a Page> if it makes a difference in terms of perf for actual sites
  37. pub fn find_tags_and_categories(all_pages: Vec<Page>) -> (Taxonomy, Taxonomy) {
  38. let mut tags = HashMap::new();
  39. let mut categories = HashMap::new();
  40. // Find all the tags/categories first
  41. for page in all_pages {
  42. if let Some(ref category) = page.meta.category {
  43. categories
  44. .entry(category.to_string())
  45. .or_insert_with(|| vec![])
  46. .push(page.clone());
  47. }
  48. if let Some(ref t) = page.meta.tags {
  49. for tag in t {
  50. tags
  51. .entry(tag.to_string())
  52. .or_insert_with(|| vec![])
  53. .push(page.clone());
  54. }
  55. }
  56. }
  57. // Then make TaxonomyItem out of them, after sorting it
  58. let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, tags);
  59. let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, categories);
  60. (tags_taxonomy, categories_taxonomy)
  61. }
  62. fn new(kind: TaxonomyKind, items: HashMap<String, Vec<Page>>) -> Taxonomy {
  63. let mut sorted_items = vec![];
  64. for (name, pages) in &items {
  65. sorted_items.push(
  66. TaxonomyItem::new(name, pages.clone())
  67. );
  68. }
  69. sorted_items.sort_by(|a, b| b.pages.len().cmp(&a.pages.len()));
  70. Taxonomy {
  71. kind,
  72. items: sorted_items,
  73. }
  74. }
  75. pub fn len(&self) -> usize {
  76. self.items.len()
  77. }
  78. pub fn get_single_item_name(&self) -> String {
  79. match self.kind {
  80. TaxonomyKind::Tags => "tag".to_string(),
  81. TaxonomyKind::Categories => "category".to_string(),
  82. }
  83. }
  84. pub fn get_list_name(&self) -> String {
  85. match self.kind {
  86. TaxonomyKind::Tags => "tags".to_string(),
  87. TaxonomyKind::Categories => "categories".to_string(),
  88. }
  89. }
  90. pub fn render_single_item(&self, item: &TaxonomyItem, tera: &Tera, config: &Config) -> Result<String> {
  91. let name = self.get_single_item_name();
  92. let mut context = Context::new();
  93. context.add("config", config);
  94. // TODO: how to sort categories and tag content?
  95. // Have a setting in config.toml or a _category.md and _tag.md
  96. // The latter is more in line with the rest of Gutenberg but order ordering
  97. // doesn't really work across sections.
  98. context.add(&name, item);
  99. context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug)));
  100. context.add("current_path", &format!("/{}/{}", name, item.slug));
  101. tera.render(&format!("{}.html", name), &context)
  102. .chain_err(|| format!("Failed to render {} page.", name))
  103. }
  104. pub fn render_list(&self, tera: &Tera, config: &Config) -> Result<String> {
  105. let name = self.get_list_name();
  106. let mut context = Context::new();
  107. context.add("config", config);
  108. context.add(&name, &self.items);
  109. context.add("current_url", &config.make_permalink(&name));
  110. context.add("current_path", &name);
  111. tera.render(&format!("{}.html", name), &context)
  112. .chain_err(|| format!("Failed to render {} page.", name))
  113. }
  114. }