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.

sorting.rs 7.1KB

5 years ago
Fix clippy warnings (#744) Clippy is returning some warnings. Let's fix or explicitly ignore them. In particular: - In `components/imageproc/src/lib.rs`, we implement `Hash` explicitly but derive `PartialEq`. We need to maintain the property that two keys being equal implies the hashes of those two keys are equal. Our `Hash` implementations preserve this, so we'll explicitly ignore the warnings. - In `components/site/src/lib.rs`, we were calling `.into()` on some values that are already of the correct type. - In `components/site/src/lib.rs`, we were using `.map(|x| *x)` in iterator chains to remove a level of indirection; we can instead say `.copied()` (introduced in Rust v1.36) or `.cloned()`. Using `.copied` here is better from a type-checking point of view, but we'll use `.cloned` for now as Rust v1.36 was only recently released. - In `components/templates/src/filters.rs` and `components/utils/src/site.rs`, we were taking `HashMap`s as function arguments but not generically accepting alternate `Hasher` implementations. - In `src/cmd/check.rs`, we use `env::current_dir()` as a default value, but our use of `unwrap_or` meant that we would always retrieve the current directory even when not needed. - In `components/errors/src/lib.rs`, we can use `if let` rather than `match`. - In `components/library/src/content/page.rs`, we can collapse a nested conditional into `else if let ...`. - In `components/library/src/sorting.rs`, a function takes `&&Page` arguments. Clippy warns about this for efficiency reasons, but we're doing it here to match a particular sorting API, so we'll explicitly ignore the warning.
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. use std::cmp::Ordering;
  2. use chrono::NaiveDateTime;
  3. use rayon::prelude::*;
  4. use slotmap::Key;
  5. use content::Page;
  6. /// Used by the RSS feed
  7. /// There to not have to import sorting stuff in the site crate
  8. #[allow(clippy::trivially_copy_pass_by_ref)]
  9. pub fn sort_actual_pages_by_date(a: &&Page, b: &&Page) -> Ordering {
  10. let ord = b.meta.datetime.unwrap().cmp(&a.meta.datetime.unwrap());
  11. if ord == Ordering::Equal {
  12. a.permalink.cmp(&b.permalink)
  13. } else {
  14. ord
  15. }
  16. }
  17. /// Takes a list of (page key, date, permalink) and sort them by dates if possible
  18. /// Pages without date will be put in the unsortable bucket
  19. /// The permalink is used to break ties
  20. pub fn sort_pages_by_date(pages: Vec<(&Key, Option<NaiveDateTime>, &str)>) -> (Vec<Key>, Vec<Key>) {
  21. let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
  22. pages.into_par_iter().partition(|page| page.1.is_some());
  23. can_be_sorted.par_sort_unstable_by(|a, b| {
  24. let ord = b.1.unwrap().cmp(&a.1.unwrap());
  25. if ord == Ordering::Equal {
  26. a.2.cmp(&b.2)
  27. } else {
  28. ord
  29. }
  30. });
  31. (can_be_sorted.iter().map(|p| *p.0).collect(), cannot_be_sorted.iter().map(|p| *p.0).collect())
  32. }
  33. /// Takes a list of (page key, weight, permalink) and sort them by weight if possible
  34. /// Pages without weight will be put in the unsortable bucket
  35. /// The permalink is used to break ties
  36. pub fn sort_pages_by_weight(pages: Vec<(&Key, Option<usize>, &str)>) -> (Vec<Key>, Vec<Key>) {
  37. let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
  38. pages.into_par_iter().partition(|page| page.1.is_some());
  39. can_be_sorted.par_sort_unstable_by(|a, b| {
  40. let ord = a.1.unwrap().cmp(&b.1.unwrap());
  41. if ord == Ordering::Equal {
  42. a.2.cmp(&b.2)
  43. } else {
  44. ord
  45. }
  46. });
  47. (can_be_sorted.iter().map(|p| *p.0).collect(), cannot_be_sorted.iter().map(|p| *p.0).collect())
  48. }
  49. /// Find the lighter/heavier and earlier/later pages for all pages having a date/weight
  50. /// and that are not drafts.
  51. pub fn find_siblings(sorted: Vec<(&Key, bool)>) -> Vec<(Key, Option<Key>, Option<Key>)> {
  52. let mut res = Vec::with_capacity(sorted.len());
  53. let length = sorted.len();
  54. for (i, (key, is_draft)) in sorted.iter().enumerate() {
  55. if *is_draft {
  56. res.push((**key, None, None));
  57. continue;
  58. }
  59. let mut with_siblings = (**key, None, None);
  60. if i > 0 {
  61. let mut j = i;
  62. loop {
  63. if j == 0 {
  64. break;
  65. }
  66. j -= 1;
  67. if sorted[j].1 {
  68. continue;
  69. }
  70. // lighter / later
  71. with_siblings.1 = Some(*sorted[j].0);
  72. break;
  73. }
  74. }
  75. if i < length - 1 {
  76. let mut j = i;
  77. loop {
  78. if j == length - 1 {
  79. break;
  80. }
  81. j += 1;
  82. if sorted[j].1 {
  83. continue;
  84. }
  85. // heavier/earlier
  86. with_siblings.2 = Some(*sorted[j].0);
  87. break;
  88. }
  89. }
  90. res.push(with_siblings);
  91. }
  92. res
  93. }
  94. #[cfg(test)]
  95. mod tests {
  96. use slotmap::DenseSlotMap;
  97. use std::path::PathBuf;
  98. use super::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
  99. use content::Page;
  100. use front_matter::PageFrontMatter;
  101. fn create_page_with_date(date: &str) -> Page {
  102. let mut front_matter = PageFrontMatter::default();
  103. front_matter.date = Some(date.to_string());
  104. front_matter.date_to_datetime();
  105. Page::new("content/hello.md", front_matter, &PathBuf::new())
  106. }
  107. fn create_page_with_weight(weight: usize) -> Page {
  108. let mut front_matter = PageFrontMatter::default();
  109. front_matter.weight = Some(weight);
  110. Page::new("content/hello.md", front_matter, &PathBuf::new())
  111. }
  112. #[test]
  113. fn can_sort_by_dates() {
  114. let mut dense = DenseSlotMap::new();
  115. let page1 = create_page_with_date("2018-01-01");
  116. let key1 = dense.insert(page1.clone());
  117. let page2 = create_page_with_date("2017-01-01");
  118. let key2 = dense.insert(page2.clone());
  119. let page3 = create_page_with_date("2019-01-01");
  120. let key3 = dense.insert(page3.clone());
  121. let input = vec![
  122. (&key1, page1.meta.datetime, page1.permalink.as_ref()),
  123. (&key2, page2.meta.datetime, page2.permalink.as_ref()),
  124. (&key3, page3.meta.datetime, page3.permalink.as_ref()),
  125. ];
  126. let (pages, _) = sort_pages_by_date(input);
  127. // Should be sorted by date
  128. assert_eq!(pages[0], key3);
  129. assert_eq!(pages[1], key1);
  130. assert_eq!(pages[2], key2);
  131. }
  132. #[test]
  133. fn can_sort_by_weight() {
  134. let mut dense = DenseSlotMap::new();
  135. let page1 = create_page_with_weight(2);
  136. let key1 = dense.insert(page1.clone());
  137. let page2 = create_page_with_weight(3);
  138. let key2 = dense.insert(page2.clone());
  139. let page3 = create_page_with_weight(1);
  140. let key3 = dense.insert(page3.clone());
  141. let input = vec![
  142. (&key1, page1.meta.weight, page1.permalink.as_ref()),
  143. (&key2, page2.meta.weight, page2.permalink.as_ref()),
  144. (&key3, page3.meta.weight, page3.permalink.as_ref()),
  145. ];
  146. let (pages, _) = sort_pages_by_weight(input);
  147. // Should be sorted by weight
  148. assert_eq!(pages[0], key3);
  149. assert_eq!(pages[1], key1);
  150. assert_eq!(pages[2], key2);
  151. }
  152. #[test]
  153. fn ignore_page_with_missing_field() {
  154. let mut dense = DenseSlotMap::new();
  155. let page1 = create_page_with_weight(2);
  156. let key1 = dense.insert(page1.clone());
  157. let page2 = create_page_with_weight(3);
  158. let key2 = dense.insert(page2.clone());
  159. let page3 = create_page_with_date("2019-01-01");
  160. let key3 = dense.insert(page3.clone());
  161. let input = vec![
  162. (&key1, page1.meta.weight, page1.permalink.as_ref()),
  163. (&key2, page2.meta.weight, page2.permalink.as_ref()),
  164. (&key3, page3.meta.weight, page3.permalink.as_ref()),
  165. ];
  166. let (pages, unsorted) = sort_pages_by_weight(input);
  167. assert_eq!(pages.len(), 2);
  168. assert_eq!(unsorted.len(), 1);
  169. }
  170. #[test]
  171. fn can_find_siblings() {
  172. let mut dense = DenseSlotMap::new();
  173. let page1 = create_page_with_weight(1);
  174. let key1 = dense.insert(page1.clone());
  175. let page2 = create_page_with_weight(2);
  176. let key2 = dense.insert(page2.clone());
  177. let page3 = create_page_with_weight(3);
  178. let key3 = dense.insert(page3.clone());
  179. let input =
  180. vec![(&key1, page1.is_draft()), (&key2, page2.is_draft()), (&key3, page3.is_draft())];
  181. let pages = find_siblings(input);
  182. assert_eq!(pages[0].1, None);
  183. assert_eq!(pages[0].2, Some(key2));
  184. assert_eq!(pages[1].1, Some(key1));
  185. assert_eq!(pages[1].2, Some(key3));
  186. assert_eq!(pages[2].1, Some(key2));
  187. assert_eq!(pages[2].2, None);
  188. }
  189. }