use std::ffi::{OsStr, OsString};
use std::io;
use std::path::{Path, PathBuf};
use super::{Child, ExitStatus};
use crate::io::AsyncReadExt;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Output {
pub status: ExitStatus,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Stdio(pub(crate) StdioKind);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum StdioKind {
Inherit,
Null,
Piped,
}
impl Stdio {
pub fn inherit() -> Self {
Self(StdioKind::Inherit)
}
pub fn null() -> Self {
Self(StdioKind::Null)
}
pub fn piped() -> Self {
Self(StdioKind::Piped)
}
}
#[derive(Clone, Debug)]
pub(crate) enum EnvChange {
Set(OsString, OsString),
Remove(OsString),
Clear,
}
#[derive(Clone, Debug)]
pub(crate) struct CommandSpec {
pub program: OsString,
pub args: Vec<OsString>,
pub env: Vec<EnvChange>,
pub current_dir: Option<PathBuf>,
pub stdin: StdioKind,
pub stdout: StdioKind,
pub stderr: StdioKind,
}
#[derive(Clone, Debug)]
pub struct Command {
spec: CommandSpec,
}
impl Command {
pub fn new(program: impl AsRef<OsStr>) -> Self {
Self {
spec: CommandSpec {
program: program.as_ref().to_os_string(),
args: Vec::new(),
env: Vec::new(),
current_dir: None,
stdin: StdioKind::Inherit,
stdout: StdioKind::Inherit,
stderr: StdioKind::Inherit,
},
}
}
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.spec.args.push(arg.as_ref().to_os_string());
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.spec
.args
.extend(args.into_iter().map(|arg| arg.as_ref().to_os_string()));
self
}
pub fn env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
self.spec.env.push(EnvChange::Set(
key.as_ref().to_os_string(),
value.as_ref().to_os_string(),
));
self
}
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
for (key, value) in vars {
self.env(key, value);
}
self
}
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
self.spec
.env
.push(EnvChange::Remove(key.as_ref().to_os_string()));
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.spec.env.push(EnvChange::Clear);
self
}
pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
self.spec.current_dir = Some(dir.as_ref().to_path_buf());
self
}
pub fn stdin(&mut self, stdio: Stdio) -> &mut Self {
self.spec.stdin = stdio.0;
self
}
pub fn stdout(&mut self, stdio: Stdio) -> &mut Self {
self.spec.stdout = stdio.0;
self
}
pub fn stderr(&mut self, stdio: Stdio) -> &mut Self {
self.spec.stderr = stdio.0;
self
}
pub fn spawn(&mut self) -> io::Result<Child> {
crate::sys::current::process::spawn(&self.spec).map(Child::from_inner)
}
pub async fn status(&mut self) -> io::Result<ExitStatus> {
self.spawn()?.wait().await
}
pub async fn output(&mut self) -> io::Result<Output> {
self.stdin(Stdio::null());
self.stdout(Stdio::piped());
self.stderr(Stdio::piped());
let mut child = self.spawn()?;
let stderr_reader = child.stderr.take().map(|mut stderr| {
crate::spawn(async move {
let mut buf = Vec::new();
stderr.read_to_end(&mut buf).await.map(|_| buf)
})
});
let mut stdout = Vec::new();
if let Some(out) = child.stdout.as_mut() {
out.read_to_end(&mut stdout).await?;
}
let stderr = match stderr_reader {
Some(handle) => handle
.await
.expect("stderr reader task should not be aborted")?,
None => Vec::new(),
};
let status = child.wait().await?;
Ok(Output {
status,
stdout,
stderr,
})
}
}