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.

262 lines
9.1KB

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