use crate::escape::escape;
use super::child::Child;
use super::stdio::TryFromChildIo;
use super::Stdio;
use super::{Error, Session};
use std::borrow::Cow;
use std::ffi::OsStr;
use std::ops::Deref;
use std::process;
#[derive(Debug)]
pub(crate) enum CommandImp {
#[cfg(feature = "process-mux")]
ProcessImpl(super::process_impl::Command),
#[cfg(feature = "native-mux")]
NativeMuxImpl(super::native_mux_impl::Command),
}
#[cfg(feature = "process-mux")]
impl From<super::process_impl::Command> for CommandImp {
fn from(imp: super::process_impl::Command) -> Self {
CommandImp::ProcessImpl(imp)
}
}
#[cfg(feature = "native-mux")]
impl From<super::native_mux_impl::Command> for CommandImp {
fn from(imp: super::native_mux_impl::Command) -> Self {
CommandImp::NativeMuxImpl(imp)
}
}
#[cfg(any(feature = "process-mux", feature = "native-mux"))]
macro_rules! delegate {
($impl:expr, $var:ident, $then:block) => {{
match $impl {
#[cfg(feature = "process-mux")]
CommandImp::ProcessImpl($var) => $then,
#[cfg(feature = "native-mux")]
CommandImp::NativeMuxImpl($var) => $then,
}
}};
}
#[cfg(not(any(feature = "process-mux", feature = "native-mux")))]
macro_rules! delegate {
($impl:expr, $var:ident, $then:block) => {{
unreachable!("Neither feature process-mux nor native-mux is enabled")
}};
}
pub trait OverSsh {
fn over_ssh<S: Deref<Target = Session> + Clone>(
&self,
session: S,
) -> Result<OwningCommand<S>, crate::Error>;
}
impl OverSsh for std::process::Command {
fn over_ssh<S: Deref<Target = Session> + Clone>(
&self,
session: S,
) -> Result<OwningCommand<S>, crate::Error> {
if self.get_envs().len() > 0 {
return Err(crate::Error::CommandHasEnv);
}
if self.get_current_dir().is_some() {
return Err(crate::Error::CommandHasCwd);
}
let program_escaped: Cow<'_, OsStr> = escape(self.get_program());
let mut command = Session::to_raw_command(session, program_escaped);
let args = self.get_args().map(escape);
command.raw_args(args);
Ok(command)
}
}
impl OverSsh for tokio::process::Command {
fn over_ssh<S: Deref<Target = Session> + Clone>(
&self,
session: S,
) -> Result<OwningCommand<S>, crate::Error> {
self.as_std().over_ssh(session)
}
}
impl<S> OverSsh for &S
where
S: OverSsh,
{
fn over_ssh<U: Deref<Target = Session> + Clone>(
&self,
session: U,
) -> Result<OwningCommand<U>, crate::Error> {
<S as OverSsh>::over_ssh(self, session)
}
}
impl<S> OverSsh for &mut S
where
S: OverSsh,
{
fn over_ssh<U: Deref<Target = Session> + Clone>(
&self,
session: U,
) -> Result<OwningCommand<U>, crate::Error> {
<S as OverSsh>::over_ssh(self, session)
}
}
#[derive(Debug)]
pub struct OwningCommand<S> {
session: S,
imp: CommandImp,
stdin_set: bool,
stdout_set: bool,
stderr_set: bool,
}
impl<S> OwningCommand<S> {
pub(crate) fn new(session: S, imp: CommandImp) -> Self {
Self {
session,
imp,
stdin_set: false,
stdout_set: false,
stderr_set: false,
}
}
pub fn arg<A: AsRef<str>>(&mut self, arg: A) -> &mut Self {
self.raw_arg(&*shell_escape::unix::escape(Cow::Borrowed(arg.as_ref())))
}
pub fn raw_arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut Self {
delegate!(&mut self.imp, imp, {
imp.raw_arg(arg.as_ref());
});
self
}
pub fn args<I, A>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = A>,
A: AsRef<str>,
{
for arg in args {
self.arg(arg);
}
self
}
pub fn raw_args<I, A>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = A>,
A: AsRef<OsStr>,
{
for arg in args {
self.raw_arg(arg);
}
self
}
pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
delegate!(&mut self.imp, imp, {
imp.stdin(cfg.into());
});
self.stdin_set = true;
self
}
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
delegate!(&mut self.imp, imp, {
imp.stdout(cfg.into());
});
self.stdout_set = true;
self
}
pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
delegate!(&mut self.imp, imp, {
imp.stderr(cfg.into());
});
self.stderr_set = true;
self
}
}
impl<S: Clone> OwningCommand<S> {
async fn spawn_impl(&mut self) -> Result<Child<S>, Error> {
Ok(Child::new(
self.session.clone(),
delegate!(&mut self.imp, imp, {
let (imp, stdin, stdout, stderr) = imp.spawn().await?;
(
imp.into(),
stdin.map(TryFromChildIo::try_from).transpose()?,
stdout.map(TryFromChildIo::try_from).transpose()?,
stderr.map(TryFromChildIo::try_from).transpose()?,
)
}),
))
}
pub async fn spawn(&mut self) -> Result<Child<S>, Error> {
if !self.stdin_set {
self.stdin(Stdio::inherit());
}
if !self.stdout_set {
self.stdout(Stdio::inherit());
}
if !self.stderr_set {
self.stderr(Stdio::inherit());
}
self.spawn_impl().await
}
pub async fn output(&mut self) -> Result<process::Output, Error> {
if !self.stdin_set {
self.stdin(Stdio::null());
}
if !self.stdout_set {
self.stdout(Stdio::piped());
}
if !self.stderr_set {
self.stderr(Stdio::piped());
}
self.spawn_impl().await?.wait_with_output().await
}
pub async fn status(&mut self) -> Result<process::ExitStatus, Error> {
self.spawn().await?.wait().await
}
}