diff --git a/Cargo.lock b/Cargo.lock index 3c348c0..59c3800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,7 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", + "trigram", ] [[package]] @@ -1759,6 +1760,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trigram" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324350103581dfc6869f2c9399c019f394ac49a7ab16af23206add07851c5347" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "try-lock" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index ccb105a..0e796ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ alpm = "2.2.1" alpm-utils = "2.0.0" pacmanconf = "2.0.0" chrono = "0.4.22" +trigram = "0.4.4" clap = { version = "3.2.17", features = [ "derive", "wrap_help" ] } regex = { version = "1.6.0", default-features = false, features = [ "std", "unicode-perl" ] } colored = "2.0.0" diff --git a/src/main.rs b/src/main.rs index fc17039..1b4ffc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ use logging::init_logger; async fn main() { color_eyre::install().unwrap(); if unsafe { libc::geteuid() } == 0 { - crash!( AppExitCode::RunAsRoot, "Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions"); + crash!( AppExitCode::RunAsRoot, "Running amethyst as root is disall&owed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions"); } let args: Args = Args::parse(); @@ -134,8 +134,17 @@ async fn cmd_search(args: SearchArgs, options: Options) { tracing::info!("No results found"); } else { tracing::info!("Results:"); + + results.sort_by(|a, b | { + let a_score = a.score(&query_string); + let b_score = b.score(&query_string); + + b_score.partial_cmp(&a_score).unwrap_or(std::cmp::Ordering::Equal) + }); + let list: Vec = results.iter().map(|x| x.to_print_string()).collect(); get_logger().print_list(&list, "\n", 0); + if list.join("\n").lines().count() > crossterm::terminal::size().unwrap().1 as usize { page_string(&list.join("\n")).silent_unwrap(AppExitCode::Other); } diff --git a/src/operations/search.rs b/src/operations/search.rs index f72f4bc..b139d5e 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -14,27 +14,53 @@ use aur_rpc::SearchField; use chrono::Local; use chrono::TimeZone; use colored::Colorize; +use trigram::similarity; #[derive(Debug)] pub struct PackageSearchResult { pub repo: String, pub name: String, pub version: String, - pub group: Option, + pub groups: Option>, pub out_of_date: Option, + pub installed: bool, pub description: String, } +impl PackageSearchResult { + pub fn score(&self, query: &str) -> f32 { + similarity(query, &self.name) + } +} + impl Display for PackageSearchResult { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let repo = &self.repo; let name = &self.name; let version = &self.version; - let group = self.group.clone().unwrap_or_default(); - let out_of_date = self.out_of_date.unwrap_or(0); - let description = &self.description; + let groups = if self.groups.is_some() { + format!(" ({})", self.groups.clone().unwrap().join(", ")) + } else { + "".to_string() + }; + let out_of_date = if self.out_of_date.is_some() { + format!( + " [out of date: since {}]", + Local + .timestamp(self.out_of_date.unwrap().try_into().unwrap(), 0) + .date_naive() + ) + } else { + "".to_string() + }; + let installed = if self.installed { + " [installed]".to_string() + } else { + "".to_string() + }; + let description = wrap_text(&self.description, 4).join("\n"); - format!("{repo}/{name} {version} {group} {out_of_date}\n {description}").fmt(f) + format!("{repo}{name} {version}{groups}{out_of_date}{installed}\n {description}").fmt(f) } } @@ -47,14 +73,16 @@ impl Printable for PackageSearchResult { }; let name = &self.name.bold(); let version = &self.version.bold().green(); - let group = if self.group.is_some() { - format!("({})", self.group.clone().unwrap()).bold().blue() + let groups = if self.groups.is_some() { + format!(" ({})", self.groups.clone().unwrap().join(", ")) } else { - "".bold() - }; + "".to_string() + } + .bold() + .blue(); let out_of_date = if self.out_of_date.is_some() { format!( - "[out of date: since {}]", + " [out of date: since {}]", Local .timestamp(self.out_of_date.unwrap().try_into().unwrap(), 0) .date_naive() @@ -64,9 +92,16 @@ impl Printable for PackageSearchResult { } .bold() .red(); + let installed = if self.installed { + " [installed]".to_string() + } else { + "".to_string() + } + .bold() + .cyan(); let description = wrap_text(&self.description, 4).join("\n"); - format!("{repo}{name} {version} {group} {out_of_date}\n {description}") + format!("{repo}{name} {version}{groups}{out_of_date}{installed}\n {description}") } } @@ -76,6 +111,8 @@ pub async fn aur_search( by_field: Option, options: Options, ) -> Vec { + let alpm = get_handler().unwrap(); + let local = alpm.localdb(); let packages = rpcsearch(query.to_string(), by_field.map(SearchBy::into)) .await .silent_unwrap(AppExitCode::RpcError); @@ -88,16 +125,18 @@ pub async fn aur_search( .map(|package| { let name = package.name; let version = package.version; - let group = None; + let groups = None; let out_of_date = package.out_of_date; + let installed = local.pkg(&*name).is_ok(); let description = package.description; PackageSearchResult { repo: "aur".to_string(), name, version, - group, + groups, out_of_date, + installed, description: description.unwrap_or_else(|| "No description".to_string()), } }) @@ -109,6 +148,7 @@ pub async fn aur_search( #[tracing::instrument(level = "trace")] pub async fn repo_search(query: &str, options: Options) -> Vec { let alpm = get_handler().unwrap(); + let local = alpm.localdb(); let dbs = alpm.syncdbs(); let mut results = Vec::new(); @@ -118,16 +158,18 @@ pub async fn repo_search(query: &str, options: Options) -> Vec