From 2266b10a7b7620d96018f76a1c9e15aa69a24bcb Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 17 Apr 2022 14:24:42 +0200 Subject: [PATCH 1/3] Encapsulate command executions Signed-off-by: Trivernis --- src/database/add.rs | 7 +-- src/database/initialise.rs | 5 +- src/error.rs | 54 +++++++++++++++++ src/internal/commands.rs | 105 ++++++++++++++++++++++++++++++++++ src/internal/mod.rs | 13 ++++- src/internal/strings.rs | 39 +++++-------- src/main.rs | 47 +++++---------- src/operations/aur_install.rs | 35 ++++-------- src/operations/install.rs | 17 ++---- src/operations/search.rs | 31 ++++------ src/operations/uninstall.rs | 44 +++++++------- src/operations/upgrade.rs | 9 +-- 12 files changed, 255 insertions(+), 151 deletions(-) create mode 100644 src/error.rs create mode 100644 src/internal/commands.rs diff --git a/src/database/add.rs b/src/database/add.rs index bf4620b..e96324f 100644 --- a/src/database/add.rs +++ b/src/database/add.rs @@ -19,8 +19,7 @@ pub fn add(pkg: Package, options: Options) { conn.execute("INSERT OR REPLACE INTO packages (name, version, description, depends, make_depends) VALUES (?1, ?2, ?3, ?4, ?5)", [&pkg.name, &pkg.version, &pkg.description.unwrap_or_else(|| "No description found.".parse().unwrap()), &pkg.depends.join(" "), &pkg.make_depends.join(" ")], - ).unwrap_or_else(|e| { - crash(format!("Failed adding package {} to the database: {}", pkg.name, e), 2); - 1 - }); + ).unwrap_or_else(|e| + crash(format!("Failed adding package {} to the database: {}", pkg.name, e), 2) + ); } diff --git a/src/database/initialise.rs b/src/database/initialise.rs index 01d92b9..a5cef65 100644 --- a/src/database/initialise.rs +++ b/src/database/initialise.rs @@ -31,8 +31,5 @@ pub fn init(options: Options) { )", [], ) - .unwrap_or_else(|e| { - crash(format!("Couldn't initialise database: {}", e), 3); - 1 - }); + .unwrap_or_else(|e| crash(format!("Couldn't initialise database: {}", e), 3)); } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..621323a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,54 @@ +use crate::crash; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter}; +use std::io; + +pub type AppResult = Result; + +#[derive(Debug)] +pub enum AppError { + Io(std::io::Error), + Other(String), +} + +impl Display for AppError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AppError::Io(io) => Display::fmt(io, f), + AppError::Other(s) => Display::fmt(s, f), + } + } +} + +impl Error for AppError {} + +impl From for AppError { + fn from(e: io::Error) -> Self { + Self::Io(e) + } +} + +impl From for AppError { + fn from(string: String) -> Self { + Self::Other(string) + } +} + +impl From<&str> for AppError { + fn from(string: &str) -> Self { + Self::from(string.to_string()) + } +} + +pub trait SilentUnwrap { + fn silent_unwrap(self) -> T; +} + +impl SilentUnwrap for AppResult { + fn silent_unwrap(self) -> T { + match self { + Ok(val) => val, + Err(_) => crash("an error occurred", 1), + } + } +} diff --git a/src/internal/commands.rs b/src/internal/commands.rs new file mode 100644 index 0000000..91a5dbd --- /dev/null +++ b/src/internal/commands.rs @@ -0,0 +1,105 @@ +use crate::error::{AppError, AppResult}; +use crate::internal::uwu_enabled; +use crate::uwu; +use std::ffi::{OsStr, OsString}; +use std::io::{BufRead, BufReader}; +use std::process::{ChildStderr, ChildStdout, Command, Stdio}; + +/// Executes a makepkg command +#[inline] +pub fn makepkg, S: AsRef>(args: I) -> AppResult { + run_command("makepkg", args) +} + +/// Executes a git command +#[inline] +pub fn git, S: AsRef>(args: I) -> AppResult { + run_command("git", args) +} + +/// Executes a bash command +#[inline] +pub fn bash, S: AsRef>(args: I) -> AppResult { + run_command("bash", args) +} + +/// Runs pacman with sudo +pub fn sudo_pacman, S: AsRef>(args: I) -> AppResult { + let mut pacman_args = args + .into_iter() + .map(|i: S| OsString::from(i.as_ref())) + .collect::>(); + let mut sudo_args = vec![OsString::from("pacman")]; + sudo_args.append(&mut pacman_args); + sudo(sudo_args) +} + +/// Executes a pacman command +#[inline] +pub fn pacman, S: AsRef>(args: I) -> AppResult { + run_command("pacman", args) +} + +#[inline] +pub fn sudo, S: AsRef>(args: I) -> AppResult { + run_command("sudo", args) +} + +/// Runs a command and parses its output as string +fn run_command, I: IntoIterator, S2: AsRef>( + command: S1, + args: I, +) -> AppResult { + let mut child = Command::new(command) + .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + let stdout = child.stdout.as_mut().unwrap(); + let stderr = child.stderr.as_mut().unwrap(); + let stdout_str = read_stdout(stdout)?; + let stderr_str = read_stderr(stderr)?; + + let status = child.wait()?; + if status.success() { + Ok(stdout_str) + } else { + Err(AppError::from(stderr_str)) + } +} + +fn read_stdout(stdout: &mut ChildStdout) -> AppResult { + let mut stdout_str = String::new(); + let stdout_reader = BufReader::new(stdout); + + for line in stdout_reader.lines() { + let line = line?; + if uwu_enabled() { + println!("{}", uwu!(&*line)) + } else { + println!("{}", &line); + } + stdout_str.push_str(&line); + stdout_str.push_str("\n"); + } + + Ok(stdout_str) +} + +fn read_stderr(stderr: &mut ChildStderr) -> AppResult { + let mut stderr_str = String::new(); + let stderr_reader = BufReader::new(stderr); + + for line in stderr_reader.lines() { + let line = line?; + if uwu_enabled() { + eprintln!("{}", uwu!(&line)) + } else { + eprintln!("{}", &line); + } + stderr_str.push_str(&line); + stderr_str.push_str("\n"); + } + + Ok(stderr_str) +} diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 04b9645..87bf5d6 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -1,4 +1,5 @@ mod clean; +mod commands; mod initialise; pub mod rpc; mod sort; @@ -6,14 +7,16 @@ mod strings; pub mod structs; pub use clean::*; +pub use commands::*; pub use initialise::*; pub use sort::*; +use std::env; pub use strings::*; #[macro_export] macro_rules! uwu { ($x:expr) => {{ - let uwu: String = String::from_str($x).unwrap(); + let uwu: String = String::from($x); let uwu = uwu.replace("l", "w"); let uwu = uwu.replace("L", "W"); @@ -26,3 +29,11 @@ macro_rules! uwu { uwu }}; } + +fn uwu_enabled() -> bool { + env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" +} + +fn uwu_debug_enabled() -> bool { + env::var("AME_UWU_DEBUG").unwrap_or_else(|_| "".to_string()) == "true" +} diff --git a/src/internal/strings.rs b/src/internal/strings.rs index dd2662d..892bb15 100644 --- a/src/internal/strings.rs +++ b/src/internal/strings.rs @@ -1,36 +1,28 @@ +use std::io; use std::io::Write; use std::process::exit; -use std::str::FromStr; use std::time::UNIX_EPOCH; -use std::{env, io}; -use crate::uwu; +use crate::{internal, uwu}; -pub fn info(a: String) { - let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" { - uwu!(&a) - } else { - a - }; +pub fn info(a: S) { + let a = a.to_string(); + let a = if internal::uwu_enabled() { uwu!(&a) } else { a }; println!("\x1b[2;22;35m❖\x1b[0m \x1b[1;37m{}\x1b[0m", a) } -pub fn crash(a: String, b: i32) { - let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" { - uwu!(&a) - } else { - a - }; +pub fn crash(a: S, b: i32) -> ! { + let a = a.to_string(); + let a = if internal::uwu_enabled() { uwu!(&a) } else { a }; println!("\x1b[2;22;31m❌:\x1b[0m \x1b[1;91m{}\x1b[0m", a); exit(b); } -pub fn log(a: String) { - let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" - && env::var("AME_UWU_DEBUG").unwrap_or_else(|_| "".to_string()) == "true" - { +pub fn log(a: S) { + let a = a.to_string(); + let a = if internal::uwu_enabled() && internal::uwu_debug_enabled() { uwu!(&a) } else { a @@ -46,15 +38,12 @@ pub fn log(a: String) { ); } -pub fn prompt(a: String, b: bool) -> bool { +pub fn prompt(a: S, b: bool) -> bool { + let a = a.to_string(); let default = ["[Y/n]", "[y/N]"]; let i = if b { 0 } else { 1 }; - let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" { - uwu!(&a) - } else { - a - }; + let a = if internal::uwu_enabled() { uwu!(&a) } else { a }; print!( "\x1b[2;22;35m?\x1b[0m \x1b[1;37m{}\x1b[0m \x1b[2;22;37m{}\x1b[0m: ", diff --git a/src/main.rs b/src/main.rs index 24b1c28..91471af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,16 @@ use clap::Parser; -use std::process; -use std::process::Command; use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; use args::Args; -use crate::internal::{crash, info, init, log, sort, structs::Options}; +use crate::internal::{bash, crash, info, init, log, pacman, sort, structs::Options}; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod args; mod database; +mod error; mod internal; mod operations; @@ -71,14 +70,14 @@ fn cmd_install(args: InstallArgs, options: Options) { )); } - let out = process::Command::new("bash") - .args(&["-c", "sudo find /etc -name *.pacnew"]) - .output() - .expect("Something has gone wrong") - .stdout; + let bash_output = bash(&["-c", "sudo find /etc -name *.pacnew"]).unwrap(); - if !String::from_utf8((*out).to_owned()).unwrap().is_empty() { - info(format!("You have .pacnew files in /etc ({}) that you haven't removed or acted upon, it is recommended you do that now", String::from_utf8((*out).to_owned()).unwrap().split_whitespace().collect::>().join(", "))); + if !bash_output.is_empty() { + let pacnew_files = bash_output + .split_whitespace() + .collect::>() + .join(", "); + info(format!("You have .pacnew files in /etc ({pacnew_files}) that you haven't removed or acted upon, it is recommended you do that now", )); } } @@ -108,33 +107,13 @@ fn cmd_search(args: SearchArgs, options: Options) { fn cmd_query(args: QueryArgs) { if args.aur { - Command::new("pacman") - .arg("-Qm") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); + pacman(&["-Qm"]).unwrap(); } if args.repo { - Command::new("pacman") - .arg("-Qn") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); + pacman(&["-Qn"]).unwrap(); } if !args.repo && !args.aur { - Command::new("pacman") - .arg("-Qn") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); - Command::new("pacman") - .arg("-Qm") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); + pacman(&["-Qn"]).unwrap(); + pacman(&["-Qm"]).unwrap(); } } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 130f541..659f1c2 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -1,12 +1,13 @@ use std::env::set_current_dir; use std::fs::remove_dir_all; use std::path::Path; -use std::process::{Command, Stdio}; +use std::process::Command; use std::{env, fs}; +use crate::error::SilentUnwrap; use crate::internal::rpc::rpcinfo; -use crate::internal::{crash, prompt}; -use crate::{info, log, Options}; +use crate::internal::{crash, git, makepkg, prompt}; +use crate::{bash, info, log, Options}; pub fn aur_install(a: Vec, options: Options) { let url = crate::internal::rpc::URL; @@ -36,12 +37,7 @@ pub fn aur_install(a: Vec, options: Options) { info("Cloning package source".to_string()); set_current_dir(Path::new(&cachedir)).unwrap(); - Command::new("git") - .arg("clone") - .arg(format!("{}/{}", url, pkg)) - .stdout(Stdio::null()) - .output() - .expect("Something has gone wrong"); + git(&["clone", &format!("{}/{}", url, pkg)]).silent_unwrap(); if verbosity >= 1 { log(format!( @@ -113,18 +109,10 @@ pub fn aur_install(a: Vec, options: Options) { .wait() .unwrap(); - let out = Command::new("bash") - .args(&["-c", &format!("ls {}/*.install &> /dev/null", pkg)]) - .status() - .unwrap(); + let result = bash(&["-c", &format!("ls {}/*.install &> /dev/null", pkg)]); - if out.code() == Some(0) { - Command::new("bash") - .args(&["-c", &format!("{} {}/*.install", editor, pkg)]) - .spawn() - .unwrap() - .wait() - .unwrap(); + if result.is_ok() { + bash(&["-c", &format!("{} {}/*.install", editor, pkg)]).silent_unwrap(); } let p2 = prompt(format!("Would you still like to install {}?", pkg), true); @@ -157,12 +145,9 @@ pub fn aur_install(a: Vec, options: Options) { // package building and installing info("Building time!".to_string()); set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap(); - let out = Command::new("makepkg") - .args(&makepkg_args) - .status() - .expect("Something has gone wrong"); + let result = makepkg(&makepkg_args); - if out.code() != Some(0) { + if result.is_ok() { fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); crash( format!("Error encountered while installing {}, aborting", pkg), diff --git a/src/operations/install.rs b/src/operations/install.rs index cb0aa38..5c19056 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -1,13 +1,14 @@ +use crate::internal::sudo_pacman; use crate::{crash, info, log, Options}; pub fn install(a: Vec, options: Options) { info(format!("Installing packages {} from repos", &a.join(", "))); - let mut opers = vec![]; + let mut opers = vec!["-S", "--needed"]; if options.noconfirm { - opers.push("--noconfirm".to_string()); + opers.push("--noconfirm"); } if options.asdeps { - opers.push("--asdeps".to_string()); + opers.push("--asdeps"); } let verbosity = options.verbosity; if !a.is_empty() { @@ -15,15 +16,7 @@ pub fn install(a: Vec, options: Options) { log(format!("Installing from repos: {:?}", &a)); } - let r = runas::Command::new("pacman") - .arg("-S") - .arg("--needed") - .args(&a) - .args(&opers) - .status() - .expect("Something has gone wrong"); - - if r.code() != Some(0) { + if let Err(_e) = sudo_pacman(&opers) { crash( format!( "An error occured while installing packages: {}, aborting", diff --git a/src/operations/search.rs b/src/operations/search.rs index 2056f67..6b0c035 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -1,19 +1,11 @@ -use std::process::Command; - +use crate::error::SilentUnwrap; use crate::internal::rpc::rpcsearch; -use crate::{log, Options}; +use crate::{log, pacman, Options}; pub fn aur_search(a: &str, options: Options) { let verbosity = options.verbosity; let res = rpcsearch(a.to_string()); - if verbosity >= 1 { - log(format!( - "Found {} resuls for \"{}\" in AUR", - res.resultcount, a - )); - } - for r in &res.results { println!( "aur/{} {}\n {}", @@ -24,25 +16,24 @@ pub fn aur_search(a: &str, options: Options) { .unwrap_or(&"No description".to_string()) ) } + + if verbosity >= 1 { + log(format!( + "Found {} resuls for \"{}\" in AUR", + res.resultcount, a + )); + } } pub fn repo_search(a: &str, options: Options) { let verbosity = options.verbosity; - let rs = Command::new("pacman") - .arg("-Ss") - .arg(&a) - .output() - .expect("Something has gone wrong"); - - let str = String::from_utf8(rs.stdout).unwrap(); + let output = pacman(&["-Ss", a]).silent_unwrap(); if verbosity >= 1 { log(format!( "Found {} results for \"{}\" in repos", - &str.split('\n').count() / 2, + &output.split('\n').count() / 2, &a )); } - - print!("{}", str); } diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index 0e2ef05..1d1700a 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -1,43 +1,47 @@ use std::path::Path; use std::{env, fs}; +use crate::error::SilentUnwrap; +use crate::internal::sudo_pacman; use crate::{log, Options}; -pub fn uninstall(mut a: Vec, options: Options) { - let b = a.clone(); +pub fn uninstall(packages: Vec, options: Options) { + let mut pacman_args = vec!["-Rs"]; + pacman_args.append(&mut packages.iter().map(|s| s.as_str()).collect()); + if options.noconfirm { - a.push("--noconfirm".to_string()); + pacman_args.push("--noconfirm"); } let verbosity = options.verbosity; if verbosity >= 1 { - log(format!("Uninstalling: {:?}", &b)); + log(format!("Uninstalling: {:?}", &packages)); } - let r = runas::Command::new("pacman") - .arg("-Rs") - .args(&a) - .status() - .expect("Something has gone wrong"); + sudo_pacman(pacman_args).silent_unwrap(); - if let Some(x) = r.code() { - if verbosity >= 1 { - log(format!( - "Uninstalling packages: {:?} exited with code {}", - &b, x - )); - } + if verbosity >= 1 { + log(format!( + "Uninstalling packages: {:?} exited with code 0", + &packages + )); } - for b in a { - crate::database::remove(&b, options); - if Path::new(&format!("{}/.cache/ame/{}", env::var("HOME").unwrap(), b)).exists() { + for package in packages { + crate::database::remove(&package, options); + if Path::new(&format!( + "{}/.cache/ame/{}", + env::var("HOME").unwrap(), + package + )) + .exists() + { if verbosity >= 1 { log("Old cache directory found, deleting".to_string()); } fs::remove_dir_all(Path::new(&format!( "{}/.cache/ame/{}", env::var("HOME").unwrap(), - b + package ))) .unwrap(); } diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs index d80c824..a7b8f03 100644 --- a/src/operations/upgrade.rs +++ b/src/operations/upgrade.rs @@ -1,6 +1,6 @@ -use runas::Command; - +use crate::error::SilentUnwrap; use crate::internal::rpc::rpcinfo; +use crate::internal::sudo_pacman; use crate::operations::aur_install::aur_install; use crate::{info, log, Options}; @@ -17,10 +17,7 @@ pub fn upgrade(options: Options) { log("Upgrading repo packages".to_string()); } - Command::new("pacman") - .args(&pacman_args) - .status() - .expect("Something has gone wrong"); + sudo_pacman(pacman_args).silent_unwrap(); if verbosity >= 1 { log("Upgrading AUR packages".to_string()); From b8507025f6c69c8e3917039d52dd101536a5bd8f Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 17 Apr 2022 20:41:59 +0200 Subject: [PATCH 2/3] Wrap commands with ShellCommand builder Signed-off-by: Trivernis --- Cargo.toml | 1 - src/database/add.rs | 3 +- src/database/initialise.rs | 8 +- src/internal/commands.rs | 186 +++++++++++++++++++--------------- src/{ => internal}/error.rs | 9 +- src/internal/exit_code.rs | 12 +++ src/internal/initialise.rs | 9 +- src/internal/mod.rs | 9 +- src/internal/strings.rs | 15 +-- src/main.rs | 46 ++++++--- src/operations/aur_install.rs | 43 +++++--- src/operations/install.rs | 33 ++++-- src/operations/search.rs | 34 ++++--- src/operations/uninstall.rs | 11 +- src/operations/upgrade.rs | 11 +- 15 files changed, 274 insertions(+), 156 deletions(-) rename src/{ => internal}/error.rs (74%) create mode 100644 src/internal/exit_code.rs diff --git a/Cargo.toml b/Cargo.toml index 0ef869c..2142ee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ codegen-units = 1 mimalloc = { version = "0.1.27", default-features = false } clap = { version = "3.1.9", features = [ "derive", "wrap_help"] } regex = { version = "1.5.4", default-features = false, features = ["std", "unicode-perl"] } -runas = "0.2.1" rusqlite = { version = "0.26.3", default-features = false } ureq = { version = "2.4.0", default-features = false, features = ["native-tls", "json"] } serde = { version = "1.0.90", default-features = false, features = ["derive", "serde_derive"] } diff --git a/src/database/add.rs b/src/database/add.rs index e96324f..07cbd9a 100644 --- a/src/database/add.rs +++ b/src/database/add.rs @@ -3,6 +3,7 @@ use std::path::Path; use rusqlite::Connection; +use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::Package; use crate::{crash, log, Options}; @@ -20,6 +21,6 @@ pub fn add(pkg: Package, options: Options) { conn.execute("INSERT OR REPLACE INTO packages (name, version, description, depends, make_depends) VALUES (?1, ?2, ?3, ?4, ?5)", [&pkg.name, &pkg.version, &pkg.description.unwrap_or_else(|| "No description found.".parse().unwrap()), &pkg.depends.join(" "), &pkg.make_depends.join(" ")], ).unwrap_or_else(|e| - crash(format!("Failed adding package {} to the database: {}", pkg.name, e), 2) + crash(format!("Failed adding package {} to the database: {}", pkg.name, e), AppExitCode::FailedAddingPkg) ); } diff --git a/src/database/initialise.rs b/src/database/initialise.rs index a5cef65..89105d7 100644 --- a/src/database/initialise.rs +++ b/src/database/initialise.rs @@ -3,6 +3,7 @@ use std::path::Path; use rusqlite::Connection; +use crate::internal::exit_code::AppExitCode; use crate::{crash, log, Options}; pub fn init(options: Options) { @@ -31,5 +32,10 @@ pub fn init(options: Options) { )", [], ) - .unwrap_or_else(|e| crash(format!("Couldn't initialise database: {}", e), 3)); + .unwrap_or_else(|e| { + crash( + format!("Couldn't initialise database: {}", e), + AppExitCode::FailedInitDb, + ) + }); } diff --git a/src/internal/commands.rs b/src/internal/commands.rs index 91a5dbd..d9270a2 100644 --- a/src/internal/commands.rs +++ b/src/internal/commands.rs @@ -1,105 +1,125 @@ -use crate::error::{AppError, AppResult}; -use crate::internal::uwu_enabled; -use crate::uwu; +use crate::internal::error::{AppError, AppResult}; use std::ffi::{OsStr, OsString}; -use std::io::{BufRead, BufReader}; -use std::process::{ChildStderr, ChildStdout, Command, Stdio}; +use std::process::{Child, Command, ExitStatus, Stdio}; -/// Executes a makepkg command -#[inline] -pub fn makepkg, S: AsRef>(args: I) -> AppResult { - run_command("makepkg", args) +pub struct StringOutput { + pub stdout: String, + pub stderr: String, + pub status: ExitStatus, } -/// Executes a git command -#[inline] -pub fn git, S: AsRef>(args: I) -> AppResult { - run_command("git", args) +/// A wrapper around [std::process::Command] with predefined +/// commands used in this project as well as elevated access. +pub struct ShellCommand { + command: String, + args: Vec, + elevated: bool, } -/// Executes a bash command -#[inline] -pub fn bash, S: AsRef>(args: I) -> AppResult { - run_command("bash", args) -} +impl ShellCommand { + pub fn pacman() -> Self { + Self::new("pacman") + } -/// Runs pacman with sudo -pub fn sudo_pacman, S: AsRef>(args: I) -> AppResult { - let mut pacman_args = args - .into_iter() - .map(|i: S| OsString::from(i.as_ref())) - .collect::>(); - let mut sudo_args = vec![OsString::from("pacman")]; - sudo_args.append(&mut pacman_args); - sudo(sudo_args) -} + pub fn makepkg() -> Self { + Self::new("makepkg") + } -/// Executes a pacman command -#[inline] -pub fn pacman, S: AsRef>(args: I) -> AppResult { - run_command("pacman", args) -} + pub fn git() -> Self { + Self::new("git") + } -#[inline] -pub fn sudo, S: AsRef>(args: I) -> AppResult { - run_command("sudo", args) -} + pub fn bash() -> Self { + Self::new("bash") + } -/// Runs a command and parses its output as string -fn run_command, I: IntoIterator, S2: AsRef>( - command: S1, - args: I, -) -> AppResult { - let mut child = Command::new(command) - .args(args) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - let stdout = child.stdout.as_mut().unwrap(); - let stderr = child.stderr.as_mut().unwrap(); - let stdout_str = read_stdout(stdout)?; - let stderr_str = read_stderr(stderr)?; - - let status = child.wait()?; - if status.success() { - Ok(stdout_str) - } else { - Err(AppError::from(stderr_str)) + fn new(command: S) -> Self { + Self { + command: command.to_string(), + args: Vec::new(), + elevated: false, + } } -} -fn read_stdout(stdout: &mut ChildStdout) -> AppResult { - let mut stdout_str = String::new(); - let stdout_reader = BufReader::new(stdout); + /// Adds one argument + pub fn arg>(mut self, arg: S) -> Self { + self.args.push(arg.as_ref().to_os_string()); - for line in stdout_reader.lines() { - let line = line?; - if uwu_enabled() { - println!("{}", uwu!(&*line)) - } else { - println!("{}", &line); - } - stdout_str.push_str(&line); - stdout_str.push_str("\n"); + self } - Ok(stdout_str) -} + /// Adds a list of arguments + pub fn args, S: AsRef>(mut self, args: I) -> Self { + self.args.append( + &mut args + .into_iter() + .map(|a: S| a.as_ref().to_os_string()) + .collect(), + ); -fn read_stderr(stderr: &mut ChildStderr) -> AppResult { - let mut stderr_str = String::new(); - let stderr_reader = BufReader::new(stderr); + self + } - for line in stderr_reader.lines() { - let line = line?; - if uwu_enabled() { - eprintln!("{}", uwu!(&line)) + /// Runs the command with sudo + pub fn elevated(mut self) -> Self { + self.elevated = true; + + self + } + + /// Waits for the child to exit but returns an error when it exists with a non-zero status code + pub fn wait_success(self) -> AppResult<()> { + let status = self.wait()?; + if status.success() { + Ok(()) } else { - eprintln!("{}", &line); + Err(AppError::NonZeroExit) } - stderr_str.push_str(&line); - stderr_str.push_str("\n"); } - Ok(stderr_str) + /// Waits for the child to exit and returns the output status + pub fn wait(self) -> AppResult { + let mut child = self.spawn(false)?; + + child.wait().map_err(AppError::from) + } + + /// Waits with output until the program completed and + /// returns the string output object + pub fn wait_with_output(self) -> AppResult { + let child = self.spawn(true)?; + let output = child.wait_with_output()?; + let stdout = String::from_utf8(output.stdout).map_err(|e| AppError::from(e.to_string()))?; + let stderr = String::from_utf8(output.stderr).map_err(|e| AppError::from(e.to_string()))?; + + Ok(StringOutput { + status: output.status, + stdout, + stderr, + }) + } + + fn spawn(self, piped: bool) -> AppResult { + let (stdout, stderr) = if piped { + (Stdio::piped(), Stdio::piped()) + } else { + (Stdio::inherit(), Stdio::inherit()) + }; + let child = if self.elevated { + Command::new("sudo") + .arg(self.command) + .args(self.args) + .stdout(stdout) + .stderr(stderr) + .spawn()? + } else { + Command::new(self.command) + .args(self.args) + .stdout(stdout) + .stderr(stderr) + .spawn()? + }; + + Ok(child) + } } diff --git a/src/error.rs b/src/internal/error.rs similarity index 74% rename from src/error.rs rename to src/internal/error.rs index 621323a..02e3010 100644 --- a/src/error.rs +++ b/src/internal/error.rs @@ -1,4 +1,5 @@ use crate::crash; +use crate::internal::exit_code::AppExitCode; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::io; @@ -9,6 +10,7 @@ pub type AppResult = Result; pub enum AppError { Io(std::io::Error), Other(String), + NonZeroExit, } impl Display for AppError { @@ -16,6 +18,7 @@ impl Display for AppError { match self { AppError::Io(io) => Display::fmt(io, f), AppError::Other(s) => Display::fmt(s, f), + AppError::NonZeroExit => Display::fmt("exited with non zero code", f), } } } @@ -41,14 +44,14 @@ impl From<&str> for AppError { } pub trait SilentUnwrap { - fn silent_unwrap(self) -> T; + fn silent_unwrap(self, error_code: AppExitCode) -> T; } impl SilentUnwrap for AppResult { - fn silent_unwrap(self) -> T { + fn silent_unwrap(self, exit_code: AppExitCode) -> T { match self { Ok(val) => val, - Err(_) => crash("an error occurred", 1), + Err(_) => crash("an error occurred", exit_code), } } } diff --git a/src/internal/exit_code.rs b/src/internal/exit_code.rs new file mode 100644 index 0000000..aa34dff --- /dev/null +++ b/src/internal/exit_code.rs @@ -0,0 +1,12 @@ +pub enum AppExitCode { + RunAsRoot = 1, + FailedAddingPkg = 2, + FailedInitDb = 3, + FailedCreatingPaths = 4, + MissingDeps = 5, + UserCancellation = 6, + PacmanError = 7, + GitError = 8, + MakePkgError = 9, + Other = 102, +} diff --git a/src/internal/initialise.rs b/src/internal/initialise.rs index 9fe510b..01ee009 100644 --- a/src/internal/initialise.rs +++ b/src/internal/initialise.rs @@ -1,3 +1,4 @@ +use crate::internal::exit_code::AppExitCode; use std::env; use std::path::Path; @@ -19,7 +20,7 @@ pub fn init(options: Options) { Err(e) => { crash( format!("Couldn't create path: {}/.local/share/ame: {}", homedir, e), - 4, + AppExitCode::FailedCreatingPaths, ); } } @@ -40,7 +41,7 @@ pub fn init(options: Options) { Err(e) => { crash( format!("Couldn't create path: {}/.cache/ame: {}", homedir, e), - 4, + AppExitCode::FailedCreatingPaths, ); } } @@ -55,7 +56,7 @@ pub fn init(options: Options) { Err(e) => { crash( format!("Couldn't remove path: {}/.cache/ame: {}", homedir, e), - 4, + AppExitCode::FailedCreatingPaths, ); } } @@ -69,7 +70,7 @@ pub fn init(options: Options) { Err(e2) => { crash( format!("Couldn't create path: {}/.cache/ame: {}", homedir, e2), - 4, + AppExitCode::FailedCreatingPaths, ); } } diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 87bf5d6..fbc6619 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -1,5 +1,7 @@ mod clean; -mod commands; +pub mod commands; +pub mod error; +pub mod exit_code; mod initialise; pub mod rpc; mod sort; @@ -7,7 +9,6 @@ mod strings; pub mod structs; pub use clean::*; -pub use commands::*; pub use initialise::*; pub use sort::*; use std::env; @@ -30,10 +31,10 @@ macro_rules! uwu { }}; } -fn uwu_enabled() -> bool { +pub fn uwu_enabled() -> bool { env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" } -fn uwu_debug_enabled() -> bool { +pub fn uwu_debug_enabled() -> bool { env::var("AME_UWU_DEBUG").unwrap_or_else(|_| "".to_string()) == "true" } diff --git a/src/internal/strings.rs b/src/internal/strings.rs index 892bb15..3ffe71d 100644 --- a/src/internal/strings.rs +++ b/src/internal/strings.rs @@ -3,25 +3,26 @@ use std::io::Write; use std::process::exit; use std::time::UNIX_EPOCH; +use crate::internal::exit_code::AppExitCode; use crate::{internal, uwu}; -pub fn info(a: S) { - let a = a.to_string(); +pub fn info(msg: S) { + let a = msg.to_string(); let a = if internal::uwu_enabled() { uwu!(&a) } else { a }; println!("\x1b[2;22;35m❖\x1b[0m \x1b[1;37m{}\x1b[0m", a) } -pub fn crash(a: S, b: i32) -> ! { - let a = a.to_string(); +pub fn crash(msg: S, exit_code: AppExitCode) -> ! { + let a = msg.to_string(); let a = if internal::uwu_enabled() { uwu!(&a) } else { a }; println!("\x1b[2;22;31m❌:\x1b[0m \x1b[1;91m{}\x1b[0m", a); - exit(b); + exit(exit_code as i32); } -pub fn log(a: S) { - let a = a.to_string(); +pub fn log(msg: S) { + let a = msg.to_string(); let a = if internal::uwu_enabled() && internal::uwu_debug_enabled() { uwu!(&a) } else { diff --git a/src/main.rs b/src/main.rs index 91471af..4d20366 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,17 @@ use clap::Parser; use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; use args::Args; +use internal::commands::ShellCommand; +use internal::error::SilentUnwrap; -use crate::internal::{bash, crash, info, init, log, pacman, sort, structs::Options}; +use crate::internal::exit_code::AppExitCode; +use crate::internal::{crash, info, init, log, sort, structs::Options}; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod args; mod database; -mod error; mod internal; mod operations; @@ -20,7 +22,7 @@ fn main() { } if unsafe { geteuid() } == 0 { - crash("Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions".to_string(), 1); + crash("Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions".to_string(), AppExitCode::RunAsRoot); } let args: Args = Args::parse(); @@ -64,13 +66,21 @@ fn cmd_install(args: InstallArgs, options: Options) { operations::aur_install(sorted.aur, options); } if !sorted.nf.is_empty() { - log(format!( - "Couldn't find packages: {} in repos or the AUR", - sorted.nf.join(", ") - )); + crash( + format!( + "Couldn't find packages: {} in repos or the AUR", + sorted.nf.join(", ") + ), + AppExitCode::PacmanError, + ); } - let bash_output = bash(&["-c", "sudo find /etc -name *.pacnew"]).unwrap(); + let bash_output = ShellCommand::bash() + .arg("-c") + .arg("sudo find /etc -name *.pacnew") + .wait_with_output() + .silent_unwrap(AppExitCode::Other) + .stdout; if !bash_output.is_empty() { let pacnew_files = bash_output @@ -107,13 +117,25 @@ fn cmd_search(args: SearchArgs, options: Options) { fn cmd_query(args: QueryArgs) { if args.aur { - pacman(&["-Qm"]).unwrap(); + ShellCommand::pacman() + .arg("-Qm") + .wait_success() + .silent_unwrap(AppExitCode::PacmanError); } if args.repo { - pacman(&["-Qn"]).unwrap(); + ShellCommand::pacman() + .arg("-Qn") + .wait_success() + .silent_unwrap(AppExitCode::PacmanError); } if !args.repo && !args.aur { - pacman(&["-Qn"]).unwrap(); - pacman(&["-Qm"]).unwrap(); + ShellCommand::pacman() + .arg("-Qn") + .wait_success() + .silent_unwrap(AppExitCode::PacmanError); + ShellCommand::pacman() + .arg("-Qm") + .wait_success() + .silent_unwrap(AppExitCode::PacmanError); } } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 659f1c2..9d1be84 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -4,10 +4,12 @@ use std::path::Path; use std::process::Command; use std::{env, fs}; -use crate::error::SilentUnwrap; +use crate::internal::commands::ShellCommand; +use crate::internal::error::SilentUnwrap; +use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; -use crate::internal::{crash, git, makepkg, prompt}; -use crate::{bash, info, log, Options}; +use crate::internal::{crash, prompt}; +use crate::{info, log, Options}; pub fn aur_install(a: Vec, options: Options) { let url = crate::internal::rpc::URL; @@ -37,7 +39,11 @@ pub fn aur_install(a: Vec, options: Options) { info("Cloning package source".to_string()); set_current_dir(Path::new(&cachedir)).unwrap(); - git(&["clone", &format!("{}/{}", url, pkg)]).silent_unwrap(); + ShellCommand::git() + .arg("clone") + .arg(format!("{}/{}", url, pkg)) + .wait() + .silent_unwrap(AppExitCode::GitError); if verbosity >= 1 { log(format!( @@ -87,7 +93,7 @@ pub fn aur_install(a: Vec, options: Options) { sorted.nf.join(", "), pkg ), - 5, + AppExitCode::MissingDeps, ); } @@ -109,16 +115,24 @@ pub fn aur_install(a: Vec, options: Options) { .wait() .unwrap(); - let result = bash(&["-c", &format!("ls {}/*.install &> /dev/null", pkg)]); - - if result.is_ok() { - bash(&["-c", &format!("{} {}/*.install", editor, pkg)]).silent_unwrap(); + let status = ShellCommand::bash() + .arg("-c") + .arg(format!("ls {}/*.install &> /dev/null", pkg)) + .wait() + .silent_unwrap(AppExitCode::Other); + + if status.success() { + ShellCommand::bash() + .arg("-c") + .arg(format!("{} {}/*.install", editor, pkg)) + .wait() + .silent_unwrap(AppExitCode::Other); } let p2 = prompt(format!("Would you still like to install {}?", pkg), true); if !p2 { fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); - crash("Not proceeding".to_string(), 6); + crash("Not proceeding".to_string(), AppExitCode::UserCancellation); } } } @@ -145,13 +159,16 @@ pub fn aur_install(a: Vec, options: Options) { // package building and installing info("Building time!".to_string()); set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap(); - let result = makepkg(&makepkg_args); + let status = ShellCommand::makepkg() + .args(makepkg_args) + .wait() + .silent_unwrap(AppExitCode::MakePkgError); - if result.is_ok() { + if !status.success() { fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); crash( format!("Error encountered while installing {}, aborting", pkg), - 7, + AppExitCode::PacmanError, ); } diff --git a/src/operations/install.rs b/src/operations/install.rs index 5c19056..d1f1c59 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -1,8 +1,13 @@ -use crate::internal::sudo_pacman; +use crate::internal::commands::ShellCommand; +use crate::internal::error::SilentUnwrap; +use crate::internal::exit_code::AppExitCode; use crate::{crash, info, log, Options}; -pub fn install(a: Vec, options: Options) { - info(format!("Installing packages {} from repos", &a.join(", "))); +pub fn install(packages: Vec, options: Options) { + info(format!( + "Installing packages {} from repos", + &packages.join(", ") + )); let mut opers = vec!["-S", "--needed"]; if options.noconfirm { opers.push("--noconfirm"); @@ -11,23 +16,33 @@ pub fn install(a: Vec, options: Options) { opers.push("--asdeps"); } let verbosity = options.verbosity; - if !a.is_empty() { + + if !packages.is_empty() { if verbosity >= 1 { - log(format!("Installing from repos: {:?}", &a)); + log(format!("Installing from repos: {:?}", &packages)); } - if let Err(_e) = sudo_pacman(&opers) { + let status = ShellCommand::pacman() + .elevated() + .args(opers) + .args(&packages) + .wait() + .silent_unwrap(AppExitCode::PacmanError); + if !status.success() { crash( format!( "An error occured while installing packages: {}, aborting", - a.join(", ") + packages.join(", ") ), - 7, + AppExitCode::PacmanError, ); } if verbosity >= 1 { - log(format!("Installing packages: {:?} was successful", &a)); + log(format!( + "Installing packages: {:?} was successful", + &packages + )); } } } diff --git a/src/operations/search.rs b/src/operations/search.rs index 6b0c035..b837133 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -1,17 +1,20 @@ -use crate::error::SilentUnwrap; +use crate::internal::commands::ShellCommand; +use crate::internal::error::SilentUnwrap; +use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcsearch; -use crate::{log, pacman, Options}; +use crate::{log, Options}; -pub fn aur_search(a: &str, options: Options) { +pub fn aur_search(query: &str, options: Options) { let verbosity = options.verbosity; - let res = rpcsearch(a.to_string()); + let res = rpcsearch(query.to_string()); - for r in &res.results { + for package in &res.results { println!( "aur/{} {}\n {}", - r.name, - r.version, - r.description + package.name, + package.version, + package + .description .as_ref() .unwrap_or(&"No description".to_string()) ) @@ -20,20 +23,27 @@ pub fn aur_search(a: &str, options: Options) { if verbosity >= 1 { log(format!( "Found {} resuls for \"{}\" in AUR", - res.resultcount, a + res.resultcount, query )); } } -pub fn repo_search(a: &str, options: Options) { +pub fn repo_search(query: &str, options: Options) { let verbosity = options.verbosity; - let output = pacman(&["-Ss", a]).silent_unwrap(); + let output = ShellCommand::pacman() + .arg("-Ss") + .arg(query) + .wait_with_output() + .silent_unwrap(AppExitCode::PacmanError) + .stdout; if verbosity >= 1 { log(format!( "Found {} results for \"{}\" in repos", &output.split('\n').count() / 2, - &a + &query )); } + + println!("{}", output) } diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index 1d1700a..e7e51a6 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -1,8 +1,9 @@ use std::path::Path; use std::{env, fs}; -use crate::error::SilentUnwrap; -use crate::internal::sudo_pacman; +use crate::internal::commands::ShellCommand; +use crate::internal::error::SilentUnwrap; +use crate::internal::exit_code::AppExitCode; use crate::{log, Options}; pub fn uninstall(packages: Vec, options: Options) { @@ -17,7 +18,11 @@ pub fn uninstall(packages: Vec, options: Options) { log(format!("Uninstalling: {:?}", &packages)); } - sudo_pacman(pacman_args).silent_unwrap(); + ShellCommand::pacman() + .elevated() + .args(pacman_args) + .wait_success() + .silent_unwrap(AppExitCode::PacmanError); if verbosity >= 1 { log(format!( diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs index a7b8f03..f03d20d 100644 --- a/src/operations/upgrade.rs +++ b/src/operations/upgrade.rs @@ -1,6 +1,7 @@ -use crate::error::SilentUnwrap; +use crate::internal::commands::ShellCommand; +use crate::internal::error::SilentUnwrap; +use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; -use crate::internal::sudo_pacman; use crate::operations::aur_install::aur_install; use crate::{info, log, Options}; @@ -17,7 +18,11 @@ pub fn upgrade(options: Options) { log("Upgrading repo packages".to_string()); } - sudo_pacman(pacman_args).silent_unwrap(); + ShellCommand::pacman() + .elevated() + .args(pacman_args) + .wait_success() + .silent_unwrap(AppExitCode::PacmanError); if verbosity >= 1 { log("Upgrading AUR packages".to_string()); From 1c1a92f5865e2c1e696a48d347b3d0c3b6878398 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 17 Apr 2022 20:44:39 +0200 Subject: [PATCH 3/3] Add ins shorthand for ame install Signed-off-by: Trivernis --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index 93a7fe7..0f9ea84 100644 --- a/src/args.rs +++ b/src/args.rs @@ -18,7 +18,7 @@ pub struct Args { #[derive(Debug, Clone, Subcommand)] pub enum Operation { /// Installs a package from either the AUR or the PacMan-defined repositories - #[clap(name="install", aliases=&["i", "-S"])] + #[clap(name="install", aliases=&["i", "ins", "-S"])] Install(InstallArgs), /// Removes a previously installed package