use archon_aur::AurClient;
use archon_repo::OfficialRepos;
pub struct Hit {
pub name: String,
pub version: String,
pub source: &'static str,
pub description: Option<String>,
pub votes: u64,
pub popularity: f64,
}
pub fn find(official: &OfficialRepos, aur: &AurClient, term: &str) -> Vec<Hit> {
let mut hits: Vec<Hit> = official
.search(term)
.into_iter()
.map(|p| Hit {
name: p.name.clone(),
version: p.version.to_string(),
source: "official",
description: p.description.clone(),
votes: 0,
popularity: 0.0,
})
.collect();
if let Ok(aur_hits) = aur.search_by_name(term) {
hits.extend(aur_hits.into_iter().map(|p| Hit {
name: p.name,
version: p.version,
source: "aur",
description: p.description,
votes: p.num_votes,
popularity: p.popularity,
}));
}
rank(&mut hits, term);
hits
}
fn rank(hits: &mut [Hit], term: &str) {
let term = term.to_lowercase();
hits.sort_by(|a, b| {
let exact = |h: &Hit| h.name.to_lowercase() != term;
let prefix = |h: &Hit| !h.name.to_lowercase().starts_with(&term);
exact(a)
.cmp(&exact(b))
.then_with(|| b.popularity.total_cmp(&a.popularity))
.then_with(|| (a.source != "official").cmp(&(b.source != "official")))
.then_with(|| prefix(a).cmp(&prefix(b)))
.then_with(|| a.name.cmp(&b.name))
});
}
#[cfg(test)]
mod tests {
use super::*;
fn hit(name: &str, source: &'static str, popularity: f64) -> Hit {
Hit {
name: name.into(),
version: "1.0-1".into(),
source,
description: None,
votes: 0,
popularity,
}
}
fn ranked_names(mut hits: Vec<Hit>, term: &str) -> Vec<String> {
rank(&mut hits, term);
hits.into_iter().map(|h| h.name).collect()
}
#[test]
fn popularity_beats_alphabetical_order() {
let names = ranked_names(
vec![
hit("chrome-extension-ocrs-git", "aur", 0.0),
hit("google-chrome", "aur", 30.0),
hit("chromedriver", "aur", 2.5),
],
"chrome",
);
assert_eq!(names, ["google-chrome", "chromedriver", "chrome-extension-ocrs-git"]);
}
#[test]
fn exact_match_pins_first_regardless_of_popularity() {
let names = ranked_names(
vec![hit("python-somethingpopular", "aur", 99.0), hit("python", "official", 0.0)],
"python",
);
assert_eq!(names[0], "python");
}
#[test]
fn prefix_match_beats_plain_substring_within_same_popularity() {
let names = ranked_names(
vec![hit("libchrome-tools", "aur", 0.0), hit("chrome-tools", "aur", 0.0)],
"chrome",
);
assert_eq!(names, ["chrome-tools", "libchrome-tools"]);
}
#[test]
fn official_wins_ties_but_not_against_real_popularity() {
let names = ranked_names(
vec![
hit("chrome-junk-git", "aur", 0.0),
hit("vlc-plugin-chromecast", "official", 0.0),
hit("google-chrome", "aur", 30.0),
],
"chrome",
);
assert_eq!(names, ["google-chrome", "vlc-plugin-chromecast", "chrome-junk-git"]);
}
#[test]
fn ranking_is_case_insensitive() {
let names = ranked_names(vec![hit("other-Firefox-thing", "aur", 5.0), hit("Firefox", "aur", 0.0)], "firefox");
assert_eq!(names[0], "Firefox", "exact match must be found despite case differences");
}
}