use crate::types::index::{IndexQueryResult, OfficialIndex, OfficialPackage};
#[must_use]
pub fn search_official(index: &OfficialIndex, query: &str, fuzzy: bool) -> Vec<IndexQueryResult> {
let ql = query.trim();
if ql.is_empty() {
return Vec::new();
}
let mut results = Vec::new();
#[cfg(feature = "fuzzy-search")]
let fuzzy_matcher = if fuzzy {
Some(fuzzy_matcher::skim::SkimMatcherV2::default())
} else {
None
};
#[cfg(not(feature = "fuzzy-search"))]
let _ = fuzzy; #[cfg(not(feature = "fuzzy-search"))]
let use_fuzzy = false;
#[cfg(feature = "fuzzy-search")]
let use_fuzzy = fuzzy;
for pkg in &index.pkgs {
let match_score = if use_fuzzy {
#[cfg(feature = "fuzzy-search")]
{
fuzzy_matcher.as_ref().and_then(|m| {
use fuzzy_matcher::FuzzyMatcher;
m.fuzzy_match(&pkg.name, ql)
})
}
#[cfg(not(feature = "fuzzy-search"))]
{
None
}
} else {
let name_lower = pkg.name.to_lowercase();
let query_lower = ql.to_lowercase();
if name_lower.contains(&query_lower) {
Some(0) } else {
None
}
};
if let Some(score) = match_score {
results.push(IndexQueryResult {
package: pkg.clone(),
fuzzy_score: if use_fuzzy { Some(score) } else { None },
});
}
}
if use_fuzzy {
results.sort_by(|a, b| {
b.fuzzy_score
.cmp(&a.fuzzy_score)
.then_with(|| a.package.name.cmp(&b.package.name))
});
}
results
}
#[must_use]
pub fn all_official(index: &OfficialIndex) -> Vec<OfficialPackage> {
index.pkgs.clone()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::index::OfficialIndex;
fn create_test_index() -> OfficialIndex {
let mut index = OfficialIndex {
pkgs: vec![
OfficialPackage {
name: "ripgrep".to_string(),
repo: "extra".to_string(),
arch: "x86_64".to_string(),
version: "14.0.0".to_string(),
description: "Fast grep tool".to_string(),
},
OfficialPackage {
name: "vim".to_string(),
repo: "extra".to_string(),
arch: "x86_64".to_string(),
version: "9.0".to_string(),
description: "Text editor".to_string(),
},
OfficialPackage {
name: "pacman".to_string(),
repo: "core".to_string(),
arch: "x86_64".to_string(),
version: "6.1.0".to_string(),
description: "Package manager".to_string(),
},
],
name_to_idx: std::collections::HashMap::new(),
};
index.rebuild_name_index();
index
}
#[test]
fn search_official_empty_query_returns_empty() {
let index = create_test_index();
assert!(search_official(&index, "", false).is_empty());
assert!(search_official(&index, " ", false).is_empty());
assert!(search_official(&index, "\t\n", false).is_empty());
}
#[test]
fn search_official_case_insensitive_substring() {
let index = create_test_index();
let results_lower = search_official(&index, "vim", false);
let results_upper = search_official(&index, "VIM", false);
let results_mixed = search_official(&index, "ViM", false);
assert_eq!(results_lower.len(), 1);
assert_eq!(results_upper.len(), 1);
assert_eq!(results_mixed.len(), 1);
assert_eq!(results_lower[0].package.name, "vim");
}
#[test]
fn search_official_partial_match() {
let index = create_test_index();
let results = search_official(&index, "rip", false);
assert_eq!(results.len(), 1);
assert_eq!(results[0].package.name, "ripgrep");
assert_eq!(results[0].fuzzy_score, None); }
#[test]
#[cfg(feature = "fuzzy-search")]
fn search_official_fuzzy_match() {
let index = create_test_index();
let substring_results = search_official(&index, "rg", false);
let fuzzy_results = search_official(&index, "rg", true);
assert_eq!(substring_results.len(), 0); assert_eq!(fuzzy_results.len(), 1); assert_eq!(fuzzy_results[0].package.name, "ripgrep");
assert!(fuzzy_results[0].fuzzy_score.is_some()); }
#[test]
#[cfg(not(feature = "fuzzy-search"))]
fn search_official_fuzzy_fallback() {
let index = create_test_index();
let results = search_official(&index, "rip", true);
assert_eq!(results.len(), 1);
assert_eq!(results[0].package.name, "ripgrep");
}
#[test]
fn all_official_returns_all_packages() {
let index = create_test_index();
let all = all_official(&index);
assert_eq!(all.len(), 3);
let names: Vec<String> = all.iter().map(|p| p.name.clone()).collect();
assert!(names.contains(&"ripgrep".to_string()));
assert!(names.contains(&"vim".to_string()));
assert!(names.contains(&"pacman".to_string()));
}
#[test]
fn all_official_empty_index() {
let index = OfficialIndex::default();
let all = all_official(&index);
assert!(all.is_empty());
}
}