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.

274 lines
9.4KB

  1. #[macro_use]
  2. extern crate serde_derive;
  3. extern crate tera;
  4. extern crate errors;
  5. extern crate config;
  6. extern crate content;
  7. #[cfg(test)]
  8. extern crate front_matter;
  9. use std::collections::HashMap;
  10. use tera::{Tera, Context, to_value, Value};
  11. use errors::{Result, ResultExt};
  12. use config::Config;
  13. use content::{Page, Section};
  14. /// A list of all the pages in the paginator with their index and links
  15. #[derive(Clone, Debug, PartialEq, Serialize)]
  16. pub struct Pager<'a> {
  17. /// The page number in the paginator (1-indexed)
  18. index: usize,
  19. /// Permalink to that page
  20. permalink: String,
  21. /// Path to that page
  22. path: String,
  23. /// All pages for the pager
  24. pages: Vec<&'a Page>
  25. }
  26. impl<'a> Pager<'a> {
  27. fn new(index: usize, pages: Vec<&'a Page>, permalink: String, path: String) -> Pager<'a> {
  28. Pager {
  29. index,
  30. permalink,
  31. path,
  32. pages,
  33. }
  34. }
  35. /// Returns a manually cloned Pager with the pages removed
  36. /// for use as template context
  37. fn clone_without_pages(&self) -> Pager<'a> {
  38. Pager {
  39. index: self.index,
  40. permalink: self.permalink.clone(),
  41. path: self.path.clone(),
  42. pages: vec![],
  43. }
  44. }
  45. }
  46. #[derive(Clone, Debug, PartialEq)]
  47. pub struct Paginator<'a> {
  48. /// All pages in the section
  49. all_pages: &'a [Page],
  50. /// Pages split in chunks of `paginate_by`
  51. pub pagers: Vec<Pager<'a>>,
  52. /// How many content pages on a paginated page at max
  53. paginate_by: usize,
  54. /// The section struct we're building the paginator for
  55. section: &'a Section,
  56. }
  57. impl<'a> Paginator<'a> {
  58. /// Create a new paginator
  59. /// It will always at least create one pager (the first) even if there are no pages to paginate
  60. pub fn new(all_pages: &'a [Page], section: &'a Section) -> Paginator<'a> {
  61. let paginate_by = section.meta.paginate_by.unwrap();
  62. let paginate_path = match section.meta.paginate_path {
  63. Some(ref p) => p,
  64. None => unreachable!(),
  65. };
  66. let mut pages = vec![];
  67. let mut current_page = vec![];
  68. for page in all_pages {
  69. current_page.push(page);
  70. if current_page.len() == paginate_by {
  71. pages.push(current_page);
  72. current_page = vec![];
  73. }
  74. }
  75. if !current_page.is_empty() {
  76. pages.push(current_page);
  77. }
  78. let mut pagers = vec![];
  79. for (index, page) in pages.iter().enumerate() {
  80. // First page has no pagination path
  81. if index == 0 {
  82. pagers.push(Pager::new(1, page.clone(), section.permalink.clone(), section.path.clone()));
  83. continue;
  84. }
  85. let page_path = format!("{}/{}/", paginate_path, index + 1);
  86. let permalink = format!("{}{}", section.permalink, page_path);
  87. let pager_path = if section.is_index() {
  88. page_path
  89. } else {
  90. format!("{}{}", section.path, page_path)
  91. };
  92. pagers.push(Pager::new(
  93. index + 1,
  94. page.clone(),
  95. permalink,
  96. pager_path,
  97. ));
  98. }
  99. // We always have the index one at least
  100. if pagers.is_empty() {
  101. pagers.push(Pager::new(1, vec![], section.permalink.clone(), section.path.clone()));
  102. }
  103. Paginator {
  104. all_pages,
  105. pagers,
  106. paginate_by,
  107. section,
  108. }
  109. }
  110. pub fn build_paginator_context(&self, current_pager: &Pager) -> HashMap<&str, Value> {
  111. let mut paginator = HashMap::new();
  112. // the pager index is 1-indexed so we want a 0-indexed one for indexing there
  113. let pager_index = current_pager.index - 1;
  114. // Global variables
  115. paginator.insert("paginate_by", to_value(self.paginate_by).unwrap());
  116. paginator.insert("first", to_value(&self.section.permalink).unwrap());
  117. let last_pager = &self.pagers[self.pagers.len() - 1];
  118. paginator.insert("last", to_value(&last_pager.permalink).unwrap());
  119. paginator.insert(
  120. "pagers",
  121. to_value(
  122. &self.pagers.iter().map(|p| p.clone_without_pages()).collect::<Vec<_>>()
  123. ).unwrap()
  124. );
  125. // Variables for this specific page
  126. if pager_index > 0 {
  127. let prev_pager = &self.pagers[pager_index - 1];
  128. paginator.insert("previous", to_value(&prev_pager.permalink).unwrap());
  129. } else {
  130. paginator.insert("previous", to_value::<Option<()>>(None).unwrap());
  131. }
  132. if pager_index < self.pagers.len() - 1 {
  133. let next_pager = &self.pagers[pager_index + 1];
  134. paginator.insert("next", to_value(&next_pager.permalink).unwrap());
  135. } else {
  136. paginator.insert("next", to_value::<Option<()>>(None).unwrap());
  137. }
  138. paginator.insert("pages", to_value(&current_pager.pages).unwrap());
  139. paginator.insert("current_index", to_value(current_pager.index).unwrap());
  140. paginator
  141. }
  142. pub fn render_pager(&self, pager: &Pager, config: &Config, tera: &Tera) -> Result<String> {
  143. let mut context = Context::new();
  144. context.add("config", &config);
  145. context.add("section", self.section);
  146. context.add("current_url", &pager.permalink);
  147. context.add("current_path", &pager.path);
  148. context.add("paginator", &self.build_paginator_context(pager));
  149. tera.render(&self.section.get_template_name(), &context)
  150. .chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file.path.display()))
  151. }
  152. }
  153. #[cfg(test)]
  154. mod tests {
  155. use tera::to_value;
  156. use front_matter::SectionFrontMatter;
  157. use content::{Page, Section};
  158. use super::Paginator;
  159. fn create_section(is_index: bool) -> Section {
  160. let mut f = SectionFrontMatter::default();
  161. f.paginate_by = Some(2);
  162. f.paginate_path = Some("page".to_string());
  163. let mut s = Section::new("content/_index.md", f);
  164. if !is_index {
  165. s.path = "posts/".to_string();
  166. s.permalink = "https://vincent.is/posts/".to_string();
  167. s.file.components = vec!["posts".to_string()];
  168. } else {
  169. s.permalink = "https://vincent.is/".to_string();
  170. }
  171. s
  172. }
  173. #[test]
  174. fn test_can_create_paginator() {
  175. let pages = vec![
  176. Page::default(),
  177. Page::default(),
  178. Page::default(),
  179. ];
  180. let section = create_section(false);
  181. let paginator = Paginator::new(pages.as_slice(), &section);
  182. assert_eq!(paginator.pagers.len(), 2);
  183. assert_eq!(paginator.pagers[0].index, 1);
  184. assert_eq!(paginator.pagers[0].pages.len(), 2);
  185. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/posts/");
  186. assert_eq!(paginator.pagers[0].path, "posts/");
  187. assert_eq!(paginator.pagers[1].index, 2);
  188. assert_eq!(paginator.pagers[1].pages.len(), 1);
  189. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/posts/page/2/");
  190. assert_eq!(paginator.pagers[1].path, "posts/page/2/");
  191. }
  192. #[test]
  193. fn test_can_create_paginator_for_index() {
  194. let pages = vec![
  195. Page::default(),
  196. Page::default(),
  197. Page::default(),
  198. ];
  199. let section = create_section(true);
  200. let paginator = Paginator::new(pages.as_slice(), &section);
  201. assert_eq!(paginator.pagers.len(), 2);
  202. assert_eq!(paginator.pagers[0].index, 1);
  203. assert_eq!(paginator.pagers[0].pages.len(), 2);
  204. assert_eq!(paginator.pagers[0].permalink, "https://vincent.is/");
  205. assert_eq!(paginator.pagers[0].path, "");
  206. assert_eq!(paginator.pagers[1].index, 2);
  207. assert_eq!(paginator.pagers[1].pages.len(), 1);
  208. assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/page/2/");
  209. assert_eq!(paginator.pagers[1].path, "page/2/");
  210. }
  211. #[test]
  212. fn test_can_build_paginator_context() {
  213. let pages = vec![
  214. Page::default(),
  215. Page::default(),
  216. Page::default(),
  217. ];
  218. let section = create_section(false);
  219. let paginator = Paginator::new(pages.as_slice(), &section);
  220. assert_eq!(paginator.pagers.len(), 2);
  221. let context = paginator.build_paginator_context(&paginator.pagers[0]);
  222. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  223. assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
  224. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
  225. assert_eq!(context["previous"], to_value::<Option<()>>(None).unwrap());
  226. assert_eq!(context["next"], to_value("https://vincent.is/posts/page/2/").unwrap());
  227. assert_eq!(context["current_index"], to_value(1).unwrap());
  228. let context = paginator.build_paginator_context(&paginator.pagers[1]);
  229. assert_eq!(context["paginate_by"], to_value(2).unwrap());
  230. assert_eq!(context["first"], to_value("https://vincent.is/posts/").unwrap());
  231. assert_eq!(context["last"], to_value("https://vincent.is/posts/page/2/").unwrap());
  232. assert_eq!(context["next"], to_value::<Option<()>>(None).unwrap());
  233. assert_eq!(context["previous"], to_value("https://vincent.is/posts/").unwrap());
  234. assert_eq!(context["current_index"], to_value(2).unwrap());
  235. }
  236. }