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.

251 lines
8.6KB

  1. #[macro_use]
  2. extern crate serde_derive;
  3. extern crate tera;
  4. extern crate slug;
  5. #[macro_use]
  6. extern crate errors;
  7. extern crate config;
  8. extern crate content;
  9. extern crate front_matter;
  10. extern crate utils;
  11. use std::collections::HashMap;
  12. use slug::slugify;
  13. use tera::{Context, Tera};
  14. use config::{Config, Taxonomy as TaxonomyConfig};
  15. use errors::{Result, ResultExt};
  16. use content::{Page, sort_pages};
  17. use front_matter::SortBy;
  18. use utils::templates::render_template;
  19. /// A tag or category
  20. #[derive(Debug, Clone, Serialize, PartialEq)]
  21. pub struct TaxonomyItem {
  22. pub name: String,
  23. pub slug: String,
  24. pub permalink: String,
  25. pub pages: Vec<Page>,
  26. }
  27. impl TaxonomyItem {
  28. pub fn new(name: &str, path: &str, config: &Config, pages: Vec<Page>) -> TaxonomyItem {
  29. // Taxonomy are almost always used for blogs so we filter by dates
  30. // and it's not like we can sort things across sections by anything other
  31. // than dates
  32. let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date);
  33. let slug = slugify(name);
  34. let permalink = {
  35. config.make_permalink(&format!("/{}/{}", path, slug))
  36. };
  37. // We still append pages without dates at the end
  38. pages.extend(ignored_pages);
  39. TaxonomyItem {
  40. name: name.to_string(),
  41. permalink,
  42. slug,
  43. pages,
  44. }
  45. }
  46. }
  47. /// All the tags or categories
  48. #[derive(Debug, Clone, PartialEq, Serialize)]
  49. pub struct Taxonomy {
  50. pub kind: TaxonomyConfig,
  51. // this vec is sorted by the count of item
  52. pub items: Vec<TaxonomyItem>,
  53. }
  54. impl Taxonomy {
  55. fn new(kind: TaxonomyConfig, config: &Config, items: HashMap<String, Vec<Page>>) -> Taxonomy {
  56. let mut sorted_items = vec![];
  57. for (name, pages) in items {
  58. sorted_items.push(
  59. TaxonomyItem::new(&name, &kind.name, config, pages)
  60. );
  61. }
  62. sorted_items.sort_by(|a, b| a.name.cmp(&b.name));
  63. Taxonomy {
  64. kind,
  65. items: sorted_items,
  66. }
  67. }
  68. pub fn len(&self) -> usize {
  69. self.items.len()
  70. }
  71. pub fn is_empty(&self) -> bool {
  72. self.len() == 0
  73. }
  74. pub fn render_term(&self, item: &TaxonomyItem, tera: &Tera, config: &Config) -> Result<String> {
  75. let mut context = Context::new();
  76. context.insert("config", config);
  77. context.insert("term", item);
  78. context.insert("taxonomy", &self.kind);
  79. context.insert("current_url", &config.make_permalink(&format!("{}/{}", self.kind.name, item.slug)));
  80. context.insert("current_path", &format!("/{}/{}", self.kind.name, item.slug));
  81. render_template(&format!("{}/single.html", self.kind.name), tera, &context, &config.theme)
  82. .chain_err(|| format!("Failed to render single term {} page.", self.kind.name))
  83. }
  84. pub fn render_all_terms(&self, tera: &Tera, config: &Config) -> Result<String> {
  85. let mut context = Context::new();
  86. context.insert("config", config);
  87. context.insert("terms", &self.items);
  88. context.insert("taxonomy", &self.kind);
  89. context.insert("current_url", &config.make_permalink(&self.kind.name));
  90. context.insert("current_path", &self.kind.name);
  91. render_template(&format!("{}/list.html", self.kind.name), tera, &context, &config.theme)
  92. .chain_err(|| format!("Failed to render a list of {} page.", self.kind.name))
  93. }
  94. }
  95. pub fn find_taxonomies(config: &Config, all_pages: &[Page]) -> Result<Vec<Taxonomy>> {
  96. let taxonomies_def = {
  97. let mut m = HashMap::new();
  98. for t in &config.taxonomies {
  99. m.insert(t.name.clone(), t);
  100. }
  101. m
  102. };
  103. let mut all_taxonomies = HashMap::new();
  104. // Find all the taxonomies first
  105. for page in all_pages {
  106. for (name, val) in &page.meta.taxonomies {
  107. if taxonomies_def.contains_key(name) {
  108. all_taxonomies
  109. .entry(name)
  110. .or_insert_with(|| HashMap::new());
  111. for v in val {
  112. all_taxonomies.get_mut(name)
  113. .unwrap()
  114. .entry(v.to_string())
  115. .or_insert_with(|| vec![])
  116. .push(page.clone());
  117. }
  118. } else {
  119. bail!("Page `{}` has taxonomy `{}` which is not defined in config.toml", page.file.path.display(), name);
  120. }
  121. }
  122. }
  123. let mut taxonomies = vec![];
  124. for (name, taxo) in all_taxonomies {
  125. taxonomies.push(Taxonomy::new(taxonomies_def[name].clone(), config, taxo));
  126. }
  127. Ok(taxonomies)
  128. }
  129. #[cfg(test)]
  130. mod tests {
  131. use super::*;
  132. use std::collections::HashMap;
  133. use config::{Config, Taxonomy};
  134. use content::Page;
  135. #[test]
  136. fn can_make_taxonomies() {
  137. let mut config = Config::default();
  138. config.taxonomies = vec![
  139. Taxonomy { name: "categories".to_string(), ..Taxonomy::default() },
  140. Taxonomy { name: "tags".to_string(), ..Taxonomy::default() },
  141. Taxonomy { name: "authors".to_string(), ..Taxonomy::default() },
  142. ];
  143. let mut page1 = Page::default();
  144. let mut taxo_page1 = HashMap::new();
  145. taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]);
  146. taxo_page1.insert("categories".to_string(), vec!["Programming tutorials".to_string()]);
  147. page1.meta.taxonomies = taxo_page1;
  148. let mut page2 = Page::default();
  149. let mut taxo_page2 = HashMap::new();
  150. taxo_page2.insert("tags".to_string(), vec!["rust".to_string(), "js".to_string()]);
  151. taxo_page2.insert("categories".to_string(), vec!["Other".to_string()]);
  152. page2.meta.taxonomies = taxo_page2;
  153. let mut page3 = Page::default();
  154. let mut taxo_page3 = HashMap::new();
  155. taxo_page3.insert("tags".to_string(), vec!["js".to_string()]);
  156. taxo_page3.insert("authors".to_string(), vec!["Vincent Prouillet".to_string()]);
  157. page3.meta.taxonomies = taxo_page3;
  158. let pages = vec![page1, page2, page3];
  159. let taxonomies = find_taxonomies(&config, &pages).unwrap();
  160. let (tags, categories, authors) = {
  161. let mut t = None;
  162. let mut c = None;
  163. let mut a = None;
  164. for x in taxonomies {
  165. match x.kind.name.as_ref() {
  166. "tags" => t = Some(x),
  167. "categories" => c = Some(x),
  168. "authors" => a = Some(x),
  169. _ => unreachable!(),
  170. }
  171. }
  172. (t.unwrap(), c.unwrap(), a.unwrap())
  173. };
  174. assert_eq!(tags.items.len(), 3);
  175. assert_eq!(categories.items.len(), 2);
  176. assert_eq!(authors.items.len(), 1);
  177. assert_eq!(tags.items[0].name, "db");
  178. assert_eq!(tags.items[0].slug, "db");
  179. assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
  180. assert_eq!(tags.items[0].pages.len(), 1);
  181. assert_eq!(tags.items[1].name, "js");
  182. assert_eq!(tags.items[1].slug, "js");
  183. assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/");
  184. assert_eq!(tags.items[1].pages.len(), 2);
  185. assert_eq!(tags.items[2].name, "rust");
  186. assert_eq!(tags.items[2].slug, "rust");
  187. assert_eq!(tags.items[2].permalink, "http://a-website.com/tags/rust/");
  188. assert_eq!(tags.items[2].pages.len(), 2);
  189. assert_eq!(categories.items[0].name, "Other");
  190. assert_eq!(categories.items[0].slug, "other");
  191. assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/");
  192. assert_eq!(categories.items[0].pages.len(), 1);
  193. assert_eq!(categories.items[1].name, "Programming tutorials");
  194. assert_eq!(categories.items[1].slug, "programming-tutorials");
  195. assert_eq!(categories.items[1].permalink, "http://a-website.com/categories/programming-tutorials/");
  196. assert_eq!(categories.items[1].pages.len(), 1);
  197. }
  198. #[test]
  199. fn errors_on_unknown_taxonomy() {
  200. let mut config = Config::default();
  201. config.taxonomies = vec![
  202. Taxonomy { name: "authors".to_string(), ..Taxonomy::default() },
  203. ];
  204. let mut page1 = Page::default();
  205. let mut taxo_page1 = HashMap::new();
  206. taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]);
  207. page1.meta.taxonomies = taxo_page1;
  208. let taxonomies = find_taxonomies(&config, &vec![page1]);
  209. assert!(taxonomies.is_err());
  210. let err = taxonomies.unwrap_err();
  211. // no path as this is created by Default
  212. assert_eq!(err.description(), "Page `` has taxonomy `tags` which is not defined in config.toml");
  213. }
  214. }