From bc80df4946ecb9b3e30f29ec43193d75352a6427 Mon Sep 17 00:00:00 2001 From: Michal S Date: Sun, 4 Sep 2022 21:36:03 +0100 Subject: [PATCH] Fix searching (both AUR and repo) Add Printable trait Improve wrapping --- .envrc | 2 +- .gitignore | 17 ++-- Cargo.lock | 126 ++++++++++++++++++++++- Cargo.toml | 4 + flake.lock | 7 +- flake.nix | 8 +- src/interact/theme.rs | 6 +- src/internal/alpm.rs | 30 ++++++ src/internal/mod.rs | 1 + src/internal/utils.rs | 12 +-- src/logging/handler.rs | 4 +- src/logging/mod.rs | 3 + src/logging/printable.rs | 7 ++ src/main.rs | 27 +++-- src/operations/aur_install/common.rs | 2 +- src/operations/search.rs | 144 ++++++++++++++++++++++----- src/operations/uninstall.rs | 1 + 17 files changed, 342 insertions(+), 59 deletions(-) create mode 100644 src/internal/alpm.rs create mode 100644 src/logging/printable.rs diff --git a/.envrc b/.envrc index 8392d15..c79d6d7 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake \ No newline at end of file +use flake || use nix shell.nix diff --git a/.gitignore b/.gitignore index 9b9f24f..e8f7b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ -target/ -.vscode -.idea -result -.DS_Store -.direnv \ No newline at end of file +# Rust +/target/ +**/*.rs.bk + +# Nix +/result* +/nix/result* + +# Direnv +/.direnv +/.envrc diff --git a/Cargo.lock b/Cargo.lock index 516604a..beea940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,8 +6,11 @@ version = 3 name = "Amethyst" version = "4.0.0" dependencies = [ + "alpm", + "alpm-utils", "async-recursion", "aur-rpc", + "chrono", "clap", "clap_complete", "color-eyre", @@ -23,6 +26,7 @@ dependencies = [ "lazy_static", "libc", "native-tls", + "pacmanconf", "parking_lot", "regex", "serde", @@ -58,6 +62,44 @@ dependencies = [ "memchr", ] +[[package]] +name = "alpm" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bedac9e074b9368777268cff76c02d8b26d3e567642a52379aadd5c5e204e120" +dependencies = [ + "alpm-sys", + "bitflags", +] + +[[package]] +name = "alpm-sys" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee1afe3484685ecedf03aefd7904428befed7815687e48aa4b21b3417eeecf0" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "alpm-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c6892a7ced000868007e9ee99a80680c96f5b3e535e9ade81a54501e391232" +dependencies = [ + "alpm", + "pacmanconf", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -158,6 +200,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "cini" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628d1f5b9a7b1196ce1aa660e3ba7e2559d350649cbe94993519c127df667f2" + [[package]] name = "clap" version = "3.2.20" @@ -510,7 +573,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -630,6 +693,20 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + [[package]] name = "idna" version = "0.2.3" @@ -797,7 +874,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -819,6 +896,25 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -907,6 +1003,15 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "pacmanconf" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba64fdfa5d4589657dc8c8497b67322d8a64c162357ea0359656bfe23eec6185" +dependencies = [ + "cini", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1367,6 +1472,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1607,6 +1723,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 706b34f..e981cb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,10 @@ codegen-units = 1 opt-level = 0 [dependencies] +alpm = "2.2.1" +alpm-utils = "2.0.0" +pacmanconf = "2.0.0" +chrono = "0.4.22" 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/flake.lock b/flake.lock index ecc0953..d95ea01 100644 --- a/flake.lock +++ b/flake.lock @@ -22,16 +22,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1662096612, - "narHash": "sha256-R+Q8l5JuyJryRPdiIaYpO5O3A55rT+/pItBrKcy7LM4=", + "lastModified": 1662309212, + "narHash": "sha256-gTDq4vSC4XUr5UDlmdsTYCJNeE1JNWWvQB8ViiNdyFI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "21de2b973f9fee595a7a1ac4693efff791245c34", + "rev": "1ed43116cdb9670e9241138996a2c72d357de70e", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 9b016b0..3dd6db2 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + nixpkgs.url = "github:nixos/nixpkgs"; utils.url = "github:numtide/flake-utils"; naersk = { url = "github:nix-community/naersk"; @@ -40,7 +40,13 @@ cargo-audit rustfmt clippy + + # For `alpm` libs + pkg-config + pacman + openssl ]; + # For rust-analyzer RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; }; diff --git a/src/interact/theme.rs b/src/interact/theme.rs index c019921..017c32d 100644 --- a/src/interact/theme.rs +++ b/src/interact/theme.rs @@ -17,7 +17,7 @@ impl AmeTheme { impl Theme for AmeTheme { fn format_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result { - let prompt = wrap_text(prompt).join("\n "); + let prompt = wrap_text(prompt, 4).join("\n"); write!(f, "{} {}:", PROMPT_SYMBOL.magenta(), prompt.bold()) } @@ -31,7 +31,7 @@ impl Theme for AmeTheme { prompt: &str, default: Option, ) -> std::fmt::Result { - let prompt = wrap_text(prompt).join("\n "); + let prompt = wrap_text(prompt, 4).join("\n"); if !prompt.is_empty() { write!(f, "{} {} ", PROMPT_SYMBOL.magenta(), &prompt.bold())?; } @@ -49,7 +49,7 @@ impl Theme for AmeTheme { prompt: &str, selection: Option, ) -> std::fmt::Result { - let prompt = wrap_text(prompt).join("\n "); + let prompt = wrap_text(prompt, 4).join("\n"); let selection = selection.map(|b| if b { "yes" } else { "no" }); match selection { diff --git a/src/internal/alpm.rs b/src/internal/alpm.rs new file mode 100644 index 0000000..d62b466 --- /dev/null +++ b/src/internal/alpm.rs @@ -0,0 +1,30 @@ +use std::path::Path; + +use alpm::Alpm; +use alpm_utils::alpm_with_conf; +use pacmanconf::Config; + +#[derive(Debug)] +pub enum Error { + Alpm(alpm::Error), + Pacmanconf(pacmanconf::Error), +} + +impl From for Error { + fn from(err: alpm::Error) -> Self { + Error::Alpm(err) + } +} + +impl From for Error { + fn from(err: pacmanconf::Error) -> Self { + Error::Pacmanconf(err) + } +} + +pub fn get_handler() -> Result { + let config = Config::from_file(Path::new("/etc/pacman.conf"))?; + let alpm = alpm_with_conf(&config)?; + + Ok(alpm) +} diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 81b1d0c..f8bc079 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -17,6 +17,7 @@ mod sort; pub mod structs; #[macro_use] pub mod utils; +pub mod alpm; mod sudoloop; #[macro_export] diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 2d1b640..4ed631a 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -63,14 +63,12 @@ fn create_if_not_exist(dir: &Path) -> &Path { dir } -pub fn wrap_text>(s: S) -> Vec { - wrap(s.as_ref(), get_wrap_options()) +pub fn wrap_text>(s: S, padding: usize) -> Vec { + let subsequent_padding = " ".repeat(padding); + let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - padding) + .subsequent_indent(&subsequent_padding); + wrap(s.as_ref(), opts) .into_iter() .map(String::from) .collect() } - -fn get_wrap_options() -> textwrap::Options<'static> { - textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" ") -} diff --git a/src/logging/handler.rs b/src/logging/handler.rs index 0e9ff6b..7c5f0d3 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -99,7 +99,7 @@ impl LogHandler { format!("{}{}{}", acc, separator, line) }); - let lines = wrap_text(lines).join("\n"); + let lines = wrap_text(lines, 2).join("\n"); self.log(lines) } @@ -214,7 +214,7 @@ impl LogHandler { fn preformat_msg(&self, msg: String) -> String { let msg = self.apply_uwu(msg); - wrap_text(msg).join("\n") + wrap_text(msg, 2).join("\n") } fn apply_uwu(&self, msg: String) -> String { diff --git a/src/logging/mod.rs b/src/logging/mod.rs index 16bf270..bce0f7d 100644 --- a/src/logging/mod.rs +++ b/src/logging/mod.rs @@ -1,5 +1,8 @@ use std::sync::Arc; +mod printable; +pub use printable::Printable; + use lazy_static::lazy_static; use tracing::Level; use tracing_error::ErrorLayer; diff --git a/src/logging/printable.rs b/src/logging/printable.rs new file mode 100644 index 0000000..e890079 --- /dev/null +++ b/src/logging/printable.rs @@ -0,0 +1,7 @@ +use std::fmt::Display; + +pub trait Printable: Display { + fn to_print_string(&self) -> String { + self.to_string() + } +} diff --git a/src/main.rs b/src/main.rs index 70a703f..863ed9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,8 @@ use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; use crate::internal::detect; use crate::internal::exit_code::AppExitCode; use crate::internal::{sort, start_sudoloop, structs::Options}; +use crate::logging::Printable; + use clap_complete::{Generator, Shell}; use std::str::FromStr; @@ -108,19 +110,28 @@ async fn cmd_remove(args: RemoveArgs, options: Options) { async fn cmd_search(args: SearchArgs, options: Options) { let query_string = args.search; - if args.aur { + let mut results = Vec::new(); + + let both = !args.aur && !args.repo; + let aur = args.aur || both; + let repo = args.repo || both; + + if aur { tracing::info!("Searching AUR for {}", &query_string); - operations::aur_search(&query_string, args.by, options).await; + let res = operations::aur_search(&query_string, args.by, options).await; + results.extend(res); } - if args.repo { + if repo { tracing::info!("Searching repos for {}", &query_string); - operations::search(&query_string, options).await; + let res = operations::search(&query_string, options).await; + results.extend(res); } - if !args.aur && !args.repo { - tracing::info!("Searching AUR and repos for {}", &query_string); - operations::search(&query_string, options).await; - operations::aur_search(&query_string, args.by, options).await; + if results.is_empty() { + tracing::info!("No results found"); + } else { + tracing::info!("Results:"); + } } diff --git a/src/operations/aur_install/common.rs b/src/operations/aur_install/common.rs index 2f7c27f..067a1c7 100644 --- a/src/operations/aur_install/common.rs +++ b/src/operations/aur_install/common.rs @@ -258,7 +258,7 @@ async fn show_and_log_stdio( let _ = out_writer.write(&[b'\n']).await?; tracing::trace!("{package_name}: {line}"); let line = format!("{}: {}", package_name.clone().bold(), line); - let lines = wrap_text(line); + let lines = wrap_text(line, 2); let line = lines.into_iter().next().unwrap(); pb.set_message(line); } diff --git a/src/operations/search.rs b/src/operations/search.rs index a3a4f51..1d3f75f 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -1,51 +1,147 @@ +use std::fmt::Display; +use std::fmt::Formatter; use std::str::FromStr; -use crate::internal::commands::ShellCommand; +use crate::internal::alpm::get_handler; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcsearch; +use crate::internal::utils::wrap_text; +use crate::logging::Printable; use crate::Options; + use aur_rpc::SearchField; +use chrono::Local; +use chrono::TimeZone; +use colored::Colorize; + +#[derive(Debug)] +pub struct PackageSearchResult { + pub repo: String, + pub name: String, + pub version: String, + pub group: Option, + pub out_of_date: Option, + pub description: String, +} + +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; + + format!("{repo}/{name} {version} {group} {out_of_date}\n {description}").fmt(f) + } +} + +impl Printable for PackageSearchResult { + fn to_print_string(&self) -> String { + let repo = if &self.repo == "aur" { + (self.repo.clone() + "/").bold().cyan() + } else { + (self.repo.clone() + "/").bold().purple() + }; + 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() + } else { + "".bold() + }; + 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() + ) + .bold() + .red() + } else { + "".bold() + }; + let description = wrap_text(&self.description, 4).join("\n"); + + format!("{repo}{name} {version} {group} {out_of_date}\n {description}") + } +} #[tracing::instrument(level = "trace")] -pub async fn aur_search(query: &str, by_field: Option, options: Options) { +pub async fn aur_search( + query: &str, + by_field: Option, + options: Options, +) -> Vec { let packages = rpcsearch(query.to_string(), by_field.map(SearchBy::into)) .await .silent_unwrap(AppExitCode::RpcError); let total_results = packages.len(); - for package in &packages { - let description = if let Some(desc) = &package.description { - desc - } else { - "" - }; - println!( - "aur/{} {}\n {}", - package.name, package.version, description - ) - } - tracing::debug!("Found {total_results} resuls for \"{query}\" in AUR",); + + let results: Vec = packages + .into_iter() + .map(|package| { + let name = package.name; + let version = package.version; + let group = None; + let out_of_date = package.out_of_date; + let description = package.description; + + PackageSearchResult { + repo: "aur".to_string(), + name, + version, + group, + out_of_date, + description: description.unwrap_or_else(|| "No description".to_string()), + } + }) + .collect(); + + results } #[tracing::instrument(level = "trace")] -pub async fn repo_search(query: &str, options: Options) { - let output = ShellCommand::pacman() - .arg("-Ss") - .arg(query) - .wait_with_output() - .await - .silent_unwrap(AppExitCode::PacmanError) - .stdout; +pub async fn repo_search(query: &str, options: Options) -> Vec { + let alpm = get_handler().unwrap(); + let dbs = alpm.syncdbs(); + + let mut results = Vec::new(); + for db in dbs { + let packages = db.search(vec![query].iter()).unwrap(); + + for package in packages { + let name = package.name(); + let version = package.version(); + let description = package.desc(); + let group = package.groups().first().map(|s| s.to_string()); + let out_of_date = None; + + let result = PackageSearchResult { + repo: db.name().to_string(), + name: name.to_string(), + version: version.to_string(), + group, + out_of_date, + description: description.unwrap_or("No description").to_string(), + }; + + results.push(result); + } + } tracing::debug!( "Found {} results for \"{}\" in repos", - &output.split('\n').count() / 2, + &results.len(), &query ); - println!("{}", output) + results } /// Represents a field to search by diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index fe89e3d..d832a2a 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -7,6 +7,7 @@ use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::Options; +/// Uninstalls the given packages #[tracing::instrument(level = "trace")] pub async fn uninstall(packages: Vec, options: Options) { let mut pacman_args = vec!["-Rs"];