Add shell_interface and environment and prepare main application for those changes
This commit is contained in:
parent
8fa7fdb88b
commit
85b0b3dbbf
44
src/environment.rs
Normal file
44
src/environment.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::env::VarError;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub trait Environment {
|
||||||
|
fn args_os(&self) -> Vec<OsString>;
|
||||||
|
fn var<K>(&self, key: K) -> Result<String, VarError>
|
||||||
|
where
|
||||||
|
K: AsRef<OsStr>;
|
||||||
|
fn set_var<K, V>(&self, key: K, value: V)
|
||||||
|
where
|
||||||
|
K: AsRef<OsStr>,
|
||||||
|
V: AsRef<OsStr>;
|
||||||
|
fn get_home_directory(&self) -> Option<PathBuf>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Prod;
|
||||||
|
|
||||||
|
impl Environment for Prod {
|
||||||
|
fn args_os(&self) -> Vec<OsString> {
|
||||||
|
env::args_os().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn var<K>(&self, key: K) -> Result<String, VarError>
|
||||||
|
where
|
||||||
|
K: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
env::var(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_var<K, V>(&self, key: K, value: V)
|
||||||
|
where
|
||||||
|
K: AsRef<OsStr>,
|
||||||
|
V: AsRef<OsStr>
|
||||||
|
{
|
||||||
|
env::set_var(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_home_directory(&self) -> Option<PathBuf> {
|
||||||
|
homedir::my_home().ok().flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
1108
src/main.rs
1108
src/main.rs
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
use crate::get_home_directory;
|
use crate::environment::{Environment, Prod};
|
||||||
use std::cell::LazyCell;
|
use std::cell::LazyCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
@ -72,9 +72,13 @@ impl FromStr for ServerReference {
|
|||||||
type Err = ServerReferenceParseError;
|
type Err = ServerReferenceParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Server::from_str(s, RelativeLocalPathAnker::CurrentDirectory)
|
Server::from_str(s, RelativeLocalPathAnker::CurrentDirectory, || {
|
||||||
.map(Self::Resolved)
|
Prod::default()
|
||||||
.or_else(|_| Ok(Self::Identifier(s.to_string())))
|
.get_home_directory()
|
||||||
|
.ok_or("missing home directory".to_string())
|
||||||
|
})
|
||||||
|
.map(Self::Resolved)
|
||||||
|
.or_else(|_| Ok(Self::Identifier(s.to_string())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,10 +130,14 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_str(
|
pub fn from_str<F>(
|
||||||
s: &str,
|
s: &str,
|
||||||
relative_local_path_anker: RelativeLocalPathAnker,
|
relative_local_path_anker: RelativeLocalPathAnker,
|
||||||
) -> Result<Self, ServerParseError> {
|
get_home_directory: F,
|
||||||
|
) -> Result<Self, ServerParseError>
|
||||||
|
where
|
||||||
|
F: Fn() -> Result<PathBuf, String>,
|
||||||
|
{
|
||||||
s.split_once(':')
|
s.split_once(':')
|
||||||
.ok_or(ServerParseError::MissingServerDirectory)
|
.ok_or(ServerParseError::MissingServerDirectory)
|
||||||
.and_then(|(identifier, server_directory)| {
|
.and_then(|(identifier, server_directory)| {
|
||||||
|
|||||||
313
src/shell_interface.rs
Normal file
313
src/shell_interface.rs
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
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 E,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ShellCommand {
|
||||||
|
Ssh {
|
||||||
|
address: String,
|
||||||
|
server_command: ServerCommand,
|
||||||
|
},
|
||||||
|
Scp {
|
||||||
|
source: ScpParam,
|
||||||
|
destination: ScpParam,
|
||||||
|
},
|
||||||
|
SshAgent,
|
||||||
|
ShhAdd,
|
||||||
|
Editor(Vec<OsString>),
|
||||||
|
Execute {
|
||||||
|
working_directory: PathBuf,
|
||||||
|
command: OsString,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShellCommand {
|
||||||
|
pub fn at<E>(self, environment: &E) -> EnvCommand<E> {
|
||||||
|
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<String>,
|
||||||
|
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::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_command_from_shell_command(shell_command: &ShellCommand) -> Command {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ShellInterface {
|
||||||
|
fn run(self) -> CommandResult<ExitStatus, StartError>;
|
||||||
|
fn output(self) -> CommandResult<CommandOutput, StartError>;
|
||||||
|
fn run_logged(self, logger: &Logger) -> CommandResult<LoggedRunOutput, StartError>
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MaybeCast<T> {
|
||||||
|
fn maybe_cast(&self) -> Option<&T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MaybeCast<T> for T {
|
||||||
|
fn maybe_cast(&self) -> Option<&T> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LoggedRunOutput {
|
||||||
|
ExitStatus(ExitStatus),
|
||||||
|
CommandOutput(CommandOutput),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ExitStatus> for LoggedRunOutput {
|
||||||
|
fn from(value: ExitStatus) -> Self {
|
||||||
|
Self::ExitStatus(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CommandOutput> for LoggedRunOutput {
|
||||||
|
fn from(value: CommandOutput) -> Self {
|
||||||
|
Self::CommandOutput(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<ExitStatus> for LoggedRunOutput {
|
||||||
|
fn as_ref(&self) -> &ExitStatus {
|
||||||
|
match self {
|
||||||
|
LoggedRunOutput::ExitStatus(status) => status,
|
||||||
|
LoggedRunOutput::CommandOutput(output) => output.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaybeCast<CommandOutput> for LoggedRunOutput {
|
||||||
|
fn maybe_cast(&self) -> Option<&CommandOutput> {
|
||||||
|
match self {
|
||||||
|
LoggedRunOutput::ExitStatus(_) => None,
|
||||||
|
LoggedRunOutput::CommandOutput(output) => Some(output),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandResult<T, E> {
|
||||||
|
pub command: ShellCommand,
|
||||||
|
pub result: Result<T, E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> CommandResult<T, E> {
|
||||||
|
pub fn into_result(self) -> Result<T, CommandError<E>> {
|
||||||
|
self.result.map_err(|error| CommandError {
|
||||||
|
command: self.command,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CommandResult<T, StartError> {
|
||||||
|
pub fn and_expect_success(self) -> CommandResult<T, ExecutionError<T>>
|
||||||
|
where
|
||||||
|
T: AsRef<ExitStatus>,
|
||||||
|
{
|
||||||
|
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<CommandOutput, ExecutionError<CommandOutput>> {
|
||||||
|
pub fn into_result_with_error_logging(
|
||||||
|
self,
|
||||||
|
logger: &Logger,
|
||||||
|
) -> Result<CommandOutput, CommandError<ExecutionError<CommandOutput>>> {
|
||||||
|
self.result.map_err(|error| {
|
||||||
|
if let ExecutionError::BadExitStatus(output) = &error {
|
||||||
|
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<ExitStatus> 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<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<ExitStatus> 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<E> {
|
||||||
|
pub command: ShellCommand,
|
||||||
|
pub error: E,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> Display for CommandError<E>
|
||||||
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Error while running command '{}': {}",
|
||||||
|
self.command, self.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> Error for CommandError<E> 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<T> {
|
||||||
|
StartError(StartError),
|
||||||
|
BadExitStatus(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<StartError> for ExecutionError<T> {
|
||||||
|
fn from(value: StartError) -> Self {
|
||||||
|
Self::StartError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Display for ExecutionError<T>
|
||||||
|
where
|
||||||
|
T: AsRef<ExitStatus>,
|
||||||
|
{
|
||||||
|
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<T> Error for ExecutionError<T> where T: AsRef<ExitStatus> + Debug {}
|
||||||
Loading…
Reference in New Issue
Block a user