2018-10-02 14:42:34 +00:00
|
|
|
use std::cmp::Ordering;
|
|
|
|
|
2018-10-31 07:18:57 +00:00
|
|
|
use chrono::NaiveDateTime;
|
2018-10-02 14:42:34 +00:00
|
|
|
use rayon::prelude::*;
|
|
|
|
use slotmap::Key;
|
|
|
|
|
|
|
|
use content::Page;
|
|
|
|
|
2018-10-05 17:34:27 +00:00
|
|
|
/// Used by the RSS feed
|
|
|
|
/// There to not have to import sorting stuff in the site crate
|
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.
2019-07-12 08:29:44 +00:00
|
|
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
2018-10-02 14:42:34 +00:00
|
|
|
pub fn sort_actual_pages_by_date(a: &&Page, b: &&Page) -> Ordering {
|
|
|
|
let ord = b.meta.datetime.unwrap().cmp(&a.meta.datetime.unwrap());
|
|
|
|
if ord == Ordering::Equal {
|
|
|
|
a.permalink.cmp(&b.permalink)
|
|
|
|
} else {
|
|
|
|
ord
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-05 17:34:27 +00:00
|
|
|
/// Takes a list of (page key, date, permalink) and sort them by dates if possible
|
|
|
|
/// Pages without date will be put in the unsortable bucket
|
|
|
|
/// The permalink is used to break ties
|
2018-10-02 14:42:34 +00:00
|
|
|
pub fn sort_pages_by_date(pages: Vec<(&Key, Option<NaiveDateTime>, &str)>) -> (Vec<Key>, Vec<Key>) {
|
2018-10-31 07:18:57 +00:00
|
|
|
let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
|
|
|
|
pages.into_par_iter().partition(|page| page.1.is_some());
|
|
|
|
|
|
|
|
can_be_sorted.par_sort_unstable_by(|a, b| {
|
|
|
|
let ord = b.1.unwrap().cmp(&a.1.unwrap());
|
|
|
|
if ord == Ordering::Equal {
|
|
|
|
a.2.cmp(&b.2)
|
|
|
|
} else {
|
|
|
|
ord
|
|
|
|
}
|
|
|
|
});
|
2018-10-02 14:42:34 +00:00
|
|
|
|
|
|
|
(can_be_sorted.iter().map(|p| *p.0).collect(), cannot_be_sorted.iter().map(|p| *p.0).collect())
|
|
|
|
}
|
|
|
|
|
2018-10-05 17:34:27 +00:00
|
|
|
/// Takes a list of (page key, weight, permalink) and sort them by weight if possible
|
|
|
|
/// Pages without weight will be put in the unsortable bucket
|
|
|
|
/// The permalink is used to break ties
|
2018-10-02 14:42:34 +00:00
|
|
|
pub fn sort_pages_by_weight(pages: Vec<(&Key, Option<usize>, &str)>) -> (Vec<Key>, Vec<Key>) {
|
2018-10-31 07:18:57 +00:00
|
|
|
let (mut can_be_sorted, cannot_be_sorted): (Vec<_>, Vec<_>) =
|
|
|
|
pages.into_par_iter().partition(|page| page.1.is_some());
|
|
|
|
|
|
|
|
can_be_sorted.par_sort_unstable_by(|a, b| {
|
|
|
|
let ord = a.1.unwrap().cmp(&b.1.unwrap());
|
|
|
|
if ord == Ordering::Equal {
|
|
|
|
a.2.cmp(&b.2)
|
|
|
|
} else {
|
|
|
|
ord
|
|
|
|
}
|
|
|
|
});
|
2018-10-02 14:42:34 +00:00
|
|
|
|
|
|
|
(can_be_sorted.iter().map(|p| *p.0).collect(), cannot_be_sorted.iter().map(|p| *p.0).collect())
|
|
|
|
}
|
|
|
|
|
2018-10-05 17:34:27 +00:00
|
|
|
/// Find the lighter/heavier and earlier/later pages for all pages having a date/weight
|
|
|
|
/// and that are not drafts.
|
2018-10-02 14:42:34 +00:00
|
|
|
pub fn find_siblings(sorted: Vec<(&Key, bool)>) -> Vec<(Key, Option<Key>, Option<Key>)> {
|
|
|
|
let mut res = Vec::with_capacity(sorted.len());
|
|
|
|
let length = sorted.len();
|
|
|
|
|
|
|
|
for (i, (key, is_draft)) in sorted.iter().enumerate() {
|
|
|
|
if *is_draft {
|
|
|
|
res.push((**key, None, None));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let mut with_siblings = (**key, None, None);
|
|
|
|
|
|
|
|
if i > 0 {
|
|
|
|
let mut j = i;
|
|
|
|
loop {
|
|
|
|
if j == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
j -= 1;
|
|
|
|
|
|
|
|
if sorted[j].1 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// lighter / later
|
|
|
|
with_siblings.1 = Some(*sorted[j].0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if i < length - 1 {
|
|
|
|
let mut j = i;
|
|
|
|
loop {
|
|
|
|
if j == length - 1 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
j += 1;
|
|
|
|
|
|
|
|
if sorted[j].1 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// heavier/earlier
|
|
|
|
with_siblings.2 = Some(*sorted[j].0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
res.push(with_siblings);
|
|
|
|
}
|
|
|
|
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use slotmap::DenseSlotMap;
|
2019-03-08 22:26:57 +00:00
|
|
|
use std::path::PathBuf;
|
2018-10-02 14:42:34 +00:00
|
|
|
|
2018-10-31 07:18:57 +00:00
|
|
|
use super::{find_siblings, sort_pages_by_date, sort_pages_by_weight};
|
2018-10-02 14:42:34 +00:00
|
|
|
use content::Page;
|
2018-10-31 07:18:57 +00:00
|
|
|
use front_matter::PageFrontMatter;
|
2018-10-02 14:42:34 +00:00
|
|
|
|
|
|
|
fn create_page_with_date(date: &str) -> Page {
|
|
|
|
let mut front_matter = PageFrontMatter::default();
|
|
|
|
front_matter.date = Some(date.to_string());
|
|
|
|
front_matter.date_to_datetime();
|
2019-03-08 22:26:57 +00:00
|
|
|
Page::new("content/hello.md", front_matter, &PathBuf::new())
|
2018-10-02 14:42:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn create_page_with_weight(weight: usize) -> Page {
|
|
|
|
let mut front_matter = PageFrontMatter::default();
|
|
|
|
front_matter.weight = Some(weight);
|
2019-03-08 22:26:57 +00:00
|
|
|
Page::new("content/hello.md", front_matter, &PathBuf::new())
|
2018-10-02 14:42:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_sort_by_dates() {
|
|
|
|
let mut dense = DenseSlotMap::new();
|
|
|
|
let page1 = create_page_with_date("2018-01-01");
|
|
|
|
let key1 = dense.insert(page1.clone());
|
|
|
|
let page2 = create_page_with_date("2017-01-01");
|
|
|
|
let key2 = dense.insert(page2.clone());
|
|
|
|
let page3 = create_page_with_date("2019-01-01");
|
|
|
|
let key3 = dense.insert(page3.clone());
|
|
|
|
|
|
|
|
let input = vec![
|
|
|
|
(&key1, page1.meta.datetime, page1.permalink.as_ref()),
|
|
|
|
(&key2, page2.meta.datetime, page2.permalink.as_ref()),
|
|
|
|
(&key3, page3.meta.datetime, page3.permalink.as_ref()),
|
|
|
|
];
|
|
|
|
let (pages, _) = sort_pages_by_date(input);
|
|
|
|
// Should be sorted by date
|
|
|
|
assert_eq!(pages[0], key3);
|
|
|
|
assert_eq!(pages[1], key1);
|
|
|
|
assert_eq!(pages[2], key2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_sort_by_weight() {
|
|
|
|
let mut dense = DenseSlotMap::new();
|
|
|
|
let page1 = create_page_with_weight(2);
|
|
|
|
let key1 = dense.insert(page1.clone());
|
|
|
|
let page2 = create_page_with_weight(3);
|
|
|
|
let key2 = dense.insert(page2.clone());
|
|
|
|
let page3 = create_page_with_weight(1);
|
|
|
|
let key3 = dense.insert(page3.clone());
|
|
|
|
|
|
|
|
let input = vec![
|
|
|
|
(&key1, page1.meta.weight, page1.permalink.as_ref()),
|
|
|
|
(&key2, page2.meta.weight, page2.permalink.as_ref()),
|
|
|
|
(&key3, page3.meta.weight, page3.permalink.as_ref()),
|
|
|
|
];
|
|
|
|
let (pages, _) = sort_pages_by_weight(input);
|
|
|
|
// Should be sorted by weight
|
|
|
|
assert_eq!(pages[0], key3);
|
|
|
|
assert_eq!(pages[1], key1);
|
|
|
|
assert_eq!(pages[2], key2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ignore_page_with_missing_field() {
|
|
|
|
let mut dense = DenseSlotMap::new();
|
|
|
|
let page1 = create_page_with_weight(2);
|
|
|
|
let key1 = dense.insert(page1.clone());
|
|
|
|
let page2 = create_page_with_weight(3);
|
|
|
|
let key2 = dense.insert(page2.clone());
|
|
|
|
let page3 = create_page_with_date("2019-01-01");
|
|
|
|
let key3 = dense.insert(page3.clone());
|
|
|
|
|
|
|
|
let input = vec![
|
|
|
|
(&key1, page1.meta.weight, page1.permalink.as_ref()),
|
|
|
|
(&key2, page2.meta.weight, page2.permalink.as_ref()),
|
|
|
|
(&key3, page3.meta.weight, page3.permalink.as_ref()),
|
|
|
|
];
|
|
|
|
|
2018-10-31 07:18:57 +00:00
|
|
|
let (pages, unsorted) = sort_pages_by_weight(input);
|
2018-10-02 14:42:34 +00:00
|
|
|
assert_eq!(pages.len(), 2);
|
|
|
|
assert_eq!(unsorted.len(), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_find_siblings() {
|
|
|
|
let mut dense = DenseSlotMap::new();
|
|
|
|
let page1 = create_page_with_weight(1);
|
|
|
|
let key1 = dense.insert(page1.clone());
|
|
|
|
let page2 = create_page_with_weight(2);
|
|
|
|
let key2 = dense.insert(page2.clone());
|
|
|
|
let page3 = create_page_with_weight(3);
|
|
|
|
let key3 = dense.insert(page3.clone());
|
|
|
|
|
2018-10-31 07:18:57 +00:00
|
|
|
let input =
|
|
|
|
vec![(&key1, page1.is_draft()), (&key2, page2.is_draft()), (&key3, page3.is_draft())];
|
2018-10-02 14:42:34 +00:00
|
|
|
|
|
|
|
let pages = find_siblings(input);
|
|
|
|
|
|
|
|
assert_eq!(pages[0].1, None);
|
|
|
|
assert_eq!(pages[0].2, Some(key2));
|
|
|
|
|
|
|
|
assert_eq!(pages[1].1, Some(key1));
|
|
|
|
assert_eq!(pages[1].2, Some(key3));
|
|
|
|
|
|
|
|
assert_eq!(pages[2].1, Some(key2));
|
|
|
|
assert_eq!(pages[2].2, None);
|
|
|
|
}
|
|
|
|
}
|