use std::cmp::Ordering; use rayon::prelude::*; use page::Page; use front_matter::SortBy; /// Sort pages by the given criteria /// /// Any pages that doesn't have a the required field when the sorting method is other than none /// will be ignored. pub fn sort_pages(pages: Vec, sort_by: SortBy) -> (Vec, Vec) { if sort_by == SortBy::None { return (pages, vec![]); } let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) = pages .into_par_iter() .partition(|page| { match sort_by { SortBy::Date => page.meta.date.is_some(), SortBy::Order => page.meta.order.is_some(), SortBy::Weight => page.meta.weight.is_some(), _ => unreachable!() } }); match sort_by { SortBy::Date => { can_be_sorted.par_sort_unstable_by(|a, b| { let ord = b.meta.date().unwrap().cmp(&a.meta.date().unwrap()); if ord == Ordering::Equal { a.permalink.cmp(&b.permalink) } else { ord } }) }, SortBy::Order => { can_be_sorted.par_sort_unstable_by(|a, b| { let ord = b.meta.order().cmp(&a.meta.order()); if ord == Ordering::Equal { a.permalink.cmp(&b.permalink) } else { ord } }) }, SortBy::Weight => { can_be_sorted.par_sort_unstable_by(|a, b| { let ord = a.meta.weight().cmp(&b.meta.weight()); if ord == Ordering::Equal { a.permalink.cmp(&b.permalink) } else { ord } }) }, _ => unreachable!() }; (can_be_sorted, cannot_be_sorted) } /// Horribly inefficient way to set previous and next on each pages that skips drafts /// So many clones pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec { let mut res = Vec::with_capacity(input.len()); // The input is already sorted for (i, _) in input.iter().enumerate() { let mut new_page = input[i].clone(); if new_page.is_draft() { res.push(new_page); continue; } if i > 0 { let mut j = i; loop { if j == 0 { break; } j -= 1; if input[j].is_draft() { continue; } // Remove prev/next otherwise we serialise the whole thing... let mut next_page = input[j].clone(); next_page.previous = None; next_page.next = None; new_page.next = Some(Box::new(next_page)); break; } } if i < input.len() - 1 { let mut j = i; loop { if j == input.len() - 1 { break; } j += 1; if input[j].is_draft() { continue; } // Remove prev/next otherwise we serialise the whole thing... let mut previous_page = input[j].clone(); previous_page.previous = None; previous_page.next = None; new_page.previous = Some(Box::new(previous_page)); break; } } res.push(new_page); } res } #[cfg(test)] mod tests { use front_matter::{PageFrontMatter, SortBy}; use page::Page; use super::{sort_pages, populate_previous_and_next_pages}; fn create_page_with_date(date: &str) -> Page { let mut front_matter = PageFrontMatter::default(); front_matter.date = Some(date.to_string()); Page::new("content/hello.md", front_matter) } fn create_page_with_order(order: usize, filename: &str) -> Page { let mut front_matter = PageFrontMatter::default(); front_matter.order = Some(order); let mut p = Page::new("content/".to_string() + filename, front_matter); // Faking a permalink to test sorting with equal order p.permalink = filename.to_string(); p } fn create_draft_page_with_order(order: usize) -> Page { let mut front_matter = PageFrontMatter::default(); front_matter.order = Some(order); front_matter.draft = Some(true); Page::new("content/hello.md", front_matter) } fn create_page_with_weight(weight: usize) -> Page { let mut front_matter = PageFrontMatter::default(); front_matter.weight = Some(weight); Page::new("content/hello.md", front_matter) } #[test] fn can_sort_by_dates() { let input = vec![ create_page_with_date("2018-01-01"), create_page_with_date("2017-01-01"), create_page_with_date("2019-01-01"), ]; let (pages, _) = sort_pages(input, SortBy::Date); // Should be sorted by date assert_eq!(pages[0].clone().meta.date.unwrap().to_string(), "2019-01-01"); assert_eq!(pages[1].clone().meta.date.unwrap().to_string(), "2018-01-01"); assert_eq!(pages[2].clone().meta.date.unwrap().to_string(), "2017-01-01"); } #[test] fn can_sort_by_order() { let input = vec![ create_page_with_order(2, "hello.md"), create_page_with_order(3, "hello2.md"), create_page_with_order(1, "hello3.md"), ]; let (pages, _) = sort_pages(input, SortBy::Order); // Should be sorted by order assert_eq!(pages[0].clone().meta.order.unwrap(), 3); assert_eq!(pages[1].clone().meta.order.unwrap(), 2); assert_eq!(pages[2].clone().meta.order.unwrap(), 1); } #[test] fn can_sort_by_order_uses_permalink_to_break_ties() { let input = vec![ create_page_with_order(3, "b.md"), create_page_with_order(3, "a.md"), create_page_with_order(3, "c.md"), ]; let (pages, _) = sort_pages(input, SortBy::Order); // Should be sorted by order assert_eq!(pages[0].clone().meta.order.unwrap(), 3); assert_eq!(pages[0].clone().permalink, "a.md"); assert_eq!(pages[1].clone().meta.order.unwrap(), 3); assert_eq!(pages[1].clone().permalink, "b.md"); assert_eq!(pages[2].clone().meta.order.unwrap(), 3); assert_eq!(pages[2].clone().permalink, "c.md"); } #[test] fn can_sort_by_weight() { let input = vec![ create_page_with_weight(2), create_page_with_weight(3), create_page_with_weight(1), ]; let (pages, _) = sort_pages(input, SortBy::Weight); // Should be sorted by weight assert_eq!(pages[0].clone().meta.weight.unwrap(), 1); assert_eq!(pages[1].clone().meta.weight.unwrap(), 2); assert_eq!(pages[2].clone().meta.weight.unwrap(), 3); } #[test] fn can_sort_by_none() { let input = vec![ create_page_with_order(2, "a.md"), create_page_with_order(3, "a.md"), create_page_with_order(1, "a.md"), ]; let (pages, _) = sort_pages(input, SortBy::None); // Should be sorted by date assert_eq!(pages[0].clone().meta.order.unwrap(), 2); assert_eq!(pages[1].clone().meta.order.unwrap(), 3); assert_eq!(pages[2].clone().meta.order.unwrap(), 1); } #[test] fn ignore_page_with_missing_field() { let input = vec![ create_page_with_order(2, "a.md"), create_page_with_order(3, "a.md"), create_page_with_date("2019-01-01"), ]; let (pages, unsorted) = sort_pages(input, SortBy::Order); assert_eq!(pages.len(), 2); assert_eq!(unsorted.len(), 1); } #[test] fn can_populate_previous_and_next_pages() { let input = vec![ create_page_with_order(1, "a.md"), create_page_with_order(2, "b.md"), create_page_with_order(3, "a.md"), ]; let pages = populate_previous_and_next_pages(&input); assert!(pages[0].clone().next.is_none()); assert!(pages[0].clone().previous.is_some()); assert_eq!(pages[0].clone().previous.unwrap().meta.order.unwrap(), 2); assert!(pages[1].clone().next.is_some()); assert!(pages[1].clone().previous.is_some()); assert_eq!(pages[1].clone().previous.unwrap().meta.order.unwrap(), 3); assert_eq!(pages[1].clone().next.unwrap().meta.order.unwrap(), 1); assert!(pages[2].clone().next.is_some()); assert!(pages[2].clone().previous.is_none()); assert_eq!(pages[2].clone().next.unwrap().meta.order.unwrap(), 2); } #[test] fn can_populate_previous_and_next_pages_skip_drafts() { let input = vec![ create_draft_page_with_order(0), create_page_with_order(1, "a.md"), create_page_with_order(2, "b.md"), create_page_with_order(3, "c.md"), create_draft_page_with_order(4), ]; let pages = populate_previous_and_next_pages(&input); assert!(pages[0].clone().next.is_none()); assert!(pages[0].clone().previous.is_none()); assert!(pages[1].clone().next.is_none()); assert!(pages[1].clone().previous.is_some()); assert_eq!(pages[1].clone().previous.unwrap().meta.order.unwrap(), 2); assert!(pages[2].clone().next.is_some()); assert!(pages[2].clone().previous.is_some()); assert_eq!(pages[2].clone().previous.unwrap().meta.order.unwrap(), 3); assert_eq!(pages[2].clone().next.unwrap().meta.order.unwrap(), 1); assert!(pages[3].clone().next.is_some()); assert!(pages[3].clone().previous.is_none()); assert_eq!(pages[3].clone().next.unwrap().meta.order.unwrap(), 2); assert!(pages[4].clone().next.is_none()); assert!(pages[4].clone().previous.is_none()); } }