use crate::state::{PackageItem, Source};
use super::idx;
#[must_use]
pub fn search_official(query: &str, fuzzy: bool) -> Vec<(PackageItem, Option<i64>)> {
let ql = query.trim();
if ql.is_empty() {
return Vec::new();
}
let mut items = Vec::new();
if let Ok(g) = idx().read() {
let fuzzy_matcher = if fuzzy {
Some(fuzzy_matcher::skim::SkimMatcherV2::default())
} else {
None
};
for p in &g.pkgs {
let match_score = if fuzzy {
fuzzy_matcher
.as_ref()
.and_then(|m| crate::util::fuzzy_match_rank_with_matcher(&p.name, ql, m))
} else {
let nl = p.name.to_lowercase();
let ql_lower = ql.to_lowercase();
if nl.contains(&ql_lower) {
Some(0) } else {
None
}
};
if let Some(score) = match_score {
items.push((
PackageItem {
name: p.name.clone(),
version: p.version.clone(),
description: p.description.clone(),
source: Source::Official {
repo: p.repo.clone(),
arch: p.arch.clone(),
},
popularity: None,
out_of_date: None,
orphaned: false,
},
Some(score),
));
}
}
}
items
}
#[must_use]
pub fn all_official() -> Vec<PackageItem> {
let mut items = Vec::new();
if let Ok(g) = idx().read() {
items.reserve(g.pkgs.len());
for p in &g.pkgs {
items.push(PackageItem {
name: p.name.clone(),
version: p.version.clone(),
description: p.description.clone(),
source: Source::Official {
repo: p.repo.clone(),
arch: p.arch.clone(),
},
popularity: None,
out_of_date: None,
orphaned: false,
});
}
}
items
}
#[must_use]
pub fn all_official_or_fetch(path: &std::path::Path) -> Vec<PackageItem> {
let items = all_official();
if !items.is_empty() {
return items;
}
super::persist::load_from_disk(path);
all_official()
}
#[cfg(test)]
mod tests {
#[test]
fn search_official_empty_query_returns_empty() {
if let Ok(mut g) = super::idx().write() {
g.pkgs = vec![crate::index::OfficialPkg {
name: "example".to_string(),
repo: "core".to_string(),
arch: "x86_64".to_string(),
version: "1.0".to_string(),
description: "desc".to_string(),
}];
}
let res = super::search_official(" ", false);
assert!(res.is_empty());
}
#[test]
fn search_official_is_case_insensitive_and_maps_fields() {
if let Ok(mut g) = super::idx().write() {
g.pkgs = vec![
crate::index::OfficialPkg {
name: "PacSea".to_string(),
repo: "core".to_string(),
arch: "x86_64".to_string(),
version: "1.2.3".to_string(),
description: "awesome".to_string(),
},
crate::index::OfficialPkg {
name: "other".to_string(),
repo: "extra".to_string(),
arch: "any".to_string(),
version: "0.1".to_string(),
description: "meh".to_string(),
},
];
}
let res = super::search_official("pac", false);
assert_eq!(res.len(), 1);
let (item, _) = &res[0];
assert_eq!(item.name, "PacSea");
assert_eq!(item.version, "1.2.3");
assert_eq!(item.description, "awesome");
match &item.source {
crate::state::Source::Official { repo, arch } => {
assert_eq!(repo, "core");
assert_eq!(arch, "x86_64");
}
crate::state::Source::Aur => panic!("expected Source::Official"),
}
}
#[test]
fn all_official_returns_all_items() {
if let Ok(mut g) = super::idx().write() {
g.pkgs = vec![
crate::index::OfficialPkg {
name: "aa".to_string(),
repo: "core".to_string(),
arch: "x86_64".to_string(),
version: "1".to_string(),
description: "A".to_string(),
},
crate::index::OfficialPkg {
name: "zz".to_string(),
repo: "extra".to_string(),
arch: "any".to_string(),
version: "2".to_string(),
description: "Z".to_string(),
},
];
}
let items = super::all_official();
assert_eq!(items.len(), 2);
let mut names: Vec<String> = items.into_iter().map(|p| p.name).collect();
names.sort();
assert_eq!(names, vec!["aa", "zz"]);
}
#[tokio::test]
async fn all_official_or_fetch_reads_from_disk_when_empty() {
use std::path::PathBuf;
if let Ok(mut g) = super::idx().write() {
g.pkgs.clear();
}
let mut path: PathBuf = std::env::temp_dir();
path.push(format!(
"pacsea_idx_query_fetch_{}_{}.json",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("System time is before UNIX epoch")
.as_nanos()
));
let idx_json = serde_json::json!({
"pkgs": [
{"name": "foo", "repo": "core", "arch": "x86_64", "version": "1", "description": ""}
]
});
std::fs::write(
&path,
serde_json::to_string(&idx_json).expect("failed to serialize index JSON"),
)
.expect("failed to write index JSON file");
let items = super::all_official_or_fetch(&path);
assert_eq!(items.len(), 1);
assert_eq!(items[0].name, "foo");
let _ = std::fs::remove_file(&path);
}
#[test]
fn search_official_fuzzy_vs_normal() {
if let Ok(mut g) = super::idx().write() {
g.pkgs = vec![
crate::index::OfficialPkg {
name: "ripgrep".to_string(),
repo: "core".to_string(),
arch: "x86_64".to_string(),
version: "1.0".to_string(),
description: "fast grep".to_string(),
},
crate::index::OfficialPkg {
name: "other".to_string(),
repo: "extra".to_string(),
arch: "any".to_string(),
version: "0.1".to_string(),
description: "meh".to_string(),
},
];
}
let res_normal = super::search_official("rg", false);
assert_eq!(res_normal.len(), 0);
let res_fuzzy = super::search_official("rg", true);
assert_eq!(res_fuzzy.len(), 1);
let (item, score) = &res_fuzzy[0];
assert_eq!(item.name, "ripgrep");
assert!(score.is_some());
let res_normal2 = super::search_official("rip", false);
assert_eq!(res_normal2.len(), 1);
let res_fuzzy2 = super::search_official("rip", true);
assert_eq!(res_fuzzy2.len(), 1);
}
}