use std::{
ffi::OsStr,
io,
path::Path,
process::{Child, ChildStdin, Command, Stdio},
};
use crate::utils::is_recoverable_kill_error;
pub trait ConfigureCommand {
fn current_dir(&mut self, dir: &Path);
fn env(&mut self, name: &str, value: &OsStr);
}
impl ConfigureCommand for Command {
fn current_dir(&mut self, dir: &Path) {
self.current_dir(dir);
}
fn env(&mut self, name: &str, value: &OsStr) {
self.env(name, value);
}
}
pub trait SpawnShell: ConfigureCommand {
type ShellProcess: ShellProcess;
type Reader: io::Read + 'static + Send;
type Writer: io::Write + 'static + Send;
fn spawn_shell(&mut self) -> io::Result<SpawnedShell<Self>>;
}
pub trait ShellProcess {
fn is_echoing(&self) -> bool;
fn check_is_alive(&mut self) -> io::Result<()>;
fn terminate(self) -> io::Result<()>;
}
#[derive(Debug)]
pub struct SpawnedShell<S: SpawnShell + ?Sized> {
pub shell: S::ShellProcess,
pub reader: S::Reader,
pub writer: S::Writer,
}
impl SpawnShell for Command {
type ShellProcess = ChildShell;
type Reader = os_pipe::PipeReader;
type Writer = ChildStdin;
fn spawn_shell(&mut self) -> io::Result<SpawnedShell<Self>> {
let (pipe_reader, pipe_writer) = os_pipe::pipe()?;
let mut shell = self
.stdin(Stdio::piped())
.stdout(pipe_writer.try_clone()?)
.stderr(pipe_writer)
.spawn()?;
self.stdout(Stdio::null()).stderr(Stdio::null());
let stdin = shell.stdin.take().unwrap();
Ok(SpawnedShell {
shell: ChildShell::new(shell, false),
reader: pipe_reader,
writer: stdin,
})
}
}
#[derive(Debug)]
pub struct ChildShell {
child: Child,
is_echoing: bool,
}
impl ChildShell {
pub fn new(child: Child, is_echoing: bool) -> Self {
Self { child, is_echoing }
}
pub(crate) fn set_echoing(&mut self) {
self.is_echoing = true;
}
}
impl ShellProcess for ChildShell {
fn is_echoing(&self) -> bool {
self.is_echoing
}
fn check_is_alive(&mut self) -> io::Result<()> {
if let Some(exit_status) = self.child.try_wait()? {
let message = format!("Shell process has prematurely exited: {}", exit_status);
Err(io::Error::new(io::ErrorKind::BrokenPipe, message))
} else {
Ok(())
}
}
fn terminate(mut self) -> io::Result<()> {
if self.child.try_wait()?.is_none() {
self.child.kill().or_else(|err| {
if is_recoverable_kill_error(&err) {
Ok(())
} else {
Err(err)
}
})?;
}
Ok(())
}
}