use std::cmp::Ordering;
pub const NAMES_FILE_V2: &str = "names-v2.tsv.zst";
pub const DESCRIPTIONS_FILE_V2: &str = "descriptions-v2.tsv.zst";
pub const NAMES_URL_V2: &str =
"https://github.com/jbr/crate-names/releases/download/artifacts/names-v2.tsv.zst";
pub const DESCRIPTIONS_URL_V2: &str =
"https://github.com/jbr/crate-names/releases/download/artifacts/descriptions-v2.tsv.zst";
pub const FACETS_FILE_V1: &str = "facets-v1.tsv.zst";
pub const FACETS_URL_V1: &str =
"https://github.com/jbr/crate-names/releases/download/artifacts/facets-v1.tsv.zst";
const fn fold(byte: u8) -> u8 {
match byte {
b'_' => b'-',
other => other.to_ascii_lowercase(),
}
}
pub fn normalize(name: &str) -> String {
name.bytes().map(fold).map(char::from).collect()
}
pub(crate) fn folded_cmp(left: &str, right: &str) -> Ordering {
left.bytes().map(fold).cmp(right.bytes().map(fold))
}
pub(crate) fn folded_cmp_key(name: &str, key: &str) -> Ordering {
name.bytes().map(fold).cmp(key.bytes())
}
pub(crate) fn folded_starts_with(name: &str, key: &str) -> bool {
name.len() >= key.len()
&& name
.bytes()
.map(fold)
.zip(key.bytes())
.all(|(name_byte, key_byte)| name_byte == key_byte)
}
#[cfg(feature = "build")]
pub(crate) const ZSTD_LEVEL: i32 = 19;
pub fn rank_from_downloads(downloads: u64) -> u8 {
let rank = (8.0 * (downloads.saturating_add(1) as f64).log2()).floor();
if rank >= 255.0 { 255 } else { rank as u8 }
}
#[cfg(feature = "build")]
pub(crate) fn flatten_whitespace(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rank_is_monotonic_and_bounded() {
assert_eq!(rank_from_downloads(0), 0);
let mut prev = 0;
for downloads in [1, 5, 100, 1_000, 1_000_000, 500_000_000, u64::MAX] {
let rank = rank_from_downloads(downloads);
assert!(rank >= prev, "rank must not decrease");
prev = rank;
}
assert_eq!(rank_from_downloads(u64::MAX), 255);
assert!(rank_from_downloads(500_000_000) < 255);
}
}