use std::cmp::Ordering;
const FIRST: [&str; 20] = [
"Type",
"Package",
"Title",
"Version",
"Date",
"Authors@R",
"Author",
"Maintainer",
"Description",
"License",
"URL",
"BugReports",
"Depends",
"Imports",
"Suggests",
"Enhances",
"LinkingTo",
"VignetteBuilder",
"RdMacros",
"Remotes",
];
const MIDDLE: [&str; 49] = [
"Acknowledgements",
"Acknowledgments",
"Additional_repositories",
"Archs",
"Biarch",
"biocViews",
"BuildKeepEmpty",
"BuildManual",
"BuildResaveData",
"BuildVignettes",
"Built",
"ByteCompile",
"Classification/ACM",
"Classification/ACM-2012",
"Classification/JEL",
"Classification/MSC",
"Classification/MSC-2010",
"Contact",
"Copyright",
"Date/Publication",
"Encoding",
"KeepSource",
"Language",
"LastChangedDate",
"LastChangedRevision",
"LazyData",
"LazyDataCompression",
"LazyLoad",
"License_is_FOSS",
"License_restricts_use",
"MailingList",
"MD5sum",
"NeedsCompilation",
"Note",
"OS_type",
"Packaged",
"Path",
"Priority",
"RcmdrModels",
"RcppModules",
"Repository",
"Revision",
"Roxygen",
"RoxygenNote",
"StagedInstall",
"SysDataCompression",
"SystemRequirements",
"UseLTO",
"ZipData",
];
const LAST: [&str; 3] = ["Collate", "Collate.windows", "Collate.unix"];
pub fn field_names() -> impl Iterator<Item = &'static str> + Clone {
FIRST.into_iter().chain(MIDDLE).chain(LAST)
}
fn rank(name: &str) -> (u8, usize) {
if let Some(index) = FIRST.iter().position(|candidate| *candidate == name) {
return (0, index);
}
if let Some(index) = LAST.iter().position(|candidate| *candidate == name) {
return (2, index);
}
(1, 0)
}
pub(super) fn compare_fields(left: &str, right: &str) -> Ordering {
let (left_bucket, left_index) = rank(left);
let (right_bucket, right_index) = rank(right);
left_bucket
.cmp(&right_bucket)
.then_with(|| match left_bucket {
1 => collate(left, right),
_ => left_index.cmp(&right_index),
})
}
pub(super) fn collate(left: &str, right: &str) -> Ordering {
let folded = left
.bytes()
.map(|byte| byte.to_ascii_lowercase())
.cmp(right.bytes().map(|byte| byte.to_ascii_lowercase()));
folded.then_with(|| left.cmp(right))
}
#[cfg(test)]
mod tests {
use super::*;
fn sorted(mut names: Vec<&str>) -> Vec<&str> {
names.sort_by(|left, right| compare_fields(left, right));
names
}
#[test]
fn the_fixed_head_keeps_its_written_order() {
assert_eq!(
sorted(vec!["Version", "Package", "Title", "Type"]),
vec!["Type", "Package", "Title", "Version"]
);
}
#[test]
fn unknown_fields_sort_between_the_head_and_collate() {
assert_eq!(
sorted(vec![
"Collate",
"Encoding",
"Package",
"Config/Needs/website"
]),
vec!["Package", "Config/Needs/website", "Encoding", "Collate"]
);
}
#[test]
fn the_collate_family_comes_last_in_its_own_order() {
assert_eq!(
sorted(vec!["Collate.unix", "Collate", "Collate.windows"]),
vec!["Collate", "Collate.windows", "Collate.unix"]
);
}
#[test]
fn collation_is_case_insensitive_and_locale_free() {
assert_eq!(collate("R6", "rlang"), Ordering::Less);
assert_eq!(collate("pkgload", "R6"), Ordering::Less);
assert_eq!(collate("MASS", "zoo"), Ordering::Less);
assert_eq!(collate("Rcpp", "stats"), Ordering::Less);
assert_eq!(collate("abc", "ABC"), Ordering::Greater);
}
#[test]
fn exported_field_names_are_in_canonical_order() {
let names: Vec<_> = field_names().collect();
assert!(
names
.windows(2)
.all(|pair| { compare_fields(pair[0], pair[1]) == Ordering::Less })
);
}
}