Merge branch 'refactoring' into 'main'
Encapsulate command executions See merge request Trivernis/amethyst!1i18n
commit
99e5db51d9
@ -0,0 +1,125 @@
|
||||
use crate::internal::error::{AppError, AppResult};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::process::{Child, Command, ExitStatus, Stdio};
|
||||
|
||||
pub struct StringOutput {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub status: ExitStatus,
|
||||
}
|
||||
|
||||
/// 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<OsString>,
|
||||
elevated: bool,
|
||||
}
|
||||
|
||||
impl ShellCommand {
|
||||
pub fn pacman() -> Self {
|
||||
Self::new("pacman")
|
||||
}
|
||||
|
||||
pub fn makepkg() -> Self {
|
||||
Self::new("makepkg")
|
||||
}
|
||||
|
||||
pub fn git() -> Self {
|
||||
Self::new("git")
|
||||
}
|
||||
|
||||
pub fn bash() -> Self {
|
||||
Self::new("bash")
|
||||
}
|
||||
|
||||
fn new<S: ToString>(command: S) -> Self {
|
||||
Self {
|
||||
command: command.to_string(),
|
||||
args: Vec::new(),
|
||||
elevated: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds one argument
|
||||
pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
|
||||
self.args.push(arg.as_ref().to_os_string());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a list of arguments
|
||||
pub fn args<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(mut self, args: I) -> Self {
|
||||
self.args.append(
|
||||
&mut args
|
||||
.into_iter()
|
||||
.map(|a: S| a.as_ref().to_os_string())
|
||||
.collect(),
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Err(AppError::NonZeroExit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for the child to exit and returns the output status
|
||||
pub fn wait(self) -> AppResult<ExitStatus> {
|
||||
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<StringOutput> {
|
||||
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<Child> {
|
||||
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)
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
use crate::crash;
|
||||
use crate::internal::exit_code::AppExitCode;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
pub type AppResult<T> = Result<T, AppError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppError {
|
||||
Io(std::io::Error),
|
||||
Other(String),
|
||||
NonZeroExit,
|
||||
}
|
||||
|
||||
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),
|
||||
AppError::NonZeroExit => Display::fmt("exited with non zero code", f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for AppError {}
|
||||
|
||||
impl From<io::Error> for AppError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> 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<T> {
|
||||
fn silent_unwrap(self, error_code: AppExitCode) -> T;
|
||||
}
|
||||
|
||||
impl<T> SilentUnwrap<T> for AppResult<T> {
|
||||
fn silent_unwrap(self, exit_code: AppExitCode) -> T {
|
||||
match self {
|
||||
Ok(val) => val,
|
||||
Err(_) => crash("an error occurred", exit_code),
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
@ -1,40 +1,48 @@
|
||||
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<String>, options: Options) {
|
||||
info(format!("Installing packages {} from repos", &a.join(", ")));
|
||||
let mut opers = vec![];
|
||||
pub fn install(packages: Vec<String>, options: Options) {
|
||||
info(format!(
|
||||
"Installing packages {} from repos",
|
||||
&packages.join(", ")
|
||||
));
|
||||
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() {
|
||||
|
||||
if !packages.is_empty() {
|
||||
if verbosity >= 1 {
|
||||
log(format!("Installing from repos: {:?}", &a));
|
||||
log(format!("Installing from repos: {:?}", &packages));
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,49 @@
|
||||
use std::process::Command;
|
||||
|
||||
use crate::internal::commands::ShellCommand;
|
||||
use crate::internal::error::SilentUnwrap;
|
||||
use crate::internal::exit_code::AppExitCode;
|
||||
use crate::internal::rpc::rpcsearch;
|
||||
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());
|
||||
|
||||
if verbosity >= 1 {
|
||||
log(format!(
|
||||
"Found {} resuls for \"{}\" in AUR",
|
||||
res.resultcount, a
|
||||
));
|
||||
}
|
||||
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())
|
||||
)
|
||||
}
|
||||
|
||||
if verbosity >= 1 {
|
||||
log(format!(
|
||||
"Found {} resuls for \"{}\" in AUR",
|
||||
res.resultcount, query
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repo_search(a: &str, options: Options) {
|
||||
pub fn repo_search(query: &str, options: Options) {
|
||||
let verbosity = options.verbosity;
|
||||
let rs = Command::new("pacman")
|
||||
let output = ShellCommand::pacman()
|
||||
.arg("-Ss")
|
||||
.arg(&a)
|
||||
.output()
|
||||
.expect("Something has gone wrong");
|
||||
|
||||
let str = String::from_utf8(rs.stdout).unwrap();
|
||||
.arg(query)
|
||||
.wait_with_output()
|
||||
.silent_unwrap(AppExitCode::PacmanError)
|
||||
.stdout;
|
||||
|
||||
if verbosity >= 1 {
|
||||
log(format!(
|
||||
"Found {} results for \"{}\" in repos",
|
||||
&str.split('\n').count() / 2,
|
||||
&a
|
||||
&output.split('\n').count() / 2,
|
||||
&query
|
||||
));
|
||||
}
|
||||
|
||||
print!("{}", str);
|
||||
println!("{}", output)
|
||||
}
|
||||
|
Loading…
Reference in New Issue