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.

lib.rs 9.5KB

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