diff --git a/src/lib.rs b/src/lib.rs index 2e9d4a6..87a9fc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::ffi::OsString; +use std::error::Error; pub mod pacman; @@ -7,12 +7,35 @@ pub struct Package { pub name: String, pub version: String, pub description: Option, + pub installed: Option, +} + +impl std::fmt::Display for Package { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let end = "\x1b[0m"; + let bold = "\x1b[1m"; + let green = "\x1b[32m"; + let magenta = "\x1b[35m"; + let cyan = "\x1b[36m"; + + let installed = self.installed.unwrap_or(false); + let installed_string = if installed { "[installed] " } else { "" }; + + let repository_string = self.repository.join(" / "); + + let description_string = self + .description + .clone() + .unwrap_or("No description provided".to_string()); + + write!( + f, + "{bold}{} {green}{} {cyan}{}{magenta}{}{end}\n {}", + self.name, self.version, installed_string, repository_string, description_string + ) + } } pub trait Manager { - fn command_name(&self) -> OsString; - fn test_command(&mut self) -> Result<(), String> - where - Self: Sized; - fn remote_search(&self, query: &str) -> Result, &str>; + fn remote_search(query: &str) -> Result, Box>; } diff --git a/src/main.rs b/src/main.rs index 2007038..3f08ddc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,11 @@ -use package_manager::{Manager, pacman::*}; +use package_manager::{Manager, pacman::Pacman}; +use std::env; fn main() { - let m = Pacman; - print!("{:?}", m.manager_path().unwrap()); + let arg = env::args().nth(1).unwrap(); + let mut packages = Pacman::remote_search(&arg).unwrap(); + packages.sort_by(|p1, p2| p1.name.cmp(&p2.name)); + for package in packages { + println!("{}", package); + } } diff --git a/src/pacman/mod.rs b/src/pacman/mod.rs index 499c20b..1f736fa 100644 --- a/src/pacman/mod.rs +++ b/src/pacman/mod.rs @@ -1,35 +1,88 @@ use crate::*; -use std::str::FromStr; -use std::{ffi::OsString, process::Command}; +use std::{collections::HashMap, process::Command}; -pub struct Pacman { - command_exists: bool, +struct UnmergedPackage { + repository: String, + name: String, + version: String, + description: Option, + installed: Option, } +pub struct Pacman; + impl Manager for Pacman { - fn command_name(&self) -> OsString { - return OsString::from_str("pacman").unwrap(); - } - fn test_command(&mut self) -> Result<(), String> - where - Self: Sized { - if self.command_exists { return Ok(()); } - let output = Command::new("which").arg(self.command_name()).output(); - match output { - Ok(output) => { - if output.status.success() { - self.command_exists = true; - return Ok(()); - } else { - let err = format!("{} could not be found in path", self.command_name().to_string_lossy()); - return Err(err); - } - } - Err(_) => return Err("Existence check could not be run.".to_string()), + fn remote_search(query: &str) -> Result, Box> { + let output = Command::new("pacman").arg("-Ss").arg(query).output()?; + let stdout = String::from_utf8(output.stdout)?; + let lines: Vec = stdout.lines().map(|s| s.to_string()).collect(); + + let mut unmerged_packages = Vec::new(); + let mut i = 0; + while i + 1 < lines.len() { + let start_line = &lines[i]; + let description_line = &lines[i + 1]; + + // split into repo + name, version, and install + let parts: Vec<&str> = start_line.split_whitespace().collect(); + let repo_name = parts[0]; + let version = parts[1].to_string(); + let installed = Some((parts.len() > 2) && parts[2].starts_with("[installed")); + + // split repo + name into repo and name + let repo_name_parts: Vec<&str> = repo_name.split("/").collect(); + let repository = repo_name_parts[0].to_string(); + let name = repo_name_parts[1].to_string(); + + // strip description + let description = Some(description_line.trim().to_string()); + + // create package + let package = UnmergedPackage { + repository, + name, + version, + description, + installed, + }; + unmerged_packages.push(package); + + i += 2; } - } - fn remote_search(&self, query: &str) -> Result, &str> { - if self.command_exists() - let output = Command::new + Ok(merge_packages(unmerged_packages)) } } + +fn process_version(version: &str) -> String { + if version.ends_with(".1") { + version[..version.len() - 2].to_string() + } else { + version.to_string() + } +} + +fn merge_packages(unmerged: Vec) -> Vec { + let mut groups: HashMap> = HashMap::new(); + for package in unmerged { + groups + .entry(package.name.clone()) + .or_insert_with(Vec::new) + .push(package); + } + + let mut result = Vec::new(); + for (name, packages) in groups { + let processed_version = process_version(&packages[0].version); + let repositories: Vec = packages.iter().map(|p| p.repository.clone()).collect(); + let description = packages[0].description.clone(); + let installed = packages[0].installed; + result.push(Package { + name, + version: processed_version, + repository: repositories, + description, + installed, + }); + } + result +}