From c4e099d01a25543e2199062ade280d9215135744 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sat, 17 Oct 2020 00:08:42 +0800 Subject: [PATCH 01/28] refactor: rewrite in async rust, part 1 --- Cargo.lock | 122 +++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 +- src/exec.rs | 95 ++++++++++++++++++++++++++++------------ src/lib.rs | 3 ++ 4 files changed, 187 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a9c2eccb98..aeedacdc930 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + [[package]] name = "arrayref" version = "0.3.6" @@ -61,6 +67,12 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + [[package]] name = "cfg-if" version = "0.1.10" @@ -250,6 +262,48 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "mio" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53a6ea5f38c0a48ca42159868c6d8e1bd56c0451238856cc08d58563643bdc3" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "os_str_bytes" version = "2.3.2" @@ -268,10 +322,16 @@ dependencies = [ "lazy_static", "regex", "serde", - "subprocess", + "tokio", "which", ] +[[package]] +name = "pin-project-lite" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -382,21 +442,39 @@ dependencies = [ ] [[package]] -name = "strsim" -version = "0.10.0" +name = "signal-hook-registry" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" +dependencies = [ + "arc-swap", + "libc", +] [[package]] -name = "subprocess" -version = "0.2.6" +name = "slab" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b9ad6c3e1b525a55872a4d2f2d404b3c959b7bbcbfd83c364580f68ed157bd" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "socket2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" dependencies = [ + "cfg-if", "libc", + "redox_syscall", "winapi", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.43" @@ -455,6 +533,36 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tokio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7137dbb0abee577362ccdc7df21605cfcbb949243aeab47dac9ea6ef7d830e21" +dependencies = [ + "bytes", + "lazy_static", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48caa7b66c7a6ec943edf78d21a594fbeb24e536c781da67d5c32edec54103f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.5.6" diff --git a/Cargo.toml b/Cargo.toml index 7bc5913886b..38bcb1ead44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ is-root = "0.1.2" lazy_static = "1.4.0" regex = "1.4.1" serde = {version = "1.0.116", features = ["derive"]} -subprocess = "0.2.6" +# subprocess = "0.2.6" +tokio = {version = "0.3.0", features = ["io-std", "io-util", "macros", "process", "rt-multi-thread"]} which = "4.0.2" [profile.release] diff --git a/src/exec.rs b/src/exec.rs index bb6845cf3e8..038ee24970d 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -3,9 +3,11 @@ use crate::print::*; pub use is_root::is_root; use regex::Regex; use std::ffi::OsStr; -use std::io::{BufReader, Read, Write}; +use std::process::Stdio; use std::sync::Mutex; -use subprocess::{Exec, Redirection}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; +use tokio::process::Command as Exec; +use tokio::select; /// Different ways in which a command shall be dealt with. #[derive(Copy, Clone, Debug)] @@ -72,17 +74,17 @@ impl> Cmd { pub fn build(self) -> Exec { // * We use `sudo -S` to launch subprocess if `sudo` is `true` and the current user is not `root`. let builder = if self.sudo && !is_root() { - Exec::cmd("sudo").arg("-S").args(&self.cmd) + Exec::new("sudo").arg("-S").args(&self.cmd) } else { let (cmd, subcmd) = self .cmd .split_first() .expect("Failed to build Cmd, command is empty"); - Exec::cmd(cmd).args(subcmd) + Exec::new(cmd).args(subcmd) }; // ! Special fix for `zypper`: `zypper install -y curl` is accepted, // ! but not `zypper install curl -y.` - builder.args(&self.flags).args(&self.kws) + *(builder.args(&self.flags).args(&self.kws)) } } @@ -109,24 +111,54 @@ impl + AsRef> Cmd { } } + /// Helper function to write a string to a `String` and `stdout`. + async fn write(s: &str, mute: bool, mut out: V, mut stdout: W) -> tokio::io::Result<()> + where + V: AsyncWriteExt + Unpin, + W: AsyncWriteExt + Unpin, + { + let bytes = s.as_bytes(); + if mute { + out.write_all(bytes).await + } else { + try_join!(stdout.write_all(bytes), out.write_all(bytes))?; + Ok(()) + } + } + /// Execute a command and return its `stdout` and `stderr`. /// If `mute` is `false`, then its normal `stdout/stderr` will be printed in the console too. - fn exec_checkall(self, mute: bool) -> Result, Error> { - let stdout_reader = self + async fn exec_checkall(self, mute: bool) -> Result, Error> { + let mut child = self .build() - .stderr(Redirection::Merge) - .stream_stdout() - .map_err(|_| Error::from("Could not capture stdout, is the executable valid?")) - .map(BufReader::new)?; + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + let mut stdout_reader = child + .stdout + .take() + .map(|x| BufReader::new(x).lines()) + .ok_or_else(|| Error::from("Child did not have a handle to stdout"))?; + let mut stderr_reader = child + .stderr + .take() + .map(|x| BufReader::new(x).lines()) + .ok_or_else(|| Error::from("Child did not have a handle to stderr"))?; let mut out = Vec::::new(); - let mut stdout = std::io::stdout(); + let mut stdout = tokio::io::stdout(); - for mb in stdout_reader.bytes() { - let b = mb?; - out.write_all(&[b])?; - if !mute { - stdout.write_all(&[b])?; + loop { + select! { + ln = stdout_reader.next_line() => match ln? { + None => break, + Some(l) => Self::write(&l, mute, &mut out, &mut stdout).await?, + }, + ln = stderr_reader.next_line() => match ln? { + None => break, + Some(l) => Self::write(&l, mute, &mut out, &mut stdout).await?, + }, + else => continue, } } @@ -135,21 +167,28 @@ impl + AsRef> Cmd { /// Execute a command and collect its `stderr`. /// If `mute` is `false`, then its normal `stderr` will be printed in the console too. - fn exec_checkerr(self, mute: bool) -> Result, Error> { - let stderr_reader = self + async fn exec_checkerr(self, mute: bool) -> Result, Error> { + let mut child = self .build() - .stream_stderr() - .map_err(|_| Error::from("Could not capture stderr, is the executable valid?")) - .map(BufReader::new)?; + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + let mut stderr_reader = child + .stderr + .take() + .map(|x| BufReader::new(x).lines()) + .ok_or_else(|| Error::from("Child did not have a handle to stderr"))?; let mut out = Vec::::new(); - let mut stderr = std::io::stderr(); + let mut stderr = tokio::io::stderr(); - for mb in stderr_reader.bytes() { - let b = mb?; - out.write_all(&[b])?; - if !mute { - stderr.write_all(&[b])?; + loop { + select! { + ln = stderr_reader.next_line() => match ln? { + None => break, + Some(l) => Self::write(&l, mute, Pin::new(&mut out), Pin::new(&mut stderr)).await?, + }, + else => continue, } } diff --git a/src/lib.rs b/src/lib.rs index d383fb8c49e..4e1f27eeedc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,6 @@ pub mod print; #[macro_use] extern crate lazy_static; + +#[macro_use] +extern crate tokio; From 63346ef9f5809d606156970a2804c3c5cd3996fa Mon Sep 17 00:00:00 2001 From: rami3l Date: Sat, 17 Oct 2020 00:54:39 +0800 Subject: [PATCH 02/28] refactor: rewrite in async rust, part 2 --- src/exec.rs | 76 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/src/exec.rs b/src/exec.rs index 038ee24970d..ae557f9c48d 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -3,6 +3,7 @@ use crate::print::*; pub use is_root::is_root; use regex::Regex; use std::ffi::OsStr; +use std::io::Write; use std::process::Stdio; use std::sync::Mutex; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; @@ -31,6 +32,15 @@ pub enum Mode { Prompt, } +/// Representation of what a command returns. +#[derive(Debug, Clone, Default)] +pub struct Output { + /// The captured `stdout`, sometimes mixed with captured `stderr`. + contents: Vec, + /// `Some(n)` for exit code, `None` for signals. + code: Option, +} + /// A command to be executed, provided in `command-keywords-flags` form. /// For example, `[brew install]-[curl fish]-[--dry-run]`). #[derive(Debug, Clone, Default)] @@ -92,22 +102,22 @@ impl + AsRef> Cmd { /// Execute a command and return a `Result, _>`. /// The exact behavior depends on the `mode` passed in. /// See `exec::Mode`'s documentation for more info. - pub fn exec(self, mode: Mode) -> Result, Error> { + pub async fn exec(self, mode: Mode) -> Result { match mode { Mode::PrintCmd => { print_cmd(&self, PROMPT_CANCELED); - Ok(Vec::new()) + Ok(Default::default()) } - Mode::Mute => self.exec_checkall(true), + Mode::Mute => self.exec_checkall(true).await, Mode::CheckAll => { print_cmd(&self, PROMPT_RUN); - self.exec_checkall(false) + self.exec_checkall(false).await } Mode::CheckErr => { print_cmd(&self, PROMPT_RUN); - self.exec_checkerr(false) + self.exec_checkerr(false).await } - Mode::Prompt => self.exec_prompt(false), + Mode::Prompt => self.exec_prompt(false).await, } } @@ -128,7 +138,7 @@ impl + AsRef> Cmd { /// Execute a command and return its `stdout` and `stderr`. /// If `mute` is `false`, then its normal `stdout/stderr` will be printed in the console too. - async fn exec_checkall(self, mute: bool) -> Result, Error> { + async fn exec_checkall(self, mute: bool) -> Result { let mut child = self .build() .stdout(Stdio::piped()) @@ -145,6 +155,14 @@ impl + AsRef> Cmd { .map(|x| BufReader::new(x).lines()) .ok_or_else(|| Error::from("Child did not have a handle to stderr"))?; + let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { + let status = child + .wait() + .await + .map_err(|_| Error::from("Child encountered an error"))?; + Ok(status.code()) + }); + let mut out = Vec::::new(); let mut stdout = tokio::io::stdout(); @@ -162,12 +180,15 @@ impl + AsRef> Cmd { } } - Ok(out) + Ok(Output { + contents: out, + code: code.await.unwrap()?, + }) } /// Execute a command and collect its `stderr`. /// If `mute` is `false`, then its normal `stderr` will be printed in the console too. - async fn exec_checkerr(self, mute: bool) -> Result, Error> { + async fn exec_checkerr(self, mute: bool) -> Result { let mut child = self .build() .stdout(Stdio::piped()) @@ -179,6 +200,14 @@ impl + AsRef> Cmd { .map(|x| BufReader::new(x).lines()) .ok_or_else(|| Error::from("Child did not have a handle to stderr"))?; + let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { + let status = child + .wait() + .await + .map_err(|_| Error::from("Child encountered an error"))?; + Ok(status.code()) + }); + let mut out = Vec::::new(); let mut stderr = tokio::io::stderr(); @@ -186,20 +215,23 @@ impl + AsRef> Cmd { select! { ln = stderr_reader.next_line() => match ln? { None => break, - Some(l) => Self::write(&l, mute, Pin::new(&mut out), Pin::new(&mut stderr)).await?, + Some(l) => Self::write(&l, mute, &mut out, &mut stderr).await?, }, else => continue, } } - Ok(out) + Ok(Output { + contents: out, + code: code.await.unwrap()?, + }) } /// Execute a command and collect its `stderr`. /// If `mute` is `false`, then its normal `stderr` will be printed in the console too. /// The user will be prompted if (s)he wishes to continue with the command execution. #[allow(clippy::mutex_atomic)] - fn exec_prompt(self, mute: bool) -> Result, Error> { + async fn exec_prompt(self, mute: bool) -> Result { lazy_static! { static ref ALL_YES: Mutex = Mutex::new(false); } @@ -209,13 +241,15 @@ impl + AsRef> Cmd { true } else { print_cmd(&self, PROMPT_PENDING); - match prompt( - "Proceed", - "[Yes/all/no]", - &["", "y", "yes", "a", "all", "n", "no"], - false, - ) - .to_lowercase() + match tokio::task::block_in_place(move || { + prompt( + "Proceed", + "[Yes/all/no]", + &["", "y", "yes", "a", "all", "n", "no"], + false, + ) + .to_lowercase() + }) .as_ref() { // The default answer is `Yes` @@ -231,10 +265,10 @@ impl + AsRef> Cmd { } }; if !proceed { - return Ok(Vec::new()); + return Ok(Default::default()); } print_cmd(&self, PROMPT_RUN); - self.exec_checkerr(mute) + self.exec_checkerr(mute).await } } From 8a4f994deed702419dfbd949fd1ef960f410f6ce Mon Sep 17 00:00:00 2001 From: rami3l Date: Sat, 17 Oct 2020 21:37:47 +0800 Subject: [PATCH 03/28] refactor: add async main (unfinished) --- .vscode/settings.json | 1 + Cargo.lock | 14 +++++++++++++- Cargo.toml | 3 ++- src/main.rs | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6e5e2cbb4d7..97c2faff8c0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,7 @@ "nuget", "nupkg", "pacaptr", + "proto", "rdepends", "repoquery", "rmtree", diff --git a/Cargo.lock b/Cargo.lock index aeedacdc930..ca3e35aaa62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +[[package]] +name = "async-trait" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -312,8 +323,9 @@ checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac" [[package]] name = "pacaptr" -version = "0.6.1" +version = "0.7.0" dependencies = [ + "async-trait", "clap", "colored", "confy", diff --git a/Cargo.toml b/Cargo.toml index 38bcb1ead44..33cf64fa180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Rami3L "] description = "A pacman-like wrapper for many package managers." edition = "2018" name = "pacaptr" -version = "0.6.1" +version = "0.7.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -19,6 +19,7 @@ lazy_static = "1.4.0" regex = "1.4.1" serde = {version = "1.0.116", features = ["derive"]} # subprocess = "0.2.6" +async-trait = "0.1.41" tokio = {version = "0.3.0", features = ["io-std", "io-util", "macros", "process", "rt-multi-thread"]} which = "4.0.2" diff --git a/src/main.rs b/src/main.rs index a2d802c6260..7ad17b54d9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ use clap::Clap; use pacaptr::dispatch::Opt; use pacaptr::print::{print_err, PROMPT_ERROR}; -fn main() { +#[tokio::main] +async fn main() { let opt = Opt::parse(); if let Err(e) = opt.dispatch() { print_err(e, PROMPT_ERROR); From 22b0e1dcfee706712db192bbcbc3c98b3880fa50 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sat, 17 Oct 2020 22:26:42 +0800 Subject: [PATCH 04/28] refactor: try to rewrite `PackageManager` in async Rust (failed) --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/exec.rs | 4 +++- src/lib.rs | 3 --- src/package_manager/mod.rs | 27 +++++++++++++++++---------- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca3e35aaa62..292ed91236c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" + [[package]] name = "arc-swap" version = "0.4.7" @@ -325,6 +331,7 @@ checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac" name = "pacaptr" version = "0.7.0" dependencies = [ + "anyhow", "async-trait", "clap", "colored", diff --git a/Cargo.toml b/Cargo.toml index 33cf64fa180..712dc91f9a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ lazy_static = "1.4.0" regex = "1.4.1" serde = {version = "1.0.116", features = ["derive"]} # subprocess = "0.2.6" +anyhow = "1.0.33" async-trait = "0.1.41" tokio = {version = "0.3.0", features = ["io-std", "io-util", "macros", "process", "rt-multi-thread"]} which = "4.0.2" diff --git a/src/exec.rs b/src/exec.rs index ae557f9c48d..fce4c262e89 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -32,13 +32,15 @@ pub enum Mode { Prompt, } +pub type StatusCode = i32; + /// Representation of what a command returns. #[derive(Debug, Clone, Default)] pub struct Output { /// The captured `stdout`, sometimes mixed with captured `stderr`. contents: Vec, /// `Some(n)` for exit code, `None` for signals. - code: Option, + code: Option, } /// A command to be executed, provided in `command-keywords-flags` form. diff --git a/src/lib.rs b/src/lib.rs index 4e1f27eeedc..d383fb8c49e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,3 @@ pub mod print; #[macro_use] extern crate lazy_static; - -#[macro_use] -extern crate tokio; diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index f12b628d80e..319fe398e13 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -13,7 +13,10 @@ pub mod zypper; use crate::dispatch::config::Config; use crate::error::Error; -use crate::exec::{Cmd, Mode}; +use crate::exec::{Cmd, Mode, Output}; +use anyhow::Result; + +use async_trait::async_trait; macro_rules! make_pm {( $( @@ -22,7 +25,7 @@ macro_rules! make_pm {( ),* ) => { $( $(#[$meta] )* - fn $method(&self, _kws: &[&str], _flags: &[&str]) -> std::result::Result<(), crate::error::Error> { + async fn $method(&self, _kws: &[&str], _flags: &[&str]) -> anyhow::Result<()> { std::result::Result::Err(format!("Operation `{}` unimplemented for `{}`", stringify!($method), self.name()).into()) })* }; @@ -31,6 +34,7 @@ macro_rules! make_pm {( /// The behaviors of a Pack(age)Manager. /// For method explanation see: https://wiki.archlinux.org/index.php/Pacman/Rosetta /// and https://wiki.archlinux.org/index.php/Pacman +#[async_trait] pub trait PackageManager { /// Get the name of the package manager. fn name(&self) -> String; @@ -39,10 +43,10 @@ pub trait PackageManager { fn cfg(&self) -> Config; /// A helper method to simplify direct command invocation. - fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result, Error> { + async fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result { // `--dry-run` should apply to both the main command and the cleanup. let res = { - let body = |cmd: &Cmd| { + let body = |cmd: &Cmd| async { let mut curr_cmd = cmd.clone(); let no_confirm = self.cfg().no_confirm; if self.cfg().no_cache { @@ -61,19 +65,20 @@ pub trait PackageManager { curr_cmd.exec(mode.into()) } } + .await }; match &strat.dry_run { DryRunStrategy::PrintCmd if self.cfg().dry_run => { - cmd.clone().exec(Mode::PrintCmd)? + cmd.clone().exec(Mode::PrintCmd).await? } DryRunStrategy::WithFlags(v) if self.cfg().dry_run => { cmd.flags.extend(v.to_owned()); // * A dry run with extra flags does not need `sudo`. cmd = cmd.sudo(false); - body(&cmd)? + body(&cmd).await? } - _ => body(&cmd)?, + _ => body(&cmd).await?, } }; @@ -93,14 +98,16 @@ pub trait PackageManager { /// A helper method to simplify direct command invocation. /// It is just like `run`, but intended to be used only for its side effects. - fn just_run(&self, cmd: Cmd, mode: PmMode, strat: Strategies) -> Result<(), Error> { - self.run(cmd, mode, strat).and(Ok(())) + async fn just_run(&self, cmd: Cmd, mode: PmMode, strat: Strategies) -> Result<()> { + self.run(cmd, mode, strat).await?; + Ok(()) } /// A helper method to simplify direct command invocation. /// It is just like `run`, but intended to be used only for its side effects, and always with default mode (`CheckErr` for now) and strategies. - fn just_run_default(&self, cmd: Cmd) -> Result<(), Error> { + async fn just_run_default(&self, cmd: Cmd) -> Result<()> { self.just_run(cmd, Default::default(), Default::default()) + .await } make_pm![ From eef8b95a00d5e0254d70b52f364b24791d600c23 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sat, 17 Oct 2020 23:49:20 +0800 Subject: [PATCH 05/28] refactor: try to rewrite `PackageManager` in async Rust, take 2 (failed) --- Cargo.lock | 144 ++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/exec.rs | 5 +- src/package_manager/mod.rs | 103 ++++++++++++++++---------- 4 files changed, 211 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 292ed91236c..16aee9af030 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,101 @@ dependencies = [ "winapi", ] +[[package]] +name = "futures" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8e3078b7b2a8a671cb7a3d17b4760e4181ea243227776ba83fd043b4ca034e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b" + +[[package]] +name = "futures-executor" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc709ca1da6f66143b8c9bec8e6260181869893714e9b5a490b169b0414144ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c" + +[[package]] +name = "futures-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd" + +[[package]] +name = "futures-task" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + [[package]] name = "getrandom" version = "0.1.15" @@ -321,6 +416,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" + [[package]] name = "os_str_bytes" version = "2.3.2" @@ -337,6 +438,7 @@ dependencies = [ "colored", "confy", "dirs", + "futures", "is-root", "lazy_static", "regex", @@ -345,12 +447,38 @@ dependencies = [ "which", ] +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -375,6 +503,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -496,9 +636,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2e59c50ed8f6b050b071aa7b6865293957a9af6b58b94f97c1c9434ad440ea" +checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 712dc91f9a3..54239e0663a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ serde = {version = "1.0.116", features = ["derive"]} # subprocess = "0.2.6" anyhow = "1.0.33" async-trait = "0.1.41" +futures = "0.3.6" tokio = {version = "0.3.0", features = ["io-std", "io-util", "macros", "process", "rt-multi-thread"]} which = "4.0.2" diff --git a/src/exec.rs b/src/exec.rs index fce4c262e89..f9d43dff5b5 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -6,9 +6,12 @@ use std::ffi::OsStr; use std::io::Write; use std::process::Stdio; use std::sync::Mutex; -use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::Command as Exec; use tokio::select; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + try_join, +}; /// Different ways in which a command shall be dealt with. #[derive(Copy, Clone, Debug)] diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 319fe398e13..55c9ffc0931 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -15,6 +15,7 @@ use crate::dispatch::config::Config; use crate::error::Error; use crate::exec::{Cmd, Mode, Output}; use anyhow::Result; +use futures::future::BoxFuture; use async_trait::async_trait; @@ -25,8 +26,19 @@ macro_rules! make_pm {( ),* ) => { $( $(#[$meta] )* - async fn $method(&self, _kws: &[&str], _flags: &[&str]) -> anyhow::Result<()> { - std::result::Result::Err(format!("Operation `{}` unimplemented for `{}`", stringify!($method), self.name()).into()) + fn $method(&self, _kws: &[&str], _flags: &[&str]) -> BoxFuture<'_,anyhow::Result<()>> { + let name = self.name(); + Box::pin(async { + ::std::result::Result::Err(crate::error::Error::from( + format!( + "Operation `{}` unimplemented for `{}`", + stringify!($method), + name + ) + .as_ref(), + ))?; + Ok(()) + }) })* }; } @@ -43,52 +55,59 @@ pub trait PackageManager { fn cfg(&self) -> Config; /// A helper method to simplify direct command invocation. - async fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result { + async fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result + where + Self: Sized, + { + let cfg = self.cfg(); + // `--dry-run` should apply to both the main command and the cleanup. - let res = { - let body = |cmd: &Cmd| async { - let mut curr_cmd = cmd.clone(); - let no_confirm = self.cfg().no_confirm; - if self.cfg().no_cache { - if let NoCacheStrategy::WithFlags(v) = &strat.no_cache { - curr_cmd.flags.extend(v.to_owned()); - } + async fn body( + cfg: &Config, + cmd: &Cmd, + mode: PmMode, + strat: &Strategies, + ) -> Result { + let mut curr_cmd = cmd.clone(); + let no_confirm = cfg.no_confirm; + if cfg.no_cache { + if let NoCacheStrategy::WithFlags(v) = &strat.no_cache { + curr_cmd.flags.extend(v.to_owned()); } - match &strat.prompt { - PromptStrategy::None => curr_cmd.exec(mode.into()), - PromptStrategy::CustomPrompt if no_confirm => curr_cmd.exec(mode.into()), - PromptStrategy::CustomPrompt => curr_cmd.exec(Mode::Prompt), - PromptStrategy::NativePrompt { no_confirm: v } => { - if no_confirm { - curr_cmd.flags.extend(v.to_owned()); - } - curr_cmd.exec(mode.into()) + } + match &strat.prompt { + PromptStrategy::None => curr_cmd.exec(mode.into()).await, + PromptStrategy::CustomPrompt if no_confirm => curr_cmd.exec(mode.into()).await, + PromptStrategy::CustomPrompt => curr_cmd.exec(Mode::Prompt).await, + PromptStrategy::NativePrompt { no_confirm: v } => { + if no_confirm { + curr_cmd.flags.extend(v.to_owned()); } + curr_cmd.exec(mode.into()).await } - .await - }; + } + }; - match &strat.dry_run { - DryRunStrategy::PrintCmd if self.cfg().dry_run => { - cmd.clone().exec(Mode::PrintCmd).await? - } - DryRunStrategy::WithFlags(v) if self.cfg().dry_run => { - cmd.flags.extend(v.to_owned()); - // * A dry run with extra flags does not need `sudo`. - cmd = cmd.sudo(false); - body(&cmd).await? - } - _ => body(&cmd).await?, + let res = match &strat.dry_run { + DryRunStrategy::PrintCmd if self.cfg().dry_run => { + cmd.clone().exec(Mode::PrintCmd).await? + } + DryRunStrategy::WithFlags(v) if self.cfg().dry_run => { + cmd.flags.extend(v.to_owned()); + // * A dry run with extra flags does not need `sudo`. + cmd = cmd.sudo(false); + body(&cfg, &cmd, mode, &strat).await? } + _ => body(&cfg, &cmd, mode, &strat).await?, }; // Perform the cleanup. - if self.cfg().no_cache { + if cfg.no_cache { let flags = cmd.flags.iter().map(|s| s.as_ref()).collect::>(); match &strat.no_cache { - NoCacheStrategy::Sc => self.sc(&[], &flags)?, - NoCacheStrategy::Scc => self.scc(&[], &flags)?, - NoCacheStrategy::Sccc => self.sccc(&[], &flags)?, + NoCacheStrategy::Sc => self.sc(&[], &flags).await?, + NoCacheStrategy::Scc => self.scc(&[], &flags).await?, + NoCacheStrategy::Sccc => self.sccc(&[], &flags).await?, _ => (), }; } @@ -98,14 +117,20 @@ pub trait PackageManager { /// A helper method to simplify direct command invocation. /// It is just like `run`, but intended to be used only for its side effects. - async fn just_run(&self, cmd: Cmd, mode: PmMode, strat: Strategies) -> Result<()> { + async fn just_run(&self, cmd: Cmd, mode: PmMode, strat: Strategies) -> Result<()> + where + Self: Sized, + { self.run(cmd, mode, strat).await?; Ok(()) } /// A helper method to simplify direct command invocation. /// It is just like `run`, but intended to be used only for its side effects, and always with default mode (`CheckErr` for now) and strategies. - async fn just_run_default(&self, cmd: Cmd) -> Result<()> { + async fn just_run_default(&self, cmd: Cmd) -> Result<()> + where + Self: Sized, + { self.just_run(cmd, Default::default(), Default::default()) .await } From 3377877cb8b8de78a999dd0ef1a8562edbca5d3c Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 00:45:09 +0800 Subject: [PATCH 06/28] refactor: try to rewrite `PackageManager` in async Rust, take 3 (failed) --- Cargo.lock | 7 +++++++ Cargo.toml | 2 +- src/exec.rs | 8 +++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16aee9af030..c3a7232883c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "futures" version = "0.3.6" @@ -699,6 +705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7137dbb0abee577362ccdc7df21605cfcbb949243aeab47dac9ea6ef7d830e21" dependencies = [ "bytes", + "fnv", "lazy_static", "libc", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 54239e0663a..2815023e9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ serde = {version = "1.0.116", features = ["derive"]} anyhow = "1.0.33" async-trait = "0.1.41" futures = "0.3.6" -tokio = {version = "0.3.0", features = ["io-std", "io-util", "macros", "process", "rt-multi-thread"]} +tokio = {version = "0.3.0", features = ["io-std", "io-util", "macros", "process", "rt-multi-thread", "sync"]} which = "4.0.2" [profile.release] diff --git a/src/exec.rs b/src/exec.rs index f9d43dff5b5..0100fae1629 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -5,13 +5,11 @@ use regex::Regex; use std::ffi::OsStr; use std::io::Write; use std::process::Stdio; -use std::sync::Mutex; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::Command as Exec; use tokio::select; -use tokio::{ - io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, - try_join, -}; +use tokio::sync::Mutex; +use tokio::try_join; /// Different ways in which a command shall be dealt with. #[derive(Copy, Clone, Debug)] From dc1d917d21d8a2c80569c36a298af7233aaa56e8 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 11:12:10 +0800 Subject: [PATCH 07/28] refactor: try to rewrite `PackageManager` in async Rust, take 4 --- src/dispatch/config.rs | 8 ++--- src/dispatch/opt.rs | 68 ++++++++++++++++++++------------------ src/error.rs | 48 --------------------------- src/exec.rs | 40 +++++++++++++--------- src/lib.rs | 5 ++- src/main.rs | 2 +- src/package_manager/mod.rs | 34 +++++++++---------- src/print.rs | 3 +- tests/smoke_test.rs | 2 ++ tests/utils.rs | 8 +++-- 10 files changed, 92 insertions(+), 126 deletions(-) delete mode 100644 src/error.rs diff --git a/src/dispatch/config.rs b/src/dispatch/config.rs index a293e18b0a5..c08a805163c 100644 --- a/src/dispatch/config.rs +++ b/src/dispatch/config.rs @@ -1,4 +1,4 @@ -use crate::error::Error; +use anyhow::{Error, Result}; use serde::{Deserialize, Serialize}; /// Configurations that may vary when running the package manager. @@ -24,12 +24,10 @@ pub struct Config { } impl Config { - pub fn load() -> Result { + pub fn load() -> Result { let crate_name = clap::crate_name!(); let config = dirs::home_dir() - .ok_or(Error { - msg: "$HOME path not found".into(), - })? + .ok_or_else(|| anyhow!("$HOME path not found"))? .join(".config") .join(crate_name) .join(&format!("{}.toml", crate_name)); diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 1f2e8fe3729..24824f78234 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -1,8 +1,9 @@ use super::config::Config; -use crate::error::Error; use crate::exec::is_exe; use crate::package_manager::*; +use anyhow::{Error, Result}; use clap::{self, Clap}; +use futures::TryFutureExt; use std::iter::FromIterator; // use structopt::{clap, StructOpt}; @@ -136,13 +137,13 @@ pub struct Opt { impl Opt { /// Check if an Opt object is malformed. - fn check(&self) -> Result<(), Error> { + fn check(&self) -> Result<()> { let count = [self.query, self.remove, self.sync, self.update] .iter() .filter(|&&x| x) .count(); if count != 1 { - Err("exactly 1 operation expected".into()) + Err(anyhow!("exactly 1 operation expected")) } else { Ok(()) } @@ -213,35 +214,36 @@ impl Opt { match pm_str { // Chocolatey - "choco" => Box::new(chocolatey::Chocolatey { cfg }), + // "choco" => Box::new(chocolatey::Chocolatey { cfg }), // Homebrew - "brew" if cfg!(target_os = "macos") => Box::new(homebrew::Homebrew { cfg }), + // "brew" if cfg!(target_os = "macos") => Box::new(homebrew::Homebrew { cfg }), // Linuxbrew - "brew" => Box::new(linuxbrew::Linuxbrew { cfg }), + // "brew" => Box::new(linuxbrew::Linuxbrew { cfg }), // Macports - "port" if cfg!(target_os = "macos") => Box::new(macports::Macports { cfg }), + // "port" if cfg!(target_os = "macos") => Box::new(macports::Macports { cfg }), // Apk for Alpine - "apk" => Box::new(apk::Apk { cfg }), + // "apk" => Box::new(apk::Apk { cfg }), // Apt for Debian/Ubuntu/Termux (new versions) - "apt" => Box::new(apt::Apt { cfg }), + // "apt" => Box::new(apt::Apt { cfg }), // Dnf for RedHat - "dnf" => Box::new(dnf::Dnf { cfg }), + // "dnf" => Box::new(dnf::Dnf { cfg }), // Zypper for SUSE - "zypper" => Box::new(zypper::Zypper { cfg }), + // "zypper" => Box::new(zypper::Zypper { cfg }), // * External Package Managers * // Conda - "conda" => Box::new(conda::Conda { cfg }), + // "conda" => Box::new(conda::Conda { cfg }), // Pip + /* "pip" => Box::new(pip::Pip { cmd: "pip".into(), cfg, @@ -250,9 +252,9 @@ impl Opt { cmd: "pip3".into(), cfg, }), - + */ // Tlmgr - "tlmgr" => Box::new(tlmgr::Tlmgr { cfg }), + // "tlmgr" => Box::new(tlmgr::Tlmgr { cfg }), // Unknown package manager X x => Box::new(unknown::Unknown { name: x.into() }), @@ -260,7 +262,7 @@ impl Opt { } /// Execute the job according to the flags received and the package manager detected. - pub fn dispatch_from(&self, pm: Box) -> Result<(), Error> { + pub async fn dispatch_from(&self, pm: Box) -> Result<()> { self.check()?; let kws: Vec<&str> = self.keywords.iter().map(|s| s.as_ref()).collect(); let flags: Vec<&str> = self.extra_flags.iter().map(|s| s.as_ref()).collect(); @@ -297,8 +299,8 @@ impl Opt { macro_rules! dispatch_match { ($( $method:ident ), *) => { match options.to_lowercase().as_ref() { - $(stringify!($method) => pm.$method(&kws, &flags),)* - _ => Err("Invalid flag".into()), + $(stringify!($method) => pm.$method(&kws, &flags).await,)* + _ => Err(anyhow!("Invalid flag")), } }; } @@ -309,18 +311,19 @@ impl Opt { ] } - pub fn dispatch(&self) -> Result<(), Error> { - self.dispatch_from(self.make_pm(Config::load()?)) + pub async fn dispatch(&self) -> Result<()> { + self.dispatch_from(self.make_pm(Config::load()?)).await } } #[cfg(test)] mod tests { use super::*; + use tokio::test; macro_rules! make_mock_pm { ($( $method:ident ), *) => { - $(fn $method(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + $(fn $method(&self, kws: &[&str], flags: &[&str]) -> futures::future::BoxFuture<'_,anyhow::Result<()>> { let kws: Vec<_> = kws.iter().chain(flags).collect(); panic!("should run: {} {:?}", stringify!($method), &kws) })* @@ -329,6 +332,7 @@ mod tests { struct MockPM {} + #[async_trait] impl PackageManager for MockPM { /// Get the name of the package manager. fn name(&self) -> String { @@ -353,19 +357,19 @@ mod tests { #[test] #[should_panic(expected = "should run: suy")] - fn simple_syu() { + async fn simple_syu() { let opt = dbg!(Opt::parse_from(&["pacaptr", "-Syu"])); assert!(opt.keywords.is_empty()); assert!(opt.sync); assert!(opt.y); assert!(opt.u); - opt.dispatch_from(Box::new(opt.make_mock())).unwrap(); + opt.dispatch_from(Box::new(opt.make_mock())).await.unwrap(); } #[test] #[should_panic(expected = "should run: suy")] - fn long_syu() { + async fn long_syu() { let opt = dbg!(Opt::parse_from(&[ "pacaptr", "--sync", @@ -377,22 +381,22 @@ mod tests { assert!(opt.sync); assert!(opt.y); assert!(opt.u); - opt.dispatch_from(Box::new(opt.make_mock())).unwrap(); + opt.dispatch_from(Box::new(opt.make_mock())).await.unwrap(); } #[test] #[should_panic(expected = r#"should run: sw ["curl", "wget"]"#)] - fn simple_si() { + async fn simple_si() { let opt = dbg!(Opt::parse_from(&["pacaptr", "-Sw", "curl", "wget"])); assert!(opt.sync); assert!(opt.w); - opt.dispatch_from(Box::new(opt.make_mock())).unwrap(); + opt.dispatch_from(Box::new(opt.make_mock())).await.unwrap(); } #[test] #[should_panic(expected = r#"should run: s ["docker"]"#)] - fn other_flags() { + async fn other_flags() { let opt = dbg!(Opt::parse_from(&[ "pacaptr", "-S", "--dryrun", "--yes", "docker", "--cask" ])); @@ -401,12 +405,12 @@ mod tests { assert!(opt.dry_run); assert!(opt.no_confirm); assert!(opt.force_cask); - opt.dispatch_from(Box::new(opt.make_mock())).unwrap(); + opt.dispatch_from(Box::new(opt.make_mock())).await.unwrap(); } #[test] #[should_panic(expected = r#"should run: s ["docker", "--proxy=localhost:1234"]"#)] - fn extra_flags() { + async fn extra_flags() { let opt = dbg!(Opt::parse_from(&[ "pacaptr", "-S", @@ -421,18 +425,18 @@ mod tests { let mut flags = opt.extra_flags.iter(); assert_eq!(flags.next(), Some(&String::from("--proxy=localhost:1234"))); assert_eq!(flags.next(), None); - opt.dispatch_from(Box::new(opt.make_mock())).unwrap(); + opt.dispatch_from(Box::new(opt.make_mock())).await.unwrap(); } #[test] #[should_panic(expected = "exactly 1 operation expected")] - fn too_many_ops() { + async fn too_many_ops() { let opt = dbg!(Opt::parse_from(&["pacaptr", "-SQns", "docker", "--cask"])); assert!(opt.sync); assert!(opt.query); assert!(opt.n); assert_eq!(opt.s, 1); - opt.dispatch_from(Box::new(opt.make_mock())).unwrap(); + opt.dispatch_from(Box::new(opt.make_mock())).await.unwrap(); } } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index b70e43b8481..00000000000 --- a/src/error.rs +++ /dev/null @@ -1,48 +0,0 @@ -#[derive(Debug)] -pub struct Error { - pub msg: String, -} - -impl Error { - fn from(msg: &str) -> Self { - Error { msg: msg.into() } - } -} - -impl std::convert::From<&str> for Error { - fn from(msg: &str) -> Self { - Error::from(msg) - } -} - -impl std::convert::From for Error { - fn from(msg: String) -> Self { - Error::from(&msg) - } -} - -impl std::convert::From for Error { - fn from(io_err: std::io::Error) -> Self { - Error::from(&format!("{}", io_err)) - } -} - -impl std::convert::From for Error { - fn from(err: std::string::FromUtf8Error) -> Self { - Error::from(&format!("{}", err)) - } -} - -impl std::convert::From for Error { - fn from(err: confy::ConfyError) -> Self { - Error::from(&format!("{}", err)) - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.msg) - } -} - -impl std::error::Error for Error {} diff --git a/src/exec.rs b/src/exec.rs index 0100fae1629..fb2812123ae 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,5 +1,5 @@ -use crate::error::Error; use crate::print::*; +use anyhow::Error; pub use is_root::is_root; use regex::Regex; use std::ffi::OsStr; @@ -39,9 +39,9 @@ pub type StatusCode = i32; #[derive(Debug, Clone, Default)] pub struct Output { /// The captured `stdout`, sometimes mixed with captured `stderr`. - contents: Vec, + pub contents: Vec, /// `Some(n)` for exit code, `None` for signals. - code: Option, + pub code: Option, } /// A command to be executed, provided in `command-keywords-flags` form. @@ -86,18 +86,26 @@ impl> Cmd { /// Convert a `Cmd` object into a `subprocess::Exec`. pub fn build(self) -> Exec { // * We use `sudo -S` to launch subprocess if `sudo` is `true` and the current user is not `root`. - let builder = if self.sudo && !is_root() { - Exec::new("sudo").arg("-S").args(&self.cmd) + // ! Special fix for `zypper`: `zypper install -y curl` is accepted, + // ! but not `zypper install curl -y`. + // ! So we place the flags first, and then keywords. + if self.sudo && !is_root() { + let mut builder = Exec::new("sudo"); + builder + .arg("-S") + .args(&self.cmd) + .args(&self.flags) + .args(&self.kws); + builder } else { let (cmd, subcmd) = self .cmd .split_first() .expect("Failed to build Cmd, command is empty"); - Exec::new(cmd).args(subcmd) - }; - // ! Special fix for `zypper`: `zypper install -y curl` is accepted, - // ! but not `zypper install curl -y.` - *(builder.args(&self.flags).args(&self.kws)) + let mut builder = Exec::new(cmd); + builder.args(subcmd).args(&self.flags).args(&self.kws); + builder + } } } @@ -151,18 +159,18 @@ impl + AsRef> Cmd { .stdout .take() .map(|x| BufReader::new(x).lines()) - .ok_or_else(|| Error::from("Child did not have a handle to stdout"))?; + .ok_or_else(|| anyhow!("Child did not have a handle to stdout"))?; let mut stderr_reader = child .stderr .take() .map(|x| BufReader::new(x).lines()) - .ok_or_else(|| Error::from("Child did not have a handle to stderr"))?; + .ok_or_else(|| anyhow!("Child did not have a handle to stderr"))?; let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { let status = child .wait() .await - .map_err(|_| Error::from("Child encountered an error"))?; + .map_err(|_| anyhow!("Child encountered an error"))?; Ok(status.code()) }); @@ -201,13 +209,13 @@ impl + AsRef> Cmd { .stderr .take() .map(|x| BufReader::new(x).lines()) - .ok_or_else(|| Error::from("Child did not have a handle to stderr"))?; + .ok_or_else(|| anyhow!("Child did not have a handle to stderr"))?; let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { let status = child .wait() .await - .map_err(|_| Error::from("Child encountered an error"))?; + .map_err(|_| anyhow!("Child encountered an error"))?; Ok(status.code()) }); @@ -239,7 +247,7 @@ impl + AsRef> Cmd { static ref ALL_YES: Mutex = Mutex::new(false); } - let mut all_yes = ALL_YES.lock().unwrap(); + let mut all_yes = ALL_YES.lock().await; let proceed: bool = if *all_yes { true } else { diff --git a/src/lib.rs b/src/lib.rs index d383fb8c49e..d9a7204bdce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,11 @@ pub mod dispatch; -pub mod error; pub mod exec; pub mod package_manager; pub mod print; +#[macro_use] +extern crate async_trait; +#[macro_use] +extern crate anyhow; #[macro_use] extern crate lazy_static; diff --git a/src/main.rs b/src/main.rs index 7ad17b54d9c..bc7d17a6130 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use pacaptr::print::{print_err, PROMPT_ERROR}; #[tokio::main] async fn main() { let opt = Opt::parse(); - if let Err(e) = opt.dispatch() { + if let Err(e) = opt.dispatch().await { print_err(e, PROMPT_ERROR); std::process::exit(1); } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 55c9ffc0931..26024a9ca6f 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,24 +1,25 @@ +/* pub mod apk; pub mod apt; pub mod chocolatey; pub mod conda; pub mod dnf; -pub mod homebrew; pub mod linuxbrew; pub mod macports; pub mod pip; pub mod tlmgr; -pub mod unknown; pub mod zypper; +*/ + +// pub mod homebrew; +pub mod unknown; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{Cmd, Mode, Output}; +use anyhow::Error; use anyhow::Result; use futures::future::BoxFuture; -use async_trait::async_trait; - macro_rules! make_pm {( $( $( #[$meta:meta] )* @@ -26,18 +27,16 @@ macro_rules! make_pm {( ),* ) => { $( $(#[$meta] )* - fn $method(&self, _kws: &[&str], _flags: &[&str]) -> BoxFuture<'_,anyhow::Result<()>> { - let name = self.name(); - Box::pin(async { - ::std::result::Result::Err(crate::error::Error::from( + fn $method<'s>(&'s self, _kws: &[&str], _flags: &[&str]) -> BoxFuture<'s,anyhow::Result<()>> { + Box::pin(async move { + let name = self.name(); + ::std::result::Result::Err(anyhow::anyhow!( format!( "Operation `{}` unimplemented for `{}`", stringify!($method), - name - ) - .as_ref(), - ))?; - Ok(()) + name, + ), + )) }) })* }; @@ -47,7 +46,7 @@ macro_rules! make_pm {( /// For method explanation see: https://wiki.archlinux.org/index.php/Pacman/Rosetta /// and https://wiki.archlinux.org/index.php/Pacman #[async_trait] -pub trait PackageManager { +pub trait PackageManager: Sync { /// Get the name of the package manager. fn name(&self) -> String; @@ -55,10 +54,7 @@ pub trait PackageManager { fn cfg(&self) -> Config; /// A helper method to simplify direct command invocation. - async fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result - where - Self: Sized, - { + async fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result { let cfg = self.cfg(); // `--dry-run` should apply to both the main command and the cleanup. diff --git a/src/print.rs b/src/print.rs index a06094ce5ae..8bf553b34ac 100644 --- a/src/print.rs +++ b/src/print.rs @@ -42,7 +42,8 @@ pub fn print_msg(msg: &str, prompt: &str) { } /// Print out an error after the given prompt. -pub fn print_err(err: impl std::error::Error, prompt: &str) { +pub fn print_err(err: impl std::fmt::Display, prompt: &str) { + let err = format!("{:#}", err); eprintln!(msg_format!(), prompt.bright_red().bold(), err); } diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index 0ad21e6bd60..10453f2fcb8 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -2,6 +2,7 @@ pub use self::utils::Test; mod utils; +/* #[cfg(target_os = "macos")] mod homebrew { use super::Test; @@ -263,3 +264,4 @@ mod zypper { .run(false) } } +*/ diff --git a/tests/utils.rs b/tests/utils.rs index 2e7c3b5fc7c..e009ea106f0 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -55,7 +55,7 @@ impl<'t> Test<'t> { self } - pub fn run(&self, verbose: bool) { + pub async fn run(&self, verbose: bool) { let try_match = |out: &str, patterns: &[&str]| { patterns .iter() @@ -80,14 +80,16 @@ impl<'t> Test<'t> { // if not matches_all(got, patterns): // raise MatchError(some_msg) let mode = if verbose { Mode::CheckAll } else { Mode::Mute }; - let got_bytes: Vec = match *input { + let output = match *input { Input::Pacaptr { args, flags } => Cmd::new(CARGO_RUN) .kws(args) .flags(flags) .exec(mode) + .await .unwrap(), - Input::Exec { cmd, kws } => Cmd::new(cmd).kws(kws).exec(mode).unwrap(), + Input::Exec { cmd, kws } => Cmd::new(cmd).kws(kws).exec(mode).await.unwrap(), }; + let got_bytes = output.contents; let got = String::from_utf8(got_bytes).unwrap(); try_match(&got, *patterns); } From de4ca084f2ec190c6a3a2125e6022c7d6357626f Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 11:16:20 +0800 Subject: [PATCH 08/28] chore: clean the clutters --- src/dispatch/config.rs | 2 +- src/dispatch/opt.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dispatch/config.rs b/src/dispatch/config.rs index c08a805163c..0f1974c0c9b 100644 --- a/src/dispatch/config.rs +++ b/src/dispatch/config.rs @@ -1,4 +1,4 @@ -use anyhow::{Error, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; /// Configurations that may vary when running the package manager. diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 24824f78234..b1e06428ad6 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -212,6 +212,7 @@ impl Opt { .or_else(|| cfg.default_pm.as_deref()) .unwrap_or_else(Opt::detect_pm_str); + #[allow(clippy::match_single_binding)] match pm_str { // Chocolatey // "choco" => Box::new(chocolatey::Chocolatey { cfg }), From 70b5bac2e7e73d1f9e2749eff08f8fd41b4dbde6 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 11:22:20 +0800 Subject: [PATCH 09/28] refactor: optimize error messages --- src/dispatch/opt.rs | 3 +-- src/exec.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index b1e06428ad6..5aa2293e5c8 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -1,9 +1,8 @@ use super::config::Config; use crate::exec::is_exe; use crate::package_manager::*; -use anyhow::{Error, Result}; +use anyhow::Result; use clap::{self, Clap}; -use futures::TryFutureExt; use std::iter::FromIterator; // use structopt::{clap, StructOpt}; diff --git a/src/exec.rs b/src/exec.rs index fb2812123ae..e7079fa00bc 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,5 +1,5 @@ use crate::print::*; -use anyhow::Error; +use anyhow::{Context, Error}; pub use is_root::is_root; use regex::Regex; use std::ffi::OsStr; @@ -154,23 +154,24 @@ impl + AsRef> Cmd { .build() .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .spawn()?; + .spawn() + .context("Failed to spawn child process")?; let mut stdout_reader = child .stdout .take() .map(|x| BufReader::new(x).lines()) - .ok_or_else(|| anyhow!("Child did not have a handle to stdout"))?; + .ok_or_else(|| anyhow!("Child process did not have a handle to stdout"))?; let mut stderr_reader = child .stderr .take() .map(|x| BufReader::new(x).lines()) - .ok_or_else(|| anyhow!("Child did not have a handle to stderr"))?; + .ok_or_else(|| anyhow!("Child process did not have a handle to stderr"))?; let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { let status = child .wait() .await - .map_err(|_| anyhow!("Child encountered an error"))?; + .context("Child process encountered an error")?; Ok(status.code()) }); @@ -204,7 +205,8 @@ impl + AsRef> Cmd { .build() .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .spawn()?; + .spawn() + .context("Failed to spawn child process")?; let mut stderr_reader = child .stderr .take() @@ -215,7 +217,7 @@ impl + AsRef> Cmd { let status = child .wait() .await - .map_err(|_| anyhow!("Child encountered an error"))?; + .context("Child process encountered an error")?; Ok(status.code()) }); From 85dde215a2805bed9250ce32a9d684023b2f4cd8 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 11:42:58 +0800 Subject: [PATCH 10/28] test: add another test for output testing --- src/exec.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/exec.rs b/src/exec.rs index e7079fa00bc..6cb6eec3354 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -7,9 +7,8 @@ use std::io::Write; use std::process::Stdio; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::Command as Exec; -use tokio::select; use tokio::sync::Mutex; -use tokio::try_join; +use tokio::{select, try_join}; /// Different ways in which a command shall be dealt with. #[derive(Copy, Clone, Debug)] @@ -350,3 +349,18 @@ pub fn is_exe(name: &str, path: &str) -> bool { (!path.is_empty() && std::path::Path::new(path).exists()) || (!name.is_empty() && which::which(name).is_ok()) } + +#[cfg(test)] +mod tests { + use super::*; + use tokio::test; + + #[test] + async fn simple_run() { + println!("Starting!"); + let cmd = + Cmd::new(&["bash", "-c"]).kws(&["echo Hello; sleep 2; echo World; sleep 2; echo !"]); + let res = cmd.exec_checkall(false).await.unwrap(); + dbg!(res); + } +} From 392dfe610cd4c087fb992391f84ebb900e12f9b4 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 11:53:33 +0800 Subject: [PATCH 11/28] fix: fix exec_checkall and exec_checkerr --- src/exec.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/exec.rs b/src/exec.rs index 6cb6eec3354..43967cd267b 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -146,6 +146,17 @@ impl + AsRef> Cmd { } } + /// Helper function to write a line to a `String` and `stdout`. + async fn writeln(s: &str, mute: bool, out: V, stdout: W) -> tokio::io::Result<()> + where + V: AsyncWriteExt + Unpin, + W: AsyncWriteExt + Unpin, + { + let mut s = s.to_owned(); + s.push('\n'); + Self::write(&s, mute, out, stdout).await + } + /// Execute a command and return its `stdout` and `stderr`. /// If `mute` is `false`, then its normal `stdout/stderr` will be printed in the console too. async fn exec_checkall(self, mute: bool) -> Result { @@ -181,11 +192,11 @@ impl + AsRef> Cmd { select! { ln = stdout_reader.next_line() => match ln? { None => break, - Some(l) => Self::write(&l, mute, &mut out, &mut stdout).await?, + Some(l) => Self::writeln(&l, mute, &mut out, &mut stdout).await?, }, ln = stderr_reader.next_line() => match ln? { None => break, - Some(l) => Self::write(&l, mute, &mut out, &mut stdout).await?, + Some(l) => Self::writeln(&l, mute, &mut out, &mut stdout).await?, }, else => continue, } @@ -227,7 +238,7 @@ impl + AsRef> Cmd { select! { ln = stderr_reader.next_line() => match ln? { None => break, - Some(l) => Self::write(&l, mute, &mut out, &mut stderr).await?, + Some(l) => Self::writeln(&l, mute, &mut out, &mut stderr).await?, }, else => continue, } @@ -350,6 +361,7 @@ pub fn is_exe(name: &str, path: &str) -> bool { || (!name.is_empty() && which::which(name).is_ok()) } +/* #[cfg(test)] mod tests { use super::*; @@ -358,9 +370,10 @@ mod tests { #[test] async fn simple_run() { println!("Starting!"); - let cmd = - Cmd::new(&["bash", "-c"]).kws(&["echo Hello; sleep 2; echo World; sleep 2; echo !"]); + let cmd = Cmd::new(&["bash", "-c"]) + .kws(&[r#"printf "Hello\n"; sleep 3; printf "World\n"; sleep 3; printf "!\n""#]); let res = cmd.exec_checkall(false).await.unwrap(); dbg!(res); } } +*/ From 72afa2abbded982b161946ea53b26100d1907668 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 14:14:40 +0800 Subject: [PATCH 12/28] refactor: activate `homebrew` (failed) --- .vscode/settings.json | 1 + src/exec.rs | 14 +++---- src/package_manager/homebrew.rs | 66 ++++++++++++++++----------------- src/package_manager/mod.rs | 5 ++- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 97c2faff8c0..ec5730f286b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,7 @@ "nuget", "nupkg", "pacaptr", + "printf", "proto", "rdepends", "repoquery", diff --git a/src/exec.rs b/src/exec.rs index 43967cd267b..958fef68932 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,5 +1,5 @@ use crate::print::*; -use anyhow::{Context, Error}; +use anyhow::{anyhow, Context, Result}; pub use is_root::is_root; use regex::Regex; use std::ffi::OsStr; @@ -112,7 +112,7 @@ impl + AsRef> Cmd { /// Execute a command and return a `Result, _>`. /// The exact behavior depends on the `mode` passed in. /// See `exec::Mode`'s documentation for more info. - pub async fn exec(self, mode: Mode) -> Result { + pub async fn exec(self, mode: Mode) -> Result { match mode { Mode::PrintCmd => { print_cmd(&self, PROMPT_CANCELED); @@ -159,7 +159,7 @@ impl + AsRef> Cmd { /// Execute a command and return its `stdout` and `stderr`. /// If `mute` is `false`, then its normal `stdout/stderr` will be printed in the console too. - async fn exec_checkall(self, mute: bool) -> Result { + async fn exec_checkall(self, mute: bool) -> Result { let mut child = self .build() .stdout(Stdio::piped()) @@ -177,7 +177,7 @@ impl + AsRef> Cmd { .map(|x| BufReader::new(x).lines()) .ok_or_else(|| anyhow!("Child process did not have a handle to stderr"))?; - let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { + let code: tokio::task::JoinHandle>> = tokio::spawn(async move { let status = child .wait() .await @@ -210,7 +210,7 @@ impl + AsRef> Cmd { /// Execute a command and collect its `stderr`. /// If `mute` is `false`, then its normal `stderr` will be printed in the console too. - async fn exec_checkerr(self, mute: bool) -> Result { + async fn exec_checkerr(self, mute: bool) -> Result { let mut child = self .build() .stdout(Stdio::piped()) @@ -223,7 +223,7 @@ impl + AsRef> Cmd { .map(|x| BufReader::new(x).lines()) .ok_or_else(|| anyhow!("Child did not have a handle to stderr"))?; - let code: tokio::task::JoinHandle, Error>> = tokio::spawn(async move { + let code: tokio::task::JoinHandle>> = tokio::spawn(async move { let status = child .wait() .await @@ -254,7 +254,7 @@ impl + AsRef> Cmd { /// If `mute` is `false`, then its normal `stderr` will be printed in the console too. /// The user will be prompted if (s)he wishes to continue with the command execution. #[allow(clippy::mutex_atomic)] - async fn exec_prompt(self, mute: bool) -> Result { + async fn exec_prompt(self, mute: bool) -> Result { lazy_static! { static ref ALL_YES: Mutex = Mutex::new(false); } diff --git a/src/package_manager/homebrew.rs b/src/package_manager/homebrew.rs index 28fa934cb65..e828793ee0c 100644 --- a/src/package_manager/homebrew.rs +++ b/src/package_manager/homebrew.rs @@ -1,8 +1,8 @@ use super::{DryRunStrategy, NoCacheStrategy, PackageManager, PmMode, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{self, Cmd, Mode}; use crate::print::{self, PROMPT_INFO, PROMPT_RUN}; +use anyhow::Result; pub struct Homebrew { pub cfg: Config, @@ -27,27 +27,14 @@ enum CaskState { } impl Homebrew { - /* - const CASK_PREFIX: &'static str = "cask/"; - - fn strip_cask_prefix(pack: &str) -> String { - { - if pack.starts_with(Self::CASK_PREFIX) { - &pack[Self::CASK_PREFIX.len()..] - } else { - pack - } - } - .to_owned() - } - */ - /// Search the output of `brew info` to see if we need `brew cask` for a certain package. - fn search(&self, pack: &str, flags: &[&str]) -> Result { + async fn search(&self, pack: &str, flags: &[&str]) -> Result { let out_bytes = Cmd::new(&["brew", "info"]) .kws(&[pack]) .flags(flags) - .exec(Mode::Mute)?; + .exec(Mode::Mute) + .await? + .contents; let out = String::from_utf8(out_bytes)?; let code = { @@ -72,34 +59,46 @@ impl Homebrew { /// With the exception of `self.cfg.force_cask`, /// this function will use `self.search()` to see if we need `brew cask` for a certain package, /// and then try to execute the corresponding command. - fn auto_cask_do<'s>( + async fn auto_cask_do<'s>( &self, subcmd: &'s [&str], pack: &str, flags: &[&str], strat: Strategies, - ) -> Result<(), Error> { - let run = |mut cmd: Vec<&'s str>, pack: &str| { + ) -> Result<()> { + async fn run( + self_: &Homebrew, + mut cmd: Vec<&str>, + subcmd: &[&str], + pack: &str, + flags: &[&str], + strat: Strategies, + ) -> Result<()> { cmd.extend(subcmd); - self.just_run( - Cmd::new(&cmd).kws(&[pack]).flags(flags), - Default::default(), - strat, - ) - }; + self_ + .just_run( + Cmd::new(&cmd).kws(&[pack]).flags(flags), + Default::default(), + strat, + ) + .await + } if self.cfg.force_cask { - return run(vec!["brew", "cask"], pack); + return run(&self, vec!["brew", "cask"], subcmd, pack, flags, strat).await; } - let code = self.search(pack, flags)?; + let code = self.search(pack, flags).await?; match code { - CaskState::NotFound | CaskState::Brew => run(vec!["brew"], pack), - CaskState::Cask => run(vec!["brew", "cask"], pack), + CaskState::NotFound | CaskState::Brew => { + run(&self, vec!["brew"], subcmd, pack, flags, strat).await + } + CaskState::Cask => run(&self, vec!["brew", "cask"], subcmd, pack, flags, strat).await, } } } +#[async_trait] impl PackageManager for Homebrew { /// Get the name of the package manager. fn name(&self) -> String { @@ -111,11 +110,12 @@ impl PackageManager for Homebrew { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default(Cmd::new(&["brew", "list"]).flags(flags)) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 26024a9ca6f..052bb9980d6 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -11,7 +11,7 @@ pub mod tlmgr; pub mod zypper; */ -// pub mod homebrew; +pub mod homebrew; pub mod unknown; use crate::dispatch::config::Config; @@ -27,7 +27,8 @@ macro_rules! make_pm {( ),* ) => { $( $(#[$meta] )* - fn $method<'s>(&'s self, _kws: &[&str], _flags: &[&str]) -> BoxFuture<'s,anyhow::Result<()>> { + fn $method(&self, _kws: &[&str], _flags: &[&str]) -> BoxFuture<'_, anyhow::Result<()>> + { Box::pin(async move { let name = self.name(); ::std::result::Result::Err(anyhow::anyhow!( From 5f9567f17dc7c583b4aca76abf755f4d25535b23 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 17:52:24 +0800 Subject: [PATCH 13/28] refactor: try to rewrite `PackageManager` in async Rust, take 5 --- src/dispatch/opt.rs | 168 +++++++++++++++++++++++++- src/package_manager/mod.rs | 234 ++++++++++++++++++++++++++----------- 2 files changed, 332 insertions(+), 70 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 5aa2293e5c8..0dca32dbe9b 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -321,6 +321,7 @@ mod tests { use super::*; use tokio::test; + /* macro_rules! make_mock_pm { ($( $method:ident ), *) => { $(fn $method(&self, kws: &[&str], flags: &[&str]) -> futures::future::BoxFuture<'_,anyhow::Result<()>> { @@ -329,6 +330,14 @@ mod tests { })* }; } + */ + + macro_rules! make_mock_op_body { + ( $self:ident, $kws:ident, $flags:ident, $method:ident ) => {{ + let kws: Vec<_> = $kws.iter().chain($flags).collect(); + panic!("should run: {} {:?}", stringify!($method), &kws) + }}; + } struct MockPM {} @@ -343,10 +352,161 @@ mod tests { Config::default() } - make_mock_pm![ - q, qc, qe, qi, qk, ql, qm, qo, qp, qs, qu, r, rn, rns, rs, rss, s, sc, scc, sccc, sg, - si, sii, sl, ss, su, suy, sw, sy, u - ]; + // ! WARNING! + // ! Dirty copy-paste! + + /// Q generates a list of installed packages. + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, q) + } + + /// Qc shows the changelog of a package. + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qc) + } + + /// Qe lists packages installed explicitly (not as dependencies). + async fn qe(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qe) + } + + /// Qi displays local package information: name, version, description, etc. + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qi) + } + + /// Qk verifies one or more packages. + async fn qk(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qk) + } + + /// Ql displays files provided by local package. + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, ql) + } + + /// Qm lists packages that are installed but are not available in any installation source (anymore). + async fn qm(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qm) + } + + /// Qo queries the package which provides FILE. + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qo) + } + + /// Qp queries a package supplied on the command line rather than an entry in the package management database. + async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qp) + } + + /// Qs searches locally installed package for names or descriptions. + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qs) + } + + /// Qu lists packages which have an update available. + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, qu) + } + + /// R removes a single package, leaving all of its dependencies installed. + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, r) + } + + /// Rn removes a package and skips the generation of configuration backup files. + async fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, rn) + } + + /// Rns removes a package and its dependencies which are not required by any other installed package, + /// and skips the generation of configuration backup files. + async fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, rns) + } + + /// Rs removes a package and its dependencies which are not required by any other installed package, + /// and not explicitly installed by the user. + async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, rs) + } + + /// Rss removes a package and its dependencies which are not required by any other installed package. + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, rss) + } + + /// S installs one or more packages by name. + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, s) + } + + /// Sc removes all the cached packages that are not currently installed, and the unused sync database. + async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sc) + } + + /// Scc removes all files from the cache. + async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, scc) + } + + /// Sccc ... + /// What is this? + async fn sccc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sccc) + } + + /// Sg lists all packages belonging to the GROUP. + async fn sg(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sg) + } + + /// Si displays remote package information: name, version, description, etc. + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, si) + } + + /// Sii displays packages which require X to be installed, aka reverse dependencies. + async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sii) + } + + /// Sl displays a list of all packages in all installation sources that are handled by the packages management. + async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sl) + } + + /// Ss searches for package(s) by searching the expression in name, description, short description. + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, ss) + } + + /// Su updates outdated packages. + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, su) + } + + /// Suy refreshes the local package database, then updates outdated packages. + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, suy) + } + + /// Sw retrieves all packages from the server, but does not install/upgrade anything. + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sw) + } + + /// Sy refreshes the local package database. + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, sy) + } + + /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. + async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, kws, flags, u) + } } impl Opt { diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 052bb9980d6..cc1ba78b19d 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -18,8 +18,8 @@ use crate::dispatch::config::Config; use crate::exec::{Cmd, Mode, Output}; use anyhow::Error; use anyhow::Result; -use futures::future::BoxFuture; +/* macro_rules! make_pm {( $( $( #[$meta:meta] )* @@ -42,6 +42,18 @@ macro_rules! make_pm {( })* }; } +*/ + +macro_rules! make_op_body { + ( $self:ident, $method:ident ) => {{ + let name = $self.name(); + ::std::result::Result::Err(anyhow::anyhow!(format!( + "Operation `{}` unimplemented for `{}`", + stringify!($method), + name, + ),)) + }}; +} /// The behaviors of a Pack(age)Manager. /// For method explanation see: https://wiki.archlinux.org/index.php/Pacman/Rosetta @@ -132,71 +144,161 @@ pub trait PackageManager: Sync { .await } - make_pm![ - /// Q generates a list of installed packages. - q, - /// Qc shows the changelog of a package. - qc, - /// Qe lists packages installed explicitly (not as dependencies). - qe, - /// Qi displays local package information: name, version, description, etc. - qi, - /// Qk verifies one or more packages. - qk, - /// Ql displays files provided by local package. - ql, - /// Qm lists packages that are installed but are not available in any installation source (anymore). - qm, - /// Qo queries the package which provides FILE. - qo, - /// Qp queries a package supplied on the command line rather than an entry in the package management database. - qp, - /// Qs searches locally installed package for names or descriptions. - qs, - /// Qu lists packages which have an update available. - qu, - /// R removes a single package, leaving all of its dependencies installed. - r, - /// Rn removes a package and skips the generation of configuration backup files. - rn, - /// Rns removes a package and its dependencies which are not required by any other installed package, - /// and skips the generation of configuration backup files. - rns, - /// Rs removes a package and its dependencies which are not required by any other installed package, - /// and not explicitly installed by the user. - rs, - /// Rss removes a package and its dependencies which are not required by any other installed package. - rss, - /// S installs one or more packages by name. - s, - /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - sc, - /// Scc removes all files from the cache. - scc, - /// Sccc ... - /// What is this? - sccc, - /// Sg lists all packages belonging to the GROUP. - sg, - /// Si displays remote package information: name, version, description, etc. - si, - /// Sii displays packages which require X to be installed, aka reverse dependencies. - sii, - /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - sl, - /// Ss searches for package(s) by searching the expression in name, description, short description. - ss, - /// Su updates outdated packages. - su, - /// Suy refreshes the local package database, then updates outdated packages. - suy, - /// Sw retrieves all packages from the server, but does not install/upgrade anything. - sw, - /// Sy refreshes the local package database. - sy, - /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - u - ]; + // ! WARNING! + // ! Dirty copy-paste! + + /// Q generates a list of installed packages. + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, q) + } + + /// Qc shows the changelog of a package. + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qc) + } + + /// Qe lists packages installed explicitly (not as dependencies). + async fn qe(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qe) + } + + /// Qi displays local package information: name, version, description, etc. + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qi) + } + + /// Qk verifies one or more packages. + async fn qk(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qk) + } + + /// Ql displays files provided by local package. + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, ql) + } + + /// Qm lists packages that are installed but are not available in any installation source (anymore). + async fn qm(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qm) + } + + /// Qo queries the package which provides FILE. + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qo) + } + + /// Qp queries a package supplied on the command line rather than an entry in the package management database. + async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qp) + } + + /// Qs searches locally installed package for names or descriptions. + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qs) + } + + /// Qu lists packages which have an update available. + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, qu) + } + + /// R removes a single package, leaving all of its dependencies installed. + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, r) + } + + /// Rn removes a package and skips the generation of configuration backup files. + async fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, rn) + } + + /// Rns removes a package and its dependencies which are not required by any other installed package, + /// and skips the generation of configuration backup files. + async fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, rns) + } + + /// Rs removes a package and its dependencies which are not required by any other installed package, + /// and not explicitly installed by the user. + async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, rs) + } + + /// Rss removes a package and its dependencies which are not required by any other installed package. + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, rss) + } + + /// S installs one or more packages by name. + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, s) + } + + /// Sc removes all the cached packages that are not currently installed, and the unused sync database. + async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sc) + } + + /// Scc removes all files from the cache. + async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, scc) + } + + /// Sccc ... + /// What is this? + async fn sccc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sccc) + } + + /// Sg lists all packages belonging to the GROUP. + async fn sg(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sg) + } + + /// Si displays remote package information: name, version, description, etc. + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, si) + } + + /// Sii displays packages which require X to be installed, aka reverse dependencies. + async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sii) + } + + /// Sl displays a list of all packages in all installation sources that are handled by the packages management. + async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sl) + } + + /// Ss searches for package(s) by searching the expression in name, description, short description. + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, ss) + } + + /// Su updates outdated packages. + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, su) + } + + /// Suy refreshes the local package database, then updates outdated packages. + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, suy) + } + + /// Sw retrieves all packages from the server, but does not install/upgrade anything. + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sw) + } + + /// Sy refreshes the local package database. + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, sy) + } + + /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. + async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + make_op_body!(self, u) + } } /// Different ways in which a command shall be dealt with. From 9df694e057926790feb25cbf358c69b7d1690bf1 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 18:39:32 +0800 Subject: [PATCH 14/28] refactor: finish `homebrew` (experimental) --- src/dispatch/opt.rs | 120 ++++++++++++++--------------- src/package_manager/homebrew.rs | 131 +++++++++++++++++++------------- src/package_manager/mod.rs | 60 +++++++-------- 3 files changed, 167 insertions(+), 144 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 0dca32dbe9b..9e7b08c4f2b 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -356,156 +356,156 @@ mod tests { // ! Dirty copy-paste! /// Q generates a list of installed packages. - async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, q) + async fn q(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, q) } /// Qc shows the changelog of a package. - async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qc) + async fn qc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qc) } /// Qe lists packages installed explicitly (not as dependencies). - async fn qe(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qe) + async fn qe(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qe) } /// Qi displays local package information: name, version, description, etc. - async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qi) + async fn qi(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qi) } /// Qk verifies one or more packages. - async fn qk(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qk) + async fn qk(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qk) } /// Ql displays files provided by local package. - async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, ql) + async fn ql(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, ql) } /// Qm lists packages that are installed but are not available in any installation source (anymore). - async fn qm(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qm) + async fn qm(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qm) } /// Qo queries the package which provides FILE. - async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qo) + async fn qo(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qo) } /// Qp queries a package supplied on the command line rather than an entry in the package management database. - async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qp) + async fn qp(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qp) } /// Qs searches locally installed package for names or descriptions. - async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qs) + async fn qs(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qs) } /// Qu lists packages which have an update available. - async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, qu) + async fn qu(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, qu) } /// R removes a single package, leaving all of its dependencies installed. - async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, r) + async fn r(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, r) } /// Rn removes a package and skips the generation of configuration backup files. - async fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, rn) + async fn rn(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, rn) } /// Rns removes a package and its dependencies which are not required by any other installed package, /// and skips the generation of configuration backup files. - async fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, rns) + async fn rns(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, rns) } /// Rs removes a package and its dependencies which are not required by any other installed package, /// and not explicitly installed by the user. - async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, rs) + async fn rs(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, rs) } /// Rss removes a package and its dependencies which are not required by any other installed package. - async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, rss) + async fn rss(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, rss) } /// S installs one or more packages by name. - async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, s) + async fn s(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, s) } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sc) + async fn sc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sc) } /// Scc removes all files from the cache. - async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, scc) + async fn scc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, scc) } /// Sccc ... /// What is this? - async fn sccc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sccc) + async fn sccc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sccc) } /// Sg lists all packages belonging to the GROUP. - async fn sg(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sg) + async fn sg(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sg) } /// Si displays remote package information: name, version, description, etc. - async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, si) + async fn si(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, si) } /// Sii displays packages which require X to be installed, aka reverse dependencies. - async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sii) + async fn sii(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sii) } /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sl) + async fn sl(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sl) } /// Ss searches for package(s) by searching the expression in name, description, short description. - async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, ss) + async fn ss(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, ss) } /// Su updates outdated packages. - async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, su) + async fn su(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, su) } /// Suy refreshes the local package database, then updates outdated packages. - async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, suy) + async fn suy(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, suy) } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sw) + async fn sw(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sw) } /// Sy refreshes the local package database. - async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, sy) + async fn sy(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, sy) } /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - make_mock_op_body!(self, kws, flags, u) + async fn u(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { + make_mock_op_body!(self, _kws, _flags, u) } } diff --git a/src/package_manager/homebrew.rs b/src/package_manager/homebrew.rs index e828793ee0c..eece9f3b2f3 100644 --- a/src/package_manager/homebrew.rs +++ b/src/package_manager/homebrew.rs @@ -3,6 +3,7 @@ use crate::dispatch::config::Config; use crate::exec::{self, Cmd, Mode}; use crate::print::{self, PROMPT_INFO, PROMPT_RUN}; use anyhow::Result; +use futures::stream::{self, TryStreamExt}; pub struct Homebrew { pub cfg: Config, @@ -59,9 +60,9 @@ impl Homebrew { /// With the exception of `self.cfg.force_cask`, /// this function will use `self.search()` to see if we need `brew cask` for a certain package, /// and then try to execute the corresponding command. - async fn auto_cask_do<'s>( + async fn auto_cask_do( &self, - subcmd: &'s [&str], + subcmd: &'_ [&str], pack: &str, flags: &[&str], strat: Strategies, @@ -120,67 +121,76 @@ impl PackageManager for Homebrew { } /// Qc shows the changelog of a package. - fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "log"]).kws(kws).flags(flags)) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { // TODO: it seems that the output of `brew list python` in fish has a mechanism against duplication: // /usr/local/Cellar/python/3.6.0/Frameworks/Python.framework/ (1234 files) self.just_run_default(Cmd::new(&["brew", "list"]).kws(kws).flags(flags)) + .await } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str| { exec::grep(contents, kws) .iter() .for_each(|ln| println!("{}", ln)) }; - let search_output = |cmd| { - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self.run(cmd, PmMode::Mute, Default::default())?; - search(&String::from_utf8(out_bytes)?); - Ok(()) - }; - - search_output(&["brew", "list"]) + let cmd: &[&str] = &["brew", "list"]; + let cmd = Cmd::new(cmd).flags(flags); + if !self.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + search(&String::from_utf8(out_bytes)?); + Ok(()) } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "outdated"]).kws(kws).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - kws.iter() - .map(|&pack| self.auto_cask_do(&["uninstall"], pack, flags, PROMPT_STRAT.clone())) - .collect() + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + stream::iter(kws.iter().map(Ok)) + .try_for_each(|&pack| async move { + self.auto_cask_do(&["uninstall"], pack, flags, PROMPT_STRAT.clone()) + .await + }) + .await } /// Rss removes a package and its dependencies which are not required by any other installed package. - fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - let err_bytes = self.run( - Cmd::new(&["brew", "rmtree"]).kws(kws).flags(flags), - Default::default(), - Strategies { - dry_run: DryRunStrategy::with_flags(&["--dry-run"]), - ..Default::default() - }, - )?; + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + let err_bytes = self + .run( + Cmd::new(&["brew", "rmtree"]).kws(kws).flags(flags), + Default::default(), + Strategies { + dry_run: DryRunStrategy::with_flags(&["--dry-run"]), + ..Default::default() + }, + ) + .await? + .contents; let err_msg = String::from_utf8(err_bytes)?; let pattern = "Unknown command: rmtree"; @@ -190,28 +200,30 @@ impl PackageManager for Homebrew { PROMPT_INFO, ); print::print_msg("`brew tap beeftornado/rmtree`", PROMPT_INFO); - return Err("`rmtree` required".into()); + return Err(anyhow!("`rmtree` required")); } Ok(()) } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { for &pack in kws { if self.cfg.needed { - self.auto_cask_do(&["install"], pack, flags, INSTALL_STRAT.clone())?; + self.auto_cask_do(&["install"], pack, flags, INSTALL_STRAT.clone()) + .await?; } else { // If the package is not installed, `brew reinstall` behaves just like `brew install`, // so `brew reinstall` matches perfectly the behavior of `pacman -S`. - self.auto_cask_do(&["reinstall"], pack, flags, INSTALL_STRAT.clone())?; + self.auto_cask_do(&["reinstall"], pack, flags, INSTALL_STRAT.clone()) + .await?; } } Ok(()) } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "cleanup"]).kws(kws).flags(flags), Default::default(), @@ -221,10 +233,11 @@ impl PackageManager for Homebrew { ..Default::default() }, ) + .await } /// Scc removes all files from the cache. - fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "cleanup", "-s"]).kws(kws).flags(flags), Default::default(), @@ -234,30 +247,34 @@ impl PackageManager for Homebrew { ..Default::default() }, ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if self.cfg.force_cask { &["brew", "cask", "info"] } else { &["brew", "info"] }; self.just_run_default(Cmd::new(cmd).kws(kws).flags(flags)) + .await } /// Sii displays packages which require X to be installed, aka reverse dependencies. - fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "uses"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { // `brew cask upgrade` is now deprecated. // A simple `brew upgrade` should do the job. @@ -266,35 +283,41 @@ impl PackageManager for Homebrew { Default::default(), PROMPT_STRAT.clone(), ) + .await } else { for &pack in kws { - self.auto_cask_do(&["upgrade"], pack, flags, PROMPT_STRAT.clone())?; + self.auto_cask_do(&["upgrade"], pack, flags, PROMPT_STRAT.clone()) + .await?; } if self.cfg.no_cache { - self.scc(&[], flags)?; + self.scc(&[], flags).await?; } Ok(()) } } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.sy(&[], flags)?; - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.sy(&[], flags).await?; + self.su(kws, flags).await } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - kws.iter() - .map(|&pack| self.auto_cask_do(&["fetch"], pack, flags, PROMPT_STRAT.clone())) - .collect() + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + stream::iter(kws.iter().map(Ok)) + .try_for_each(|&pack| async move { + self.auto_cask_do(&["fetch"], pack, flags, PROMPT_STRAT.clone()) + .await + }) + .await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.just_run_default(Cmd::new(&["brew", "update"]).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.just_run_default(Cmd::new(&["brew", "update"]).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index cc1ba78b19d..6ade5de4b0d 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -148,155 +148,155 @@ pub trait PackageManager: Sync { // ! Dirty copy-paste! /// Q generates a list of installed packages. - async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn q(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, q) } /// Qc shows the changelog of a package. - async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qc) } /// Qe lists packages installed explicitly (not as dependencies). - async fn qe(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qe(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qe) } /// Qi displays local package information: name, version, description, etc. - async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qi(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qi) } /// Qk verifies one or more packages. - async fn qk(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qk(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qk) } /// Ql displays files provided by local package. - async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn ql(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, ql) } /// Qm lists packages that are installed but are not available in any installation source (anymore). - async fn qm(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qm(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qm) } /// Qo queries the package which provides FILE. - async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qo(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qo) } /// Qp queries a package supplied on the command line rather than an entry in the package management database. - async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qp(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qp) } /// Qs searches locally installed package for names or descriptions. - async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qs(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qs) } /// Qu lists packages which have an update available. - async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn qu(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, qu) } /// R removes a single package, leaving all of its dependencies installed. - async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn r(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, r) } /// Rn removes a package and skips the generation of configuration backup files. - async fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn rn(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, rn) } /// Rns removes a package and its dependencies which are not required by any other installed package, /// and skips the generation of configuration backup files. - async fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn rns(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, rns) } /// Rs removes a package and its dependencies which are not required by any other installed package, /// and not explicitly installed by the user. - async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn rs(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, rs) } /// Rss removes a package and its dependencies which are not required by any other installed package. - async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn rss(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, rss) } /// S installs one or more packages by name. - async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn s(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, s) } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sc) } /// Scc removes all files from the cache. - async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn scc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, scc) } /// Sccc ... /// What is this? - async fn sccc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sccc(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sccc) } /// Sg lists all packages belonging to the GROUP. - async fn sg(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sg(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sg) } /// Si displays remote package information: name, version, description, etc. - async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn si(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, si) } /// Sii displays packages which require X to be installed, aka reverse dependencies. - async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sii(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sii) } /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sl(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sl) } /// Ss searches for package(s) by searching the expression in name, description, short description. - async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn ss(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, ss) } /// Su updates outdated packages. - async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn su(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, su) } /// Suy refreshes the local package database, then updates outdated packages. - async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn suy(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, suy) } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sw(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sw) } /// Sy refreshes the local package database. - async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn sy(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, sy) } /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + async fn u(&self, _kws: &[&str], _flags: &[&str]) -> Result<()> { make_op_body!(self, u) } } From 54f35b0298e132971e69cc4c9abeb483d5f826f2 Mon Sep 17 00:00:00 2001 From: rami3l Date: Sun, 18 Oct 2020 18:59:10 +0800 Subject: [PATCH 15/28] fix: fix `exec_checkerr` --- src/dispatch/opt.rs | 2 +- src/exec.rs | 1 - src/package_manager/mod.rs | 37 +++++++++++++++++++++++++++++++++++-- tests/smoke_test.rs | 21 ++++++++++++++------- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 9e7b08c4f2b..d14838d3106 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -217,7 +217,7 @@ impl Opt { // "choco" => Box::new(chocolatey::Chocolatey { cfg }), // Homebrew - // "brew" if cfg!(target_os = "macos") => Box::new(homebrew::Homebrew { cfg }), + "brew" if cfg!(target_os = "macos") => Box::new(homebrew::Homebrew { cfg }), // Linuxbrew // "brew" => Box::new(linuxbrew::Linuxbrew { cfg }), diff --git a/src/exec.rs b/src/exec.rs index 958fef68932..b4b78726d07 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -213,7 +213,6 @@ impl + AsRef> Cmd { async fn exec_checkerr(self, mute: bool) -> Result { let mut child = self .build() - .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .context("Failed to spawn child process")?; diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 6ade5de4b0d..3ddfb447712 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -130,8 +130,7 @@ pub trait PackageManager: Sync { where Self: Sized, { - self.run(cmd, mode, strat).await?; - Ok(()) + self.run(cmd, mode, strat).await.and(Ok(())) } /// A helper method to simplify direct command invocation. @@ -418,3 +417,37 @@ impl Default for NoCacheStrategy { NoCacheStrategy::None } } + +/* +#[cfg(test)] +mod tests { + use super::*; + use tokio::test; + + struct MockPM {} + + #[async_trait] + impl PackageManager for MockPM { + /// Get the name of the package manager. + fn name(&self) -> String { + "mockpm".into() + } + + fn cfg(&self) -> Config { + Config::default() + } + } + + #[test] + async fn simple_run() { + println!("Starting!"); + let cmd = Cmd::new(&["bash", "-c"]) + .kws(&[r#"printf "Hello\n"; sleep 3; printf "World\n"; sleep 3; printf "!\n""#]); + let res = MockPM {} + .run(cmd, PmMode::CheckErr, Default::default()) + .await + .unwrap(); + dbg!(res); + } +} +*/ diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index 10453f2fcb8..94b3f8c14d4 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -2,57 +2,62 @@ pub use self::utils::Test; mod utils; -/* #[cfg(target_os = "macos")] mod homebrew { use super::Test; + use tokio::test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "curl"], &[]) .output(&["curl is keg-only"]) .run(false) + .await } #[test] #[should_panic(expected = "Failed with pattern `curl is not keg-only`")] - fn si_fail() { + async fn si_fail() { Test::new() .pacaptr(&["-Si", "curl"], &[]) .output(&["curl is not keg-only"]) .run(false) + .await } #[test] - fn s_auto_cask() { + async fn s_auto_cask() { Test::new() .pacaptr(&["-S", "curl", "gimp", "--dryrun"], &[]) .output(&["brew (re)?install curl", "brew cask (re)?install gimp"]) .run(false) + .await } #[test] - fn s_force_cask() { + async fn s_force_cask() { Test::new() .pacaptr(&["-S", "docker", "--dryrun"], &[]) .output(&["brew (re)?install docker"]) .pacaptr(&["-S", "docker", "--cask", "--dryrun"], &[]) .output(&["brew cask (re)?install docker"]) .run(false) + .await } #[test] - fn r_cask() { + async fn r_cask() { Test::new() .pacaptr(&["-R", "curl", "gimp", "--dryrun"], &[]) .output(&["brew uninstall curl", "brew cask uninstall gimp"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "wget", "--yes"], &[]) .output(&["brew (re)?install wget"]) @@ -61,9 +66,11 @@ mod homebrew { .pacaptr(&["-R", "wget", "--yes"], &[]) .output(&["brew uninstall wget", "Uninstalling /usr/local/Cellar/wget"]) .run(false) + .await } } +/* #[cfg(target_os = "windows")] mod chocolatey { use super::Test; From 54410bb3c42efc3e66d9796b10db7e1d204948c8 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 10:45:43 +0800 Subject: [PATCH 16/28] fix: fix #25 --- src/dispatch/opt.rs | 11 +++++----- src/main.rs | 10 +++++++--- src/package_manager/mod.rs | 41 ++++++++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index d14838d3106..93d3af51867 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -1,10 +1,9 @@ use super::config::Config; -use crate::exec::is_exe; +use crate::exec::{is_exe, StatusCode}; use crate::package_manager::*; use anyhow::Result; use clap::{self, Clap}; use std::iter::FromIterator; -// use structopt::{clap, StructOpt}; /// The command line options to be collected. #[derive(Debug, Clap)] @@ -262,7 +261,7 @@ impl Opt { } /// Execute the job according to the flags received and the package manager detected. - pub async fn dispatch_from(&self, pm: Box) -> Result<()> { + pub async fn dispatch_from(&self, pm: Box) -> Result { self.check()?; let kws: Vec<&str> = self.keywords.iter().map(|s| s.as_ref()).collect(); let flags: Vec<&str> = self.extra_flags.iter().map(|s| s.as_ref()).collect(); @@ -308,10 +307,12 @@ impl Opt { dispatch_match![ q, qc, qe, qi, qk, ql, qm, qo, qp, qs, qu, r, rn, rns, rs, rss, s, sc, scc, sccc, sg, si, sii, sl, ss, su, suy, sw, sy, u - ] + ]?; + + Ok(pm.code().await) } - pub async fn dispatch(&self) -> Result<()> { + pub async fn dispatch(&self) -> Result { self.dispatch_from(self.make_pm(Config::load()?)).await } } diff --git a/src/main.rs b/src/main.rs index bc7d17a6130..5d33558f18e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,12 @@ use pacaptr::print::{print_err, PROMPT_ERROR}; #[tokio::main] async fn main() { let opt = Opt::parse(); - if let Err(e) = opt.dispatch().await { - print_err(e, PROMPT_ERROR); - std::process::exit(1); + match opt.dispatch().await { + Ok(0) => (), + Ok(n) => std::process::exit(n), + Err(e) => { + print_err(e, PROMPT_ERROR); + std::process::exit(1); + } } } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 3ddfb447712..e347ed8ce5b 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -15,9 +15,9 @@ pub mod homebrew; pub mod unknown; use crate::dispatch::config::Config; -use crate::exec::{Cmd, Mode, Output}; -use anyhow::Error; +use crate::exec::{Cmd, Mode, Output, StatusCode}; use anyhow::Result; +use tokio::sync::Mutex; /* macro_rules! make_pm {( @@ -66,17 +66,39 @@ pub trait PackageManager: Sync { /// Get the config of the package manager. fn cfg(&self) -> Config; + /// Get the `StatusCode` to be returned. + async fn code(&self) -> StatusCode { + self._code(None).await + } + + /// Set the `StatusCode` to be returned. + async fn set_code(&self, to: StatusCode) { + self._code(Some(to)).await; + } + + /// Get/Set the `StatusCode` to be returned. + /// If `to` is `Some(n)`, then the current `StatusCode` will be reset to `n`. + /// Then the current `StatusCode` will be returned. + #[doc(hidden)] + async fn _code(&self, to: Option) -> StatusCode { + lazy_static! { + static ref CODE: Mutex = Mutex::new(0); + } + + let mut code = CODE.lock().await; + if let Some(n) = to { + *code = n; + } + + *code + } + /// A helper method to simplify direct command invocation. async fn run(&self, mut cmd: Cmd, mode: PmMode, strat: Strategies) -> Result { let cfg = self.cfg(); // `--dry-run` should apply to both the main command and the cleanup. - async fn body( - cfg: &Config, - cmd: &Cmd, - mode: PmMode, - strat: &Strategies, - ) -> Result { + async fn body(cfg: &Config, cmd: &Cmd, mode: PmMode, strat: &Strategies) -> Result { let mut curr_cmd = cmd.clone(); let no_confirm = cfg.no_confirm; if cfg.no_cache { @@ -121,6 +143,9 @@ pub trait PackageManager: Sync { }; } + // Reset the current status code. + self.set_code(res.code.unwrap_or(1)).await; + Ok(res) } From 41bd6099b4b509b545beddf546bf43928fa6a7ba Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 11:05:38 +0800 Subject: [PATCH 17/28] fix: fix non-zero return code when using `--dryrun` --- Cargo.lock | 20 ++++++++++---------- src/exec.rs | 11 ++++++++++- src/main.rs | 1 - 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3a7232883c..a44f93a2b6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" dependencies = [ "memchr", ] @@ -588,18 +588,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2", "quote", @@ -642,9 +642,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" +checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556" dependencies = [ "proc-macro2", "quote", @@ -731,9 +731,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ "serde", ] diff --git a/src/exec.rs b/src/exec.rs index b4b78726d07..416a12cd602 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -35,7 +35,7 @@ pub enum Mode { pub type StatusCode = i32; /// Representation of what a command returns. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct Output { /// The captured `stdout`, sometimes mixed with captured `stderr`. pub contents: Vec, @@ -43,6 +43,15 @@ pub struct Output { pub code: Option, } +impl Default for Output { + fn default() -> Self { + Output { + contents: Default::default(), + code: Some(0), + } + } +} + /// A command to be executed, provided in `command-keywords-flags` form. /// For example, `[brew install]-[curl fish]-[--dry-run]`). #[derive(Debug, Clone, Default)] diff --git a/src/main.rs b/src/main.rs index 5d33558f18e..4410043c828 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ use pacaptr::print::{print_err, PROMPT_ERROR}; async fn main() { let opt = Opt::parse(); match opt.dispatch().await { - Ok(0) => (), Ok(n) => std::process::exit(n), Err(e) => { print_err(e, PROMPT_ERROR); From 47d6b289232f0c90da27c51cd728b3686a2a88c1 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 19:24:31 +0800 Subject: [PATCH 18/28] refactor: finish `choco` --- src/dispatch/opt.rs | 2 +- src/package_manager/chocolatey.rs | 38 +++++++++++++++++++------------ src/package_manager/mod.rs | 2 +- tests/smoke_test.rs | 11 +++++---- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 93d3af51867..46fdf6c1698 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -213,7 +213,7 @@ impl Opt { #[allow(clippy::match_single_binding)] match pm_str { // Chocolatey - // "choco" => Box::new(chocolatey::Chocolatey { cfg }), + "choco" => Box::new(chocolatey::Chocolatey { cfg }), // Homebrew "brew" if cfg!(target_os = "macos") => Box::new(homebrew::Homebrew { cfg }), diff --git a/src/package_manager/chocolatey.rs b/src/package_manager/chocolatey.rs index 40101f9bc5a..620caa55a7e 100644 --- a/src/package_manager/chocolatey.rs +++ b/src/package_manager/chocolatey.rs @@ -1,7 +1,7 @@ use super::{DryRunStrategy, PackageManager, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::Cmd; +use anyhow::Result; pub struct Chocolatey { pub cfg: Config, @@ -20,12 +20,14 @@ lazy_static! { } impl Chocolatey { - fn check_dry_run(&self, cmd: Cmd) -> Result<(), Error> { + async fn check_dry_run(&self, cmd: Cmd) -> Result<()> { self.just_run(cmd, Default::default(), CHECK_DRY_STRAT.clone()) + .await } } // Windows is so special! It's better not to "sudo" automatically. +#[async_trait] impl PackageManager for Chocolatey { /// Get the name of the package manager. fn name(&self) -> String { @@ -37,35 +39,38 @@ impl PackageManager for Chocolatey { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry_run( Cmd::new(&["choco", "list", "--localonly"]) .kws(kws) .flags(flags), ) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry_run(Cmd::new(&["choco", "outdated"]).kws(kws).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["choco", "uninstall"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rss removes a package and its dependencies which are not required by any other installed package. - fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["choco", "uninstall", "--removedependencies"]) .kws(kws) @@ -73,10 +78,11 @@ impl PackageManager for Chocolatey { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if self.cfg.needed { &["choco", "install"] } else { @@ -87,20 +93,23 @@ impl PackageManager for Chocolatey { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry_run(Cmd::new(&["choco", "info"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry_run(Cmd::new(&["choco", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if kws.is_empty() { &["choco", "upgrade", "all"] } else { @@ -111,10 +120,11 @@ impl PackageManager for Chocolatey { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.su(kws, flags).await } } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index e347ed8ce5b..db277b35694 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,7 +1,6 @@ /* pub mod apk; pub mod apt; -pub mod chocolatey; pub mod conda; pub mod dnf; pub mod linuxbrew; @@ -11,6 +10,7 @@ pub mod tlmgr; pub mod zypper; */ +pub mod chocolatey; pub mod homebrew; pub mod unknown; diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index 94b3f8c14d4..09de79539ad 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -70,40 +70,43 @@ mod homebrew { } } -/* #[cfg(target_os = "windows")] mod chocolatey { use super::Test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "wget"], &[]) .output(&["GNU Wget is a free software package"]) .run(false) + .await } #[test] #[should_panic(expected = "Failed with pattern `GNU Wget is not a free software package`")] - fn si_fail() { + async fn si_fail() { Test::new() .pacaptr(&["-Si", "wget"], &[]) .output(&["GNU Wget is not a free software package"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "wget", "--yes"], &[]) .output(&["The install of wget was successful."]) .pacaptr(&["-R", "wget", "--yes"], &[]) .output(&["Wget has been successfully uninstalled."]) .run(false) + .await } } +/* #[cfg(target_os = "linux")] mod linuxbrew { use super::Test; From 2e7ccfffd69e2a6d338060ffaa68a2e30d098dc8 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 19:36:18 +0800 Subject: [PATCH 19/28] refactor: finish `apk` --- src/dispatch/opt.rs | 2 +- src/package_manager/apk.rs | 98 ++++++++++++++++++++------------- src/package_manager/homebrew.rs | 2 +- src/package_manager/mod.rs | 2 +- tests/smoke_test.rs | 44 +++++++++------ 5 files changed, 90 insertions(+), 58 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 46fdf6c1698..bf41ab66845 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -225,7 +225,7 @@ impl Opt { // "port" if cfg!(target_os = "macos") => Box::new(macports::Macports { cfg }), // Apk for Alpine - // "apk" => Box::new(apk::Apk { cfg }), + "apk" => Box::new(apk::Apk { cfg }), // Apt for Debian/Ubuntu/Termux (new versions) // "apt" => Box::new(apt::Apt { cfg }), diff --git a/src/package_manager/apk.rs b/src/package_manager/apk.rs index 438b856a3fa..588194e9aec 100644 --- a/src/package_manager/apk.rs +++ b/src/package_manager/apk.rs @@ -1,8 +1,8 @@ use super::{NoCacheStrategy, PackageManager, PmMode, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{self, Cmd}; use crate::print::{self, PROMPT_RUN}; +use anyhow::Result; pub struct Apk { pub cfg: Config, @@ -20,6 +20,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Apk { /// Get the name of the package manager. fn name(&self) -> String { @@ -31,82 +32,88 @@ impl PackageManager for Apk { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default(Cmd::new(&["apk", "info"]).flags(flags)) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apk", "info", "-L"]).kws(kws).flags(flags)) + .await } /// Qo queries the package which provides FILE. - fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["apk", "info", "--who-owns"]) .kws(kws) .flags(flags), ) + .await } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str| { exec::grep(contents, kws) .iter() .for_each(|ln| println!("{}", ln)) }; - let search_output = |cmd| { - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self.run(cmd, PmMode::Mute, Default::default())?; - search(&String::from_utf8(out_bytes)?); - Ok(()) - }; - - search_output(&["apk", "info", "-d"]) + let cmd = &["apk", "info", "-d"]; + let cmd = Cmd::new(cmd).flags(flags); + if !self.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + search(&String::from_utf8(out_bytes)?); + Ok(()) } /// Qu lists packages which have an update available. //? Is that the right way to input '<'? - fn qu(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apk", "version", "-l", "<"]).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "del"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rn removes a package and skips the generation of configuration backup files. - fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "del", "--purge"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rns removes a package and its dependencies which are not required by any other installed package, and skips the generation of configuration backup files. - fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "del", "--purge", "-r"]) .kws(kws) @@ -114,73 +121,82 @@ impl PackageManager for Apk { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rs removes a package and its dependencies which are not required by any other installed package, /// and not explicitly installed by the user. - fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.r(kws, flags) + async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.r(kws, flags).await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "add"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "cache", "-v", "clean"]).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Scc removes all files from the cache. - fn scc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn scc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["rm", "-vrf", "/var/cache/apk/*"]).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apk", "info", "-a"]).kws(kws).flags(flags)) + .await } /// Sii displays packages which require X to be installed, aka reverse dependencies. - fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apk", "info", "-r"]).kws(kws).flags(flags)) + .await } /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apk", "search"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apk", "search", "-v"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd = if kws.is_empty() { Cmd::new(&["apk", "upgrade"]).kws(kws).flags(flags) } else { Cmd::new(&["apk", "add", "-u"]).kws(kws).flags(flags) }; self.just_run(cmd, Default::default(), INSTALL_STRAT.clone()) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd = if kws.is_empty() { Cmd::new(&["apk", "upgrade", "-U", "-a"]) .kws(kws) @@ -189,28 +205,31 @@ impl PackageManager for Apk { Cmd::new(&["apk", "add", "-U", "-u"]).kws(kws).flags(flags) }; self.just_run(cmd, Default::default(), INSTALL_STRAT.clone()) + .await } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "fetch"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.just_run_default(Cmd::new(&["apk", "update"]).kws(kws).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.just_run_default(Cmd::new(&["apk", "update"]).kws(kws).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - fn u(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apk", "add", "--allow-untrusted"]) .kws(kws) @@ -218,5 +237,6 @@ impl PackageManager for Apk { Default::default(), INSTALL_STRAT.clone(), ) + .await } } diff --git a/src/package_manager/homebrew.rs b/src/package_manager/homebrew.rs index eece9f3b2f3..4bedc398cbb 100644 --- a/src/package_manager/homebrew.rs +++ b/src/package_manager/homebrew.rs @@ -149,7 +149,7 @@ impl PackageManager for Homebrew { .for_each(|ln| println!("{}", ln)) }; - let cmd: &[&str] = &["brew", "list"]; + let cmd = &["brew", "list"]; let cmd = Cmd::new(cmd).flags(flags); if !self.cfg.dry_run { print::print_cmd(&cmd, PROMPT_RUN); diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index db277b35694..8d0c3057573 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,5 +1,4 @@ /* -pub mod apk; pub mod apt; pub mod conda; pub mod dnf; @@ -10,6 +9,7 @@ pub mod tlmgr; pub mod zypper; */ +pub mod apk; pub mod chocolatey; pub mod homebrew; pub mod unknown; diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index 09de79539ad..9c85a6e8199 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -106,22 +106,22 @@ mod chocolatey { } } -/* #[cfg(target_os = "linux")] mod linuxbrew { use super::Test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "curl"], &[]) .output(&["Get a file from an HTTP, HTTPS or FTP server"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "wget", "--yes"], &[]) .output(&["brew (re)?install wget"]) @@ -130,6 +130,7 @@ mod linuxbrew { .pacaptr(&["-R", "wget", "--yes"], &[]) .output(&["brew uninstall wget"]) .run(false) + .await } } @@ -138,25 +139,27 @@ mod apt { use super::Test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "screen"], &[]) .output(&["Package: screen"]) .run(false) + .await } #[test] #[should_panic(expected = "Failed with pattern `Package: wget`")] - fn si_fail() { + async fn si_fail() { Test::new() .pacaptr(&["-Si", "screen"], &[]) .output(&["Package: wget"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "screen", "--yes"], &[]) .output(&["apt(-get)? install", "--reinstall", "--yes", "screen"]) @@ -167,6 +170,7 @@ mod apt { .pacaptr(&["-Qi", "screen"], &[]) .output(&["Status: deinstall"]) .run(false) + .await } } @@ -175,25 +179,27 @@ mod apk { use super::Test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "wget"], &[]) .output(&["A network utility to retrieve files from the Web"]) .run(false) + .await } #[test] #[should_panic(expected = "Failed with pattern `Why not use curl instead?`")] - fn si_fail() { + async fn si_fail() { Test::new() .pacaptr(&["-Si", "wget"], &[]) .output(&["Why not use curl instead?"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "wget", "--yes"], &[]) .output(&["Installing wget"]) @@ -202,6 +208,7 @@ mod apk { .pacaptr(&["-R", "wget", "--yes"], &[]) .output(&["Purging wget"]) .run(false) + .await } } @@ -210,25 +217,27 @@ mod dnf { use super::Test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "curl"], &[]) .output(&["A utility for getting files from remote servers"]) .run(false) + .await } #[test] #[should_panic(expected = "Failed with pattern `Why not use curl instead?`")] - fn si_fail() { + async fn si_fail() { Test::new() .pacaptr(&["-Si", "wget"], &[]) .output(&["Why not use curl instead?"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "wget", "--yes"], &[]) .output(&["dnf install", "-y", "wget", "Installed:", "Complete!"]) @@ -237,6 +246,7 @@ mod dnf { .pacaptr(&["-R", "wget", "--yes"], &[]) .output(&["dnf remove", "-y", "wget", "Removed:", "Complete!"]) .run(false) + .await } } @@ -245,25 +255,27 @@ mod zypper { use super::Test; #[test] - fn si_ok() { + async fn si_ok() { Test::new() .pacaptr(&["-Si", "curl"], &[]) .output(&["A Tool for Transferring Data from URLs"]) .run(false) + .await } #[test] #[should_panic(expected = "Failed with pattern `Why not use curl instead?`")] - fn si_fail() { + async fn si_fail() { Test::new() .pacaptr(&["-Si", "wget"], &[]) .output(&["Why not use curl instead?"]) .run(false) + .await } #[test] #[ignore] - fn r() { + async fn r() { Test::new() .pacaptr(&["-S", "wget", "--yes"], &[]) .output(&["zypper install", "-y", "wget", "Installing: wget"]) @@ -272,6 +284,6 @@ mod zypper { .pacaptr(&["-R", "wget", "--yes"], &[]) .output(&["zypper remove", "-y", "wget", "Removing wget"]) .run(false) + .await } } -*/ From dce8a2093890fde64e37ba95c3066f3499f7f113 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 19:41:06 +0800 Subject: [PATCH 20/28] refactor: finish `apt` --- src/dispatch/opt.rs | 2 +- src/package_manager/apt.rs | 72 ++++++++++++++++++++++++-------------- src/package_manager/mod.rs | 2 +- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index bf41ab66845..83ce4563b73 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -228,7 +228,7 @@ impl Opt { "apk" => Box::new(apk::Apk { cfg }), // Apt for Debian/Ubuntu/Termux (new versions) - // "apt" => Box::new(apt::Apt { cfg }), + "apt" => Box::new(apt::Apt { cfg }), // Dnf for RedHat // "dnf" => Box::new(dnf::Dnf { cfg }), diff --git a/src/package_manager/apt.rs b/src/package_manager/apt.rs index 7e3bc74c6e9..0fec9fe642f 100644 --- a/src/package_manager/apt.rs +++ b/src/package_manager/apt.rs @@ -1,7 +1,7 @@ use super::{NoCacheStrategy, PackageManager, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::Cmd; +use anyhow::Result; pub struct Apt { pub cfg: Config, @@ -19,6 +19,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Apt { /// Get the name of the package manager. fn name(&self) -> String { @@ -30,54 +31,61 @@ impl PackageManager for Apt { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apt", "list"]).kws(kws).flags(flags)) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dpkg-query", "-s"]).kws(kws).flags(flags)) + .await } /// Qo queries the package which provides FILE. - fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dpkg-query", "-S"]).kws(kws).flags(flags)) + .await } /// Qp queries a package supplied on the command line rather than an entry in the package management database. - fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dpkg-deb", "-I"]).kws(kws).flags(flags)) + .await } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["apt", "upgrade", "--trivial-only"]) .kws(kws) .flags(flags), ) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apt", "remove"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rn removes a package and skips the generation of configuration backup files. - fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rn(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apt", "purge"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rns removes a package and its dependencies which are not required by any other installed package, and skips the generation of configuration backup files. - fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rns(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apt", "autoremove", "--purge"]) .kws(kws) @@ -85,20 +93,22 @@ impl PackageManager for Apt { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rs removes a package and its dependencies which are not required by any other installed package, /// and not explicitly installed by the user. - fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apt", "autoremove"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if self.cfg.needed { &["apt", "install"] } else { @@ -109,79 +119,89 @@ impl PackageManager for Apt { Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apt", "clean"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Scc removes all files from the cache. - fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["apt", "autoclean"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apt", "show"]).kws(kws).flags(flags)) + .await } /// Sii displays packages which require X to be installed, aka reverse dependencies. - fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apt", "rdepends"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["apt", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run( Cmd::new(&["apt", "upgrade"]).flags(flags), Default::default(), PROMPT_STRAT.clone(), - )?; + ) + .await?; self.just_run( Cmd::new(&["apt", "dist-upgrade"]).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } else { - self.s(kws, flags) + self.s(kws, flags).await } } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.sy(kws, flags)?; - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.sy(kws, flags).await?; + self.su(kws, flags).await } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["apt", "install", "--download-only"]) .kws(kws) .flags(flags), ) + .await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.just_run_default(Cmd::new(&["apt", "update"]).kws(kws).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.just_run_default(Cmd::new(&["apt", "update"]).kws(kws).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 8d0c3057573..1f8b539f6a4 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,5 +1,4 @@ /* -pub mod apt; pub mod conda; pub mod dnf; pub mod linuxbrew; @@ -10,6 +9,7 @@ pub mod zypper; */ pub mod apk; +pub mod apt; pub mod chocolatey; pub mod homebrew; pub mod unknown; From 1dcbb6864d721f09f1f53761a56ace8a795f0d53 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:15:06 +0800 Subject: [PATCH 21/28] refactor: finish `conda` --- src/dispatch/opt.rs | 2 +- src/package_manager/conda.rs | 64 +++++++++++++++++++++--------------- src/package_manager/mod.rs | 2 +- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 83ce4563b73..8bed7084159 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -239,7 +239,7 @@ impl Opt { // * External Package Managers * // Conda - // "conda" => Box::new(conda::Conda { cfg }), + "conda" => Box::new(conda::Conda { cfg }), // Pip /* diff --git a/src/package_manager/conda.rs b/src/package_manager/conda.rs index f40a12d2d92..fea02aa7c9c 100644 --- a/src/package_manager/conda.rs +++ b/src/package_manager/conda.rs @@ -1,8 +1,9 @@ use super::{PackageManager, PmMode, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{self, Cmd}; use crate::print::{self, PROMPT_RUN}; +use anyhow::Result; +use futures::stream::{self, TryStreamExt}; pub struct Conda { pub cfg: Config, @@ -15,6 +16,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Conda { /// Get the name of the package manager. fn name(&self) -> String { @@ -26,83 +28,92 @@ impl PackageManager for Conda { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default(Cmd::new(&["conda", "list"]).flags(flags)) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str| { exec::grep(contents, kws) .iter() .for_each(|ln| println!("{}", ln)) }; - let search_output = |cmd| { - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self.run(cmd, PmMode::Mute, Default::default())?; - search(&String::from_utf8(out_bytes)?); - Ok(()) - }; - - search_output(&["conda", "list"]) + let cmd = &["conda", "list"]; + let cmd = Cmd::new(cmd).flags(flags); + if !self.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + search(&String::from_utf8(out_bytes)?); + Ok(()) } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["conda", "remove"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["conda", "install"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["conda", "clean", "--all"]).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["conda", "search", "--info"]) .kws(kws) .flags(flags), ) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let kws: Vec = kws.iter().map(|&s| format!("*{}*", s)).collect(); - kws.iter() - .map(|kw| self.just_run_default(Cmd::new(&["conda", "search"]).kws(&[kw]).flags(flags))) - .collect() + + stream::iter(kws.iter().map(Ok)) + .try_for_each(|kw| async move { + self.just_run_default(Cmd::new(&["conda", "search"]).kws(&[kw]).flags(flags)) + .await + }) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["conda", "update", "--all"]) .kws(kws) @@ -110,10 +121,11 @@ impl PackageManager for Conda { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.su(kws, flags).await } } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 1f8b539f6a4..c06531b6134 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,5 +1,4 @@ /* -pub mod conda; pub mod dnf; pub mod linuxbrew; pub mod macports; @@ -11,6 +10,7 @@ pub mod zypper; pub mod apk; pub mod apt; pub mod chocolatey; +pub mod conda; pub mod homebrew; pub mod unknown; From 2495f285770a3f0a16a20fb423167a0b6eddc133 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:15:30 +0800 Subject: [PATCH 22/28] fix: fix behavior of `homebrew_Qs` --- src/package_manager/homebrew.rs | 36 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/package_manager/homebrew.rs b/src/package_manager/homebrew.rs index 4bedc398cbb..c3c04bfac43 100644 --- a/src/package_manager/homebrew.rs +++ b/src/package_manager/homebrew.rs @@ -143,22 +143,30 @@ impl PackageManager for Homebrew { // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { - let search = |contents: &str| { - exec::grep(contents, kws) - .iter() - .for_each(|ln| println!("{}", ln)) + async fn run(self_: &Homebrew, cmd: &[&str], kws: &[&str], flags: &[&str]) -> Result<()> { + let search = |contents: &str| { + exec::grep(contents, kws) + .iter() + .for_each(|ln| println!("{}", ln)) + }; + + let cmd = Cmd::new(cmd).flags(flags); + if !self_.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self_ + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + + search(&String::from_utf8(out_bytes)?); + Ok(()) }; - let cmd = &["brew", "list"]; - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self - .run(cmd, PmMode::Mute, Default::default()) - .await? - .contents; - search(&String::from_utf8(out_bytes)?); + // ! `brew list` lists all formulae and casks only when using tty. + run(self, &["brew", "list"], kws, flags).await?; + run(self, &["brew", "list", "--cask"], kws, flags).await?; + Ok(()) } From c7b93b3a7bfd458673c36e8253e1107e232091fa Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:34:55 +0800 Subject: [PATCH 23/28] refactor: finish `dnf` and `zypper` --- src/dispatch/opt.rs | 4 +- src/package_manager/dnf.rs | 107 ++++++++++++++++++++-------------- src/package_manager/mod.rs | 4 +- src/package_manager/zypper.rs | 96 ++++++++++++++++++------------ 4 files changed, 126 insertions(+), 85 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index 8bed7084159..b208e8d455e 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -231,10 +231,10 @@ impl Opt { "apt" => Box::new(apt::Apt { cfg }), // Dnf for RedHat - // "dnf" => Box::new(dnf::Dnf { cfg }), + "dnf" => Box::new(dnf::Dnf { cfg }), // Zypper for SUSE - // "zypper" => Box::new(zypper::Zypper { cfg }), + "zypper" => Box::new(zypper::Zypper { cfg }), // * External Package Managers * diff --git a/src/package_manager/dnf.rs b/src/package_manager/dnf.rs index 3ce0747f23d..ba51903704e 100644 --- a/src/package_manager/dnf.rs +++ b/src/package_manager/dnf.rs @@ -1,8 +1,8 @@ use super::{NoCacheStrategy, PackageManager, PmMode, PromptStrategy, Strategies}; -use crate::error::Error; use crate::exec::{self, Cmd}; use crate::print::PROMPT_RUN; use crate::{dispatch::config::Config, print}; +use anyhow::Result; pub struct Dnf { pub cfg: Config, @@ -20,6 +20,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Dnf { /// Get the name of the package manager. fn name(&self) -> String { @@ -31,104 +32,114 @@ impl PackageManager for Dnf { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default( Cmd::new(&["rpm", "-qa", "--qf", "%{NAME} %{VERSION}\\n"]).flags(flags), ) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } /// Qc shows the changelog of a package. - fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-q", "changelog"]).kws(kws).flags(flags)) + .await } /// Qe lists packages installed explicitly (not as dependencies). - fn qe(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qe(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["dnf", "repoquery", "--userinstalled"]) .kws(kws) .flags(flags), ) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-ql"]).kws(kws).flags(flags)) + .await } /// Qm lists packages that are installed but are not available in any installation source (anymore). - fn qm(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qm(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dnf", "list", "extras"]).flags(flags)) + .await } /// Qo queries the package which provides FILE. - fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-qf"]).kws(kws).flags(flags)) + .await } /// Qp queries a package supplied on the command line rather than an entry in the package management database. - fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-qip"]).kws(kws).flags(flags)) + .await } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. // TODO: Is this right? - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str| { exec::grep(contents, kws) .iter() .for_each(|ln| println!("{}", ln)) }; - let search_output = |cmd| { - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self.run(cmd, PmMode::Mute, Default::default())?; - search(&String::from_utf8(out_bytes)?); - Ok(()) - }; - - search_output(&["rpm", "-qa"]) + let cmd = &["rpm", "-qa"]; + let cmd = Cmd::new(cmd).flags(flags); + if !self.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + search(&String::from_utf8(out_bytes)?); + Ok(()) } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dnf", "list", "updates"]).kws(kws).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "remove"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "install"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "clean", "expire-cache"]).flags(flags), Default::default(), @@ -137,10 +148,11 @@ impl PackageManager for Dnf { ..Default::default() }, ) + .await } /// Scc removes all files from the cache. - fn scc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn scc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "clean", "packages"]).flags(flags), Default::default(), @@ -149,11 +161,12 @@ impl PackageManager for Dnf { ..Default::default() }, ) + .await } /// Sccc ... /// What is this? - fn sccc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sccc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "clean", "all"]).flags(flags), Default::default(), @@ -162,72 +175,80 @@ impl PackageManager for Dnf { ..Default::default() }, ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dnf", "info"]).kws(kws).flags(flags)) + .await } /// Sg lists all packages belonging to the GROUP. - fn sg(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sg(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if kws.is_empty() { &["dnf", "group", "list"] } else { &["dnf", "group", "info"] }; self.just_run_default(Cmd::new(cmd).kws(kws).flags(flags)) + .await } /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["dnf", "list", "available"]) .kws(kws) .flags(flags), ) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["dnf", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "upgrade"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.su(kws, flags).await } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["dnf", "download"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.sc(&[], flags)?; - self.just_run_default(Cmd::new(&["dnf", "check-update"]).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.sc(&[], flags).await?; + self.just_run_default(Cmd::new(&["dnf", "check-update"]).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - fn u(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.s(kws, flags) + async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.s(kws, flags).await } } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index c06531b6134..20687bb5599 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,18 +1,18 @@ /* -pub mod dnf; pub mod linuxbrew; pub mod macports; pub mod pip; pub mod tlmgr; -pub mod zypper; */ pub mod apk; pub mod apt; pub mod chocolatey; pub mod conda; +pub mod dnf; pub mod homebrew; pub mod unknown; +pub mod zypper; use crate::dispatch::config::Config; use crate::exec::{Cmd, Mode, Output, StatusCode}; diff --git a/src/package_manager/zypper.rs b/src/package_manager/zypper.rs index 0147343fc30..0bc9c3767a2 100644 --- a/src/package_manager/zypper.rs +++ b/src/package_manager/zypper.rs @@ -1,15 +1,16 @@ use super::{DryRunStrategy, NoCacheStrategy, PackageManager, PmMode, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{self, Cmd}; +use anyhow::Result; pub struct Zypper { pub cfg: Config, } impl Zypper { - fn check_dry(&self, cmd: Cmd) -> Result<(), Error> { + async fn check_dry(&self, cmd: Cmd) -> Result<()> { self.just_run(cmd, Default::default(), CHECK_DRY_STRAT.clone()) + .await } } @@ -30,6 +31,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Zypper { /// Get the name of the package manager. fn name(&self) -> String { @@ -41,44 +43,48 @@ impl PackageManager for Zypper { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default( Cmd::new(&["rpm", "-qa", "--qf", "%{NAME} %{VERSION}\\n"]).flags(flags), ) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } /// Qc shows the changelog of a package. - fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-q", "changelog"]).kws(kws).flags(flags)) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-ql"]).kws(kws).flags(flags)) + .await } /// Qm lists packages that are installed but are not available in any installation source (anymore). - fn qm(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qm(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str, pattern: &str| { exec::grep(contents, &[pattern]) .iter() .for_each(|ln| println!("{}", ln)) }; - let out_bytes = self.run( - Cmd::new(&["zypper", "search", "-si"]).kws(kws).flags(flags), - PmMode::Mute, - Default::default(), - )?; + let cmd = &["zypper", "search", "-si"]; + let cmd = Cmd::new(cmd).kws(kws).flags(flags); + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; let out = String::from_utf8(out_bytes)?; search(&out, "System Packages"); @@ -86,42 +92,47 @@ impl PackageManager for Zypper { } /// Qo queries the package which provides FILE. - fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-qf"]).kws(kws).flags(flags)) + .await } /// Qp queries a package supplied on the command line rather than an entry in the package management database. - fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qp(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["rpm", "-qip"]).kws(kws).flags(flags)) + .await } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry( Cmd::new(&["zypper", "search", "--installed-only"]) .kws(kws) .flags(flags), ) + .await } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry(Cmd::new(&["zypper", "list-updates"]).kws(kws).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["zypper", "remove"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rss removes a package and its dependencies which are not required by any other installed package. - fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["zypper", "remove", "--clean-deps"]) .kws(kws) @@ -129,19 +140,21 @@ impl PackageManager for Zypper { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["zypper", "install"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["zypper", "clean"]).flags(flags), Default::default(), @@ -150,57 +163,62 @@ impl PackageManager for Zypper { ..Default::default() }, ) + .await } /// Scc removes all files from the cache. - fn scc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.sc(_kws, flags) + async fn scc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { + self.sc(_kws, flags).await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry( Cmd::new(&["zypper", "info", "--requires"]) .kws(kws) .flags(flags), ) + .await } /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if kws.is_empty() { &["zypper", "packages", "-R"] } else { &["zypper", "packages", "-r"] }; - self.check_dry(Cmd::new(cmd).kws(kws).flags(flags)) + self.check_dry(Cmd::new(cmd).kws(kws).flags(flags)).await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.check_dry(Cmd::new(&["zypper", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.check_dry(Cmd::new(&["zypper", "--no-refresh", "dist-upgrade"]).flags(flags))?; + async fn su(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { + self.check_dry(Cmd::new(&["zypper", "--no-refresh", "dist-upgrade"]).flags(flags)) + .await?; if self.cfg.no_cache { - self.sccc(_kws, flags)?; + self.sccc(_kws, flags).await?; } Ok(()) } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn suy(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["zypper", "dist-upgrade"]).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["zypper", "install", "--download-only"]) .kws(kws) @@ -208,19 +226,21 @@ impl PackageManager for Zypper { Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.check_dry(Cmd::new(&["zypper", "refresh"]).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.check_dry(Cmd::new(&["zypper", "refresh"]).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - fn u(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.s(kws, flags) + async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.s(kws, flags).await } } From 1d0e9e3c010ceea053925329331498220480a4b0 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:45:33 +0800 Subject: [PATCH 24/28] refactor: finish `pip` and `tlmgr` --- src/dispatch/opt.rs | 5 ++- src/package_manager/mod.rs | 4 +-- src/package_manager/pip.rs | 69 +++++++++++++++++++++--------------- src/package_manager/tlmgr.rs | 41 +++++++++++++-------- 4 files changed, 71 insertions(+), 48 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index b208e8d455e..b9aa93cd396 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -242,7 +242,6 @@ impl Opt { "conda" => Box::new(conda::Conda { cfg }), // Pip - /* "pip" => Box::new(pip::Pip { cmd: "pip".into(), cfg, @@ -251,9 +250,9 @@ impl Opt { cmd: "pip3".into(), cfg, }), - */ + // Tlmgr - // "tlmgr" => Box::new(tlmgr::Tlmgr { cfg }), + "tlmgr" => Box::new(tlmgr::Tlmgr { cfg }), // Unknown package manager X x => Box::new(unknown::Unknown { name: x.into() }), diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 20687bb5599..148e98ca478 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,8 +1,6 @@ /* pub mod linuxbrew; pub mod macports; -pub mod pip; -pub mod tlmgr; */ pub mod apk; @@ -11,6 +9,8 @@ pub mod chocolatey; pub mod conda; pub mod dnf; pub mod homebrew; +pub mod pip; +pub mod tlmgr; pub mod unknown; pub mod zypper; diff --git a/src/package_manager/pip.rs b/src/package_manager/pip.rs index 71c76d775e2..7747b63b4db 100644 --- a/src/package_manager/pip.rs +++ b/src/package_manager/pip.rs @@ -1,8 +1,8 @@ use super::{PackageManager, PmMode, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{self, Cmd}; use crate::print::{self, PROMPT_RUN}; +use anyhow::Result; pub struct Pip { pub cmd: String, @@ -16,105 +16,118 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Pip { /// Get the name of the package manager. fn name(&self) -> String { "pip".into() } + fn cfg(&self) -> Config { + self.cfg.clone() + } + /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default(Cmd::new(&[&self.cmd, "list"]).flags(flags)) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } - fn cfg(&self) -> Config { - self.cfg.clone() - } - /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str| { exec::grep(contents, kws) .iter() .for_each(|ln| println!("{}", ln)) }; - let search_output = |cmd| { - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self.run(cmd, PmMode::Mute, Default::default())?; - search(&String::from_utf8(out_bytes)?); - Ok(()) - }; - - search_output(&[&self.cmd, "list"]) + let cmd = &[&self.cmd, "list"]; + let cmd = Cmd::new(cmd).flags(flags); + if !self.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + search(&String::from_utf8(out_bytes)?); + Ok(()) } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&[&self.cmd, "list", "--outdated"]) .kws(kws) .flags(flags), ) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&[&self.cmd, "uninstall"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&[&self.cmd, "install"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&[&self.cmd, "cache", "purge"]).flags(flags)) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&[&self.cmd, "show"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&[&self.cmd, "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if !kws.is_empty() { self.just_run_default( Cmd::new(&[&self.cmd, "install", "--upgrade"]) .kws(kws) .flags(flags), ) + .await } else { - Err(format!("Operation `su` unimplemented for `{}`", self.name()).into()) + Err(anyhow!( + "Operation `su` unimplemented for `{}`", + self.name() + )) } } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&[&self.cmd, "download"]).kws(kws).flags(flags)) + .await } } diff --git a/src/package_manager/tlmgr.rs b/src/package_manager/tlmgr.rs index 87f38850ded..3ff5dff9b20 100644 --- a/src/package_manager/tlmgr.rs +++ b/src/package_manager/tlmgr.rs @@ -1,7 +1,7 @@ use super::{DryRunStrategy, PackageManager, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::Cmd; +use anyhow::Result; pub struct Tlmgr { pub cfg: Config, @@ -14,6 +14,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Tlmgr { /// Get the name of the package manager. fn name(&self) -> String { @@ -25,72 +26,80 @@ impl PackageManager for Tlmgr { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.qi(kws, flags) + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.qi(kws, flags).await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["tlmgr", "info", "--only-installed"]) .kws(kws) .flags(flags), ) + .await } /// Qk verifies one or more packages. - fn qk(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qk(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["tlmgr", "check", "files"]).flags(flags)) + .await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["tlmgr", "info", "--only-installed", "--list"]) .kws(kws) .flags(flags), ) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["tlmgr", "remove"]).kws(kws).flags(flags), Default::default(), CHECK_DRY_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["tlmgr", "install"]).kws(kws).flags(flags), Default::default(), CHECK_DRY_STRAT.clone(), ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["tlmgr", "info"]).kws(kws).flags(flags)) + .await } /// Sl displays a list of all packages in all installation sources that are handled by the packages management. - fn sl(&self, _kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sl(&self, _kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["tlmgr", "info"]).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default( Cmd::new(&["tlmgr", "search", "--global"]) .kws(kws) .flags(flags), ) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if kws.is_empty() { &["tlmgr", "update", "--self", "--all"] } else { @@ -101,15 +110,16 @@ impl PackageManager for Tlmgr { Default::default(), CHECK_DRY_STRAT.clone(), ) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.su(kws, flags).await } /// U upgrades or adds package(s) to the system and installs the required dependencies from sync repositories. - fn u(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn u(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["tlmgr", "install", "--file"]) .kws(kws) @@ -117,5 +127,6 @@ impl PackageManager for Tlmgr { Default::default(), CHECK_DRY_STRAT.clone(), ) + .await } } From e02d402c1bcae6dbbbfa3dc8034052c50306fd61 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:50:36 +0800 Subject: [PATCH 25/28] refactor: finish `macports` --- src/dispatch/opt.rs | 2 +- src/package_manager/macports.rs | 62 +++++++++++++++++++++------------ src/package_manager/mod.rs | 2 +- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index b9aa93cd396..aa38df17a15 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -222,7 +222,7 @@ impl Opt { // "brew" => Box::new(linuxbrew::Linuxbrew { cfg }), // Macports - // "port" if cfg!(target_os = "macos") => Box::new(macports::Macports { cfg }), + "port" if cfg!(target_os = "macos") => Box::new(macports::Macports { cfg }), // Apk for Alpine "apk" => Box::new(apk::Apk { cfg }), diff --git a/src/package_manager/macports.rs b/src/package_manager/macports.rs index 6fdcb171239..6099d1efe60 100644 --- a/src/package_manager/macports.rs +++ b/src/package_manager/macports.rs @@ -1,7 +1,7 @@ use super::{NoCacheStrategy, PackageManager, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::Cmd; +use anyhow::Result; pub struct Macports { pub cfg: Config, @@ -19,6 +19,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Macports { /// Get the name of the package manager. fn name(&self) -> String { @@ -30,53 +31,60 @@ impl PackageManager for Macports { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "installed"]).kws(kws).flags(flags)) + .await } /// Qc shows the changelog of a package. - fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "log"]).kws(kws).flags(flags)) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "contents"]).kws(kws).flags(flags)) + .await } /// Qo queries the package which provides FILE. - fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "provides"]).kws(kws).flags(flags)) + .await } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "-v", "installed"]).kws(kws).flags(flags)) + .await } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "outdated"]).kws(kws).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new_sudo(&["port", "uninstall"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rss removes a package and its dependencies which are not required by any other installed package. - fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new_sudo(&["port", "uninstall", "--follow-dependencies"]) .kws(kws) @@ -84,19 +92,21 @@ impl PackageManager for Macports { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new_sudo(&["port", "install"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if flags.is_empty() { &["port", "clean", "--all", "inactive"] } else { @@ -107,10 +117,11 @@ impl PackageManager for Macports { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Scc removes all files from the cache. - fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if flags.is_empty() { &["port", "clean", "--all", "installed"] } else { @@ -121,20 +132,23 @@ impl PackageManager for Macports { Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "info"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["port", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd: &[&str] = if flags.is_empty() { &["port", "upgrade", "outdated"] } else { @@ -145,19 +159,21 @@ impl PackageManager for Macports { Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.sy(&[], flags)?; - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.sy(&[], flags).await?; + self.su(kws, flags).await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.just_run_default(Cmd::new(&["port", "selfupdate"]).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.just_run_default(Cmd::new(&["port", "selfupdate"]).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 148e98ca478..adcded52e50 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,6 +1,5 @@ /* pub mod linuxbrew; -pub mod macports; */ pub mod apk; @@ -9,6 +8,7 @@ pub mod chocolatey; pub mod conda; pub mod dnf; pub mod homebrew; +pub mod macports; pub mod pip; pub mod tlmgr; pub mod unknown; From f03cb055bbea8418a712b29d2946638acb6b22c3 Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:56:26 +0800 Subject: [PATCH 26/28] refactor: finish `linuxbrew` --- src/dispatch/opt.rs | 2 +- src/package_manager/linuxbrew.rs | 108 ++++++++++++++++++------------- src/package_manager/mod.rs | 5 +- 3 files changed, 65 insertions(+), 50 deletions(-) diff --git a/src/dispatch/opt.rs b/src/dispatch/opt.rs index aa38df17a15..4609cb5321f 100644 --- a/src/dispatch/opt.rs +++ b/src/dispatch/opt.rs @@ -219,7 +219,7 @@ impl Opt { "brew" if cfg!(target_os = "macos") => Box::new(homebrew::Homebrew { cfg }), // Linuxbrew - // "brew" => Box::new(linuxbrew::Linuxbrew { cfg }), + "brew" => Box::new(linuxbrew::Linuxbrew { cfg }), // Macports "port" if cfg!(target_os = "macos") => Box::new(macports::Macports { cfg }), diff --git a/src/package_manager/linuxbrew.rs b/src/package_manager/linuxbrew.rs index f314dad94b6..6e0766a1344 100644 --- a/src/package_manager/linuxbrew.rs +++ b/src/package_manager/linuxbrew.rs @@ -1,8 +1,8 @@ use super::{DryRunStrategy, NoCacheStrategy, PackageManager, PmMode, PromptStrategy, Strategies}; use crate::dispatch::config::Config; -use crate::error::Error; use crate::exec::{self, Cmd}; use crate::print::{self, PROMPT_INFO, PROMPT_RUN}; +use anyhow::Result; pub struct Linuxbrew { pub cfg: Config, @@ -20,6 +20,7 @@ lazy_static! { }; } +#[async_trait] impl PackageManager for Linuxbrew { /// Get the name of the package manager. fn name(&self) -> String { @@ -31,78 +32,86 @@ impl PackageManager for Linuxbrew { } /// Q generates a list of installed packages. - fn q(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> { if kws.is_empty() { self.just_run_default(Cmd::new(&["brew", "list"]).flags(flags)) + .await } else { - self.qs(kws, flags) + self.qs(kws, flags).await } } /// Qc shows the changelog of a package. - fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "log"]).kws(kws).flags(flags)) + .await } /// Qi displays local package information: name, version, description, etc. - fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.si(kws, flags) + async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.si(kws, flags).await } /// Ql displays files provided by local package. - fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> { // TODO: it seems that the output of `brew list python` in fish has a mechanism against duplication: // /usr/local/Cellar/python/3.6.0/Frameworks/Python.framework/ (1234 files) self.just_run_default(Cmd::new(&["brew", "list"]).kws(kws).flags(flags)) + .await } /// Qs searches locally installed package for names or descriptions. // According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a, // when including multiple search terms, only packages with descriptions matching ALL of those terms are returned. - fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let search = |contents: &str| { exec::grep(contents, kws) .iter() .for_each(|ln| println!("{}", ln)) }; - let search_output = |cmd| { - let cmd = Cmd::new(cmd).flags(flags); - if !self.cfg.dry_run { - print::print_cmd(&cmd, PROMPT_RUN); - } - let out_bytes = self.run(cmd, PmMode::Mute, Default::default())?; - search(&String::from_utf8(out_bytes)?); - Ok(()) - }; - - search_output(&["brew", "list"]) + let cmd = &["brew", "list"]; + let cmd = Cmd::new(cmd).flags(flags); + if !self.cfg.dry_run { + print::print_cmd(&cmd, PROMPT_RUN); + } + let out_bytes = self + .run(cmd, PmMode::Mute, Default::default()) + .await? + .contents; + search(&String::from_utf8(out_bytes)?); + Ok(()) } /// Qu lists packages which have an update available. - fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "outdated"]).kws(kws).flags(flags)) + .await } /// R removes a single package, leaving all of its dependencies installed. - fn r(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "uninstall"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Rss removes a package and its dependencies which are not required by any other installed package. - fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - let err_bytes = self.run( - Cmd::new(&["brew", "rmtree"]).kws(kws).flags(flags), - Default::default(), - Strategies { - dry_run: DryRunStrategy::with_flags(&["--dry-run"]), - ..Default::default() - }, - )?; + async fn rss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + let err_bytes = self + .run( + Cmd::new(&["brew", "rmtree"]).kws(kws).flags(flags), + Default::default(), + Strategies { + dry_run: DryRunStrategy::with_flags(&["--dry-run"]), + ..Default::default() + }, + ) + .await? + .contents; let err_msg = String::from_utf8(err_bytes)?; let pattern = "Unknown command: rmtree"; @@ -112,14 +121,14 @@ impl PackageManager for Linuxbrew { PROMPT_INFO, ); print::print_msg("`brew tap beeftornado/rmtree`", PROMPT_INFO); - return Err("`rmtree` required".into()); + return Err(anyhow!("`rmtree` required")); } Ok(()) } /// S installs one or more packages by name. - fn s(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> { let cmd = if self.cfg.needed { &["brew", "install"] } else { @@ -132,10 +141,11 @@ impl PackageManager for Linuxbrew { Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Sc removes all the cached packages that are not currently installed, and the unused sync database. - fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "cleanup"]).kws(kws).flags(flags), Default::default(), @@ -145,10 +155,11 @@ impl PackageManager for Linuxbrew { ..Default::default() }, ) + .await } /// Scc removes all files from the cache. - fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn scc(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "cleanup", "-s"]).kws(kws).flags(flags), Default::default(), @@ -158,52 +169,59 @@ impl PackageManager for Linuxbrew { ..Default::default() }, ) + .await } /// Si displays remote package information: name, version, description, etc. - fn si(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "info"]).kws(kws).flags(flags)) + .await } /// Sii displays packages which require X to be installed, aka reverse dependencies. - fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "uses"]).kws(kws).flags(flags)) + .await } /// Ss searches for package(s) by searching the expression in name, description, short description. - fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run_default(Cmd::new(&["brew", "search"]).kws(kws).flags(flags)) + .await } /// Su updates outdated packages. - fn su(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "upgrade"]).kws(kws).flags(flags), Default::default(), INSTALL_STRAT.clone(), ) + .await } /// Suy refreshes the local package database, then updates outdated packages. - fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.sy(&[], flags)?; - self.su(kws, flags) + async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.sy(&[], flags).await?; + self.su(kws, flags).await } /// Sw retrieves all packages from the server, but does not install/upgrade anything. - fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { + async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> { self.just_run( Cmd::new(&["brew", "fetch"]).kws(kws).flags(flags), Default::default(), PROMPT_STRAT.clone(), ) + .await } /// Sy refreshes the local package database. - fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<(), Error> { - self.just_run_default(Cmd::new(&["brew", "update"]).flags(flags))?; + async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> { + self.just_run_default(Cmd::new(&["brew", "update"]).flags(flags)) + .await?; if !kws.is_empty() { - self.s(kws, flags)?; + self.s(kws, flags).await?; } Ok(()) } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index adcded52e50..cab6ae199c1 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,13 +1,10 @@ -/* -pub mod linuxbrew; -*/ - pub mod apk; pub mod apt; pub mod chocolatey; pub mod conda; pub mod dnf; pub mod homebrew; +pub mod linuxbrew; pub mod macports; pub mod pip; pub mod tlmgr; From 63e7a3854dc0c4c8656a37e0f94434b9376a796d Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 20:59:25 +0800 Subject: [PATCH 27/28] chore(test): enable CI tests on PR --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92a3de8aaa5..ace4d008117 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,9 @@ name: test on: push: - branches: [master, dev] + branches: [master] + pull_request: + branches: [master] jobs: windows-test: From e0008a404340d08d731bcea6b9077c1261c15deb Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 19 Oct 2020 21:08:53 +0800 Subject: [PATCH 28/28] test: fix async test --- tests/smoke_test.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index 9c85a6e8199..0407a81e75b 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -73,6 +73,7 @@ mod homebrew { #[cfg(target_os = "windows")] mod chocolatey { use super::Test; + use tokio::test; #[test] async fn si_ok() { @@ -109,6 +110,7 @@ mod chocolatey { #[cfg(target_os = "linux")] mod linuxbrew { use super::Test; + use tokio::test; #[test] async fn si_ok() { @@ -137,6 +139,7 @@ mod linuxbrew { #[cfg(target_os = "linux")] mod apt { use super::Test; + use tokio::test; #[test] async fn si_ok() { @@ -177,6 +180,7 @@ mod apt { #[cfg(target_os = "linux")] mod apk { use super::Test; + use tokio::test; #[test] async fn si_ok() { @@ -215,6 +219,7 @@ mod apk { #[cfg(target_os = "linux")] mod dnf { use super::Test; + use tokio::test; #[test] async fn si_ok() { @@ -253,6 +258,7 @@ mod dnf { #[cfg(target_os = "linux")] mod zypper { use super::Test; + use tokio::test; #[test] async fn si_ok() {