use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Sender;
const READ_BUF: usize = 4096;
#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error("failed to spawn {binary}: {source}")]
Spawn {
binary: PathBuf,
source: std::io::Error,
},
#[error("work directory does not exist: {}", dir.display())]
WorkDirMissing { dir: PathBuf },
#[error("work directory is not a directory: {}", dir.display())]
WorkDirNotADir { dir: PathBuf },
#[error("child {0} stream was not captured")]
Stdio(&'static str),
}
impl CliError {
pub(super) fn spawn(binary: &Path, cwd: Option<&Path>, source: std::io::Error) -> Self {
cwd.and_then(work_dir_fault).unwrap_or(Self::Spawn {
binary: binary.to_path_buf(),
source,
})
}
}
pub fn work_dir_fault(dir: &Path) -> Option<CliError> {
if dir.is_dir() {
return None;
}
let dir = dir.to_path_buf();
Some(if dir.exists() {
CliError::WorkDirNotADir { dir }
} else {
CliError::WorkDirMissing { dir }
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Chunk {
Stdout(Vec<u8>),
Stderr(Vec<u8>),
Exited(ExitInfo),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitInfo {
Code(i32),
Signal(i32),
Unknown,
}
impl ExitInfo {
pub fn shell_code(self) -> i32 {
match self {
ExitInfo::Code(c) => c,
ExitInfo::Signal(s) => 128 + s,
ExitInfo::Unknown => -1,
}
}
}
pub(super) fn pump<R: Read>(mut reader: R, tx: Sender<Chunk>, wrap: fn(Vec<u8>) -> Chunk) {
let mut buf = [0u8; READ_BUF];
while pump_step(&mut reader, &tx, &mut buf, wrap) {}
}
pub(super) fn pump_step<R: Read>(
reader: &mut R,
tx: &Sender<Chunk>,
buf: &mut [u8],
wrap: fn(Vec<u8>) -> Chunk,
) -> bool {
let Ok(n @ 1..) = reader.read(buf) else {
return false; };
let bytes = buf.get(..n).unwrap_or_default().to_vec(); tx.send(wrap(bytes)).is_ok()
}