use crate::log; use crate::logger::{LogLevel, Logger}; use std::error::Error; use std::ffi::OsString; use std::fmt::{Debug, Display, Formatter}; use std::io; use std::iter::once; use std::path::PathBuf; use std::process::Command; #[derive(Debug)] pub struct EnvCommand<'a, E> { command: ShellCommand, environment: &'a mut E, } #[derive(Debug, Clone)] pub enum ShellCommand { Ssh { address: String, server_command: ServerCommand, }, Scp { source: ScpParam, destination: ScpParam, }, SshAgent, ShhAdd, Editor(Vec), Execute { working_directory: PathBuf, command: OsString, }, } impl ShellCommand { pub fn at(self, environment: &mut E) -> EnvCommand { EnvCommand { command: self, environment, } } } #[derive(Debug, Clone)] pub enum ServerCommand { Realpath { path: PathBuf, }, Ls { dir: PathBuf, }, Rm { file: PathBuf, }, Mv { source: PathBuf, destination: PathBuf, }, Execute { working_directory: PathBuf, command: OsString, }, } #[derive(Debug, Clone)] pub struct ScpParam { pub server: Option, pub path: PathBuf, } impl Display for ShellCommand { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}", command_to_string(&build_command_from_shell_command(self)) ) } } fn command_to_string(command: &Command) -> String { once(command.get_program().to_string_lossy().to_string()) .chain(command.get_args().map(|arg| { let arg_str = arg.to_string_lossy(); if arg_str.contains(' ') { format!("\"{arg_str}\"") } else { arg_str.to_string() } })) .collect::>() .join(" ") } fn build_command_from_shell_command(_shell_command: &ShellCommand) -> Command { todo!() } pub trait ShellInterface { fn run(self) -> CommandResult; fn output(self) -> CommandResult; fn run_logged(self, logger: &Logger) -> CommandResult where Self: Sized, { match logger.level { LogLevel::Debug | LogLevel::Info => { let res = self.run(); CommandResult { result: res.result.map(LoggedRunOutput::from), command: res.command, } } LogLevel::Error => { let res = self.output(); CommandResult { result: res.result.map(LoggedRunOutput::from), command: res.command, } } } } } //TODO implement shell interface for env command pub trait MaybeCast { fn maybe_cast(&self) -> Option<&T>; } impl MaybeCast for T { fn maybe_cast(&self) -> Option<&T> { Some(self) } } #[derive(Debug)] pub enum LoggedRunOutput { ExitStatus(ExitStatus), CommandOutput(CommandOutput), } impl From for LoggedRunOutput { fn from(value: ExitStatus) -> Self { Self::ExitStatus(value) } } impl From for LoggedRunOutput { fn from(value: CommandOutput) -> Self { Self::CommandOutput(value) } } impl AsRef for LoggedRunOutput { fn as_ref(&self) -> &ExitStatus { match self { LoggedRunOutput::ExitStatus(status) => status, LoggedRunOutput::CommandOutput(output) => output.as_ref(), } } } impl MaybeCast for LoggedRunOutput { fn maybe_cast(&self) -> Option<&CommandOutput> { match self { LoggedRunOutput::ExitStatus(_) => None, LoggedRunOutput::CommandOutput(output) => Some(output), } } } #[derive(Debug)] pub struct CommandResult { pub command: ShellCommand, pub result: Result, } impl CommandResult { pub fn into_result(self) -> Result> { self.result.map_err(|error| CommandError { command: self.command, error, }) } } impl CommandResult { pub fn and_expect_success(self) -> CommandResult> where T: AsRef, { CommandResult { result: self.result.map_err(ExecutionError::from).and_then(|t| { if t.as_ref().success { Ok(t) } else { Err(ExecutionError::BadExitStatus(t)) } }), command: self.command, } } } impl CommandResult> { pub fn into_result_with_error_logging( self, logger: &Logger, ) -> Result>> where T: MaybeCast, { self.result.map_err(|error| { if let ExecutionError::BadExitStatus(t) = &error { if let Some(output) = t.maybe_cast() { log!(logger, error, "{}", output.stdout.to_string_lossy()); log!(logger, error, "{}", output.stderr.to_string_lossy()); } } CommandError { command: self.command, error, } }) } } #[derive(Debug, Clone)] pub struct CommandOutput { pub stdout: OsString, pub stderr: OsString, pub status: ExitStatus, } impl AsRef for CommandOutput { fn as_ref(&self) -> &ExitStatus { &self.status } } #[derive(Debug, Clone)] pub struct ExitStatus { pub success: bool, pub string_form: String, pub code: Option, } impl AsRef for ExitStatus { fn as_ref(&self) -> &ExitStatus { self } } impl Display for ExitStatus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.string_form, f) } } #[derive(Debug)] pub struct CommandError { pub command: ShellCommand, pub error: E, } impl Display for CommandError where E: Display, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "Error while running command '{}': {}", self.command, self.error ) } } impl Error for CommandError where E: Error {} #[derive(Debug)] pub struct StartError(io::Error); impl Display for StartError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Failed to run command: {}", self.0) } } impl Error for StartError {} #[derive(Debug)] pub enum ExecutionError { StartError(StartError), BadExitStatus(T), } impl From for ExecutionError { fn from(value: StartError) -> Self { Self::StartError(value) } } impl Display for ExecutionError where T: AsRef, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ExecutionError::StartError(e) => Display::fmt(e, f), ExecutionError::BadExitStatus(status) => { write!(f, "execution failed with {}", status.as_ref()) } } } } impl Error for ExecutionError where T: AsRef + Debug {}