Compare commits

...

2 Commits

4 changed files with 132 additions and 60 deletions

View File

@ -1,9 +1,13 @@
use crate::shell_interface::{
build_command_from_shell_command, CommandOutput, CommandResult, ExitStatus, ShellCommand,
ShellInterface, StartError,
};
use std::env::VarError; use std::env::VarError;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::path::PathBuf; use std::path::PathBuf;
use std::{env, io}; use std::{env, io};
pub trait Environment { pub trait Environment: ShellInterface {
fn args_os(&self) -> Vec<OsString>; fn args_os(&self) -> Vec<OsString>;
fn var<K>(&self, key: K) -> Result<String, VarError> fn var<K>(&self, key: K) -> Result<String, VarError>
where where
@ -34,7 +38,7 @@ impl Environment for Prod {
fn set_var<K, V>(&mut self, key: K, value: V) fn set_var<K, V>(&mut self, key: K, value: V)
where where
K: AsRef<OsStr>, K: AsRef<OsStr>,
V: AsRef<OsStr> V: AsRef<OsStr>,
{ {
env::set_var(key, value); env::set_var(key, value);
} }
@ -49,3 +53,28 @@ impl Environment for Prod {
Ok(buffer.trim().to_string()) Ok(buffer.trim().to_string())
} }
} }
impl ShellInterface for Prod {
fn run_command(&mut self, command: ShellCommand) -> CommandResult<ExitStatus, StartError> {
CommandResult {
result: build_command_from_shell_command(&command)
.status()
.map(ExitStatus::from)
.map_err(StartError::from),
command,
}
}
fn collect_command_output(
&mut self,
command: ShellCommand,
) -> CommandResult<CommandOutput, StartError> {
CommandResult {
result: build_command_from_shell_command(&command)
.output()
.map(CommandOutput::from)
.map_err(StartError::from),
command,
}
}
}

52
src/integration_test.rs Normal file
View File

@ -0,0 +1,52 @@
use crate::environment::Environment;
use crate::shell_interface::{
CommandOutput, CommandResult, ExitStatus, ShellCommand, ShellInterface, StartError,
};
use std::env::VarError;
use std::ffi::{OsStr, OsString};
use std::io::Error;
use std::path::PathBuf;
struct TestEnvironment {}
impl Environment for TestEnvironment {
fn args_os(&self) -> Vec<OsString> {
todo!()
}
fn var<K>(&self, key: K) -> Result<String, VarError>
where
K: AsRef<OsStr>,
{
todo!()
}
fn set_var<K, V>(&mut self, key: K, value: V)
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
todo!()
}
fn get_home_directory(&self) -> Option<PathBuf> {
todo!()
}
fn read_line(&mut self) -> Result<String, Error> {
todo!()
}
}
impl ShellInterface for TestEnvironment {
fn run_command(&mut self, command: ShellCommand) -> CommandResult<ExitStatus, StartError> {
todo!()
}
fn collect_command_output(
&mut self,
command: ShellCommand,
) -> CommandResult<CommandOutput, StartError> {
todo!()
}
}

View File

@ -1,6 +1,8 @@
mod action; mod action;
mod environment; mod environment;
mod file; mod file;
#[cfg(test)]
mod integration_test;
mod logger; mod logger;
mod os_str_extension; mod os_str_extension;
mod os_string_builder; mod os_string_builder;
@ -14,7 +16,7 @@ use crate::logger::{LogLevel, Logger};
use crate::os_str_extension::OsStrExtension; use crate::os_str_extension::OsStrExtension;
use crate::os_string_builder::ReplaceWithOsStr; use crate::os_string_builder::ReplaceWithOsStr;
use crate::server::{RelativeLocalPathAnker, ServerAddress}; use crate::server::{RelativeLocalPathAnker, ServerAddress};
use crate::shell_interface::{ScpParam, ServerCommand, ShellCommand, ShellInterface}; use crate::shell_interface::{ScpParam, ServerCommand, ShellCommand};
use clap::{Parser, Subcommand, ValueEnum}; use clap::{Parser, Subcommand, ValueEnum};
use lazy_regex::{lazy_regex, Lazy, Regex}; use lazy_regex::{lazy_regex, Lazy, Regex};
use server::{Server, ServerReference}; use server::{Server, ServerReference};

View File

@ -5,7 +5,6 @@ use std::error::Error;
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::iter::once; use std::iter::once;
use std::marker::PhantomData;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Output}; use std::process::{Command, Output};
use std::{io, process}; use std::{io, process};
@ -13,11 +12,44 @@ use std::{io, process};
#[derive(Debug)] #[derive(Debug)]
pub struct EnvCommand<'a, E> { pub struct EnvCommand<'a, E> {
command: ShellCommand, command: ShellCommand,
phantom_data: PhantomData<&'a E>,
#[cfg(test)]
environment: &'a mut E, environment: &'a mut E,
} }
impl<E> EnvCommand<'_, E>
where
E: ShellInterface,
{
pub fn run(self) -> CommandResult<ExitStatus, StartError> {
self.environment.run_command(self.command)
}
pub fn output(self) -> CommandResult<CommandOutput, StartError> {
self.environment.collect_command_output(self.command)
}
pub 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,
}
}
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ShellCommand { pub enum ShellCommand {
Ssh { Ssh {
@ -38,12 +70,10 @@ pub enum ShellCommand {
} }
impl ShellCommand { impl ShellCommand {
pub fn in_env<E>(self, _environment: &mut E) -> EnvCommand<E> { pub fn in_env<E>(self, environment: &mut E) -> EnvCommand<E> {
EnvCommand { EnvCommand {
command: self, command: self,
phantom_data: Default::default(), environment,
#[cfg(test)]
environment: _environment,
} }
} }
} }
@ -120,8 +150,7 @@ impl Display for ShellCommand {
} }
} }
//TODO remove crate visibility once it isn't needed anymore fn command_to_string(command: &Command) -> String {
pub(crate) fn command_to_string(command: &Command) -> String {
once(command.get_program().to_string_lossy().to_string()) once(command.get_program().to_string_lossy().to_string())
.chain(command.get_args().map(|arg| { .chain(command.get_args().map(|arg| {
let arg_str = arg.to_string_lossy(); let arg_str = arg.to_string_lossy();
@ -144,7 +173,7 @@ macro_rules! cmd {
}}; }};
} }
fn build_command_from_shell_command(shell_command: &ShellCommand) -> Command { pub fn build_command_from_shell_command(shell_command: &ShellCommand) -> Command {
match shell_command { match shell_command {
ShellCommand::Ssh { ShellCommand::Ssh {
address, address,
@ -191,51 +220,11 @@ fn build_command_from_shell_command(shell_command: &ShellCommand) -> Command {
} }
pub trait ShellInterface { pub trait ShellInterface {
fn run(self) -> CommandResult<ExitStatus, StartError>; fn run_command(&mut self, command: ShellCommand) -> CommandResult<ExitStatus, StartError>;
fn output(self) -> CommandResult<CommandOutput, StartError>; fn collect_command_output(
fn run_logged(self, logger: &Logger) -> CommandResult<LoggedRunOutput, StartError> &mut self,
where command: ShellCommand,
Self: Sized, ) -> CommandResult<CommandOutput, StartError>;
{
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,
}
}
}
}
}
impl<E> ShellInterface for EnvCommand<'_, E> {
fn run(self) -> CommandResult<ExitStatus, StartError> {
CommandResult {
result: build_command_from_shell_command(&self.command)
.status()
.map(ExitStatus::from)
.map_err(StartError),
command: self.command,
}
}
fn output(self) -> CommandResult<CommandOutput, StartError> {
CommandResult {
result: build_command_from_shell_command(&self.command)
.output()
.map(CommandOutput::from)
.map_err(StartError),
command: self.command,
}
}
} }
pub trait MaybeCast<T> { pub trait MaybeCast<T> {