use std::ffi::{OsStr, OsString};
use std::io;
use std::os::windows::io::{AsHandle, OwnedHandle};
use std::path::{Path, PathBuf};
use std::process::{ExitStatus, Output};
use crate::child::{Child, SuspendedChild};
use crate::handles::Stdio;
use crate::options::SpawnOptions;
use crate::plan::{IoMode, SpawnPlan};
use crate::sys;
use crate::transaction::SpawnTransaction;
#[derive(Debug)]
pub(crate) enum Arg {
Text(OsString),
Raw(OsString),
Handle(OwnedHandle),
}
#[derive(Debug)]
pub(crate) enum EnvOp {
Set(OsString, EnvValue),
Remove(OsString),
}
#[derive(Debug)]
pub(crate) enum EnvValue {
Text(OsString),
Handle(OwnedHandle),
}
#[derive(Debug)]
pub struct Command {
pub(crate) program: OsString,
pub(crate) args: Vec<Arg>,
pub(crate) env_clear: bool,
pub(crate) env_ops: Vec<EnvOp>,
pub(crate) cwd: Option<PathBuf>,
pub(crate) stdin: Option<Stdio>,
pub(crate) stdout: Option<Stdio>,
pub(crate) stderr: Option<Stdio>,
}
impl Command {
#[must_use]
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
Self {
program: program.as_ref().to_os_string(),
args: Vec::new(),
env_clear: false,
env_ops: Vec::new(),
cwd: None,
stdin: None,
stdout: None,
stderr: None,
}
}
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
self.args.push(Arg::Text(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>,
{
for arg in args {
self.arg(arg);
}
self
}
pub fn raw_arg<S: AsRef<OsStr>>(&mut self, text: S) -> &mut Self {
self.args.push(Arg::Raw(text.as_ref().to_os_string()));
self
}
pub fn arg_handle<T: AsHandle>(&mut self, handle: &T) -> io::Result<&mut Self> {
self.args.push(Arg::Handle(sys::duplicate_local(
handle.as_handle(),
false,
)?));
Ok(self)
}
pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.env_ops.push(EnvOp::Set(
key.as_ref().to_os_string(),
EnvValue::Text(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_handle<K: AsRef<OsStr>, T: AsHandle>(
&mut self,
key: K,
handle: &T,
) -> io::Result<&mut Self> {
self.env_ops.push(EnvOp::Set(
key.as_ref().to_os_string(),
EnvValue::Handle(sys::duplicate_local(handle.as_handle(), false)?),
));
Ok(self)
}
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
self.env_ops
.push(EnvOp::Remove(key.as_ref().to_os_string()));
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.env_clear = true;
self.env_ops.clear();
self
}
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
self.cwd = Some(dir.as_ref().to_path_buf());
self
}
pub fn stdin<T: Into<Stdio>>(&mut self, stdio: T) -> &mut Self {
self.stdin = Some(stdio.into());
self
}
pub fn stdout<T: Into<Stdio>>(&mut self, stdio: T) -> &mut Self {
self.stdout = Some(stdio.into());
self
}
pub fn stderr<T: Into<Stdio>>(&mut self, stdio: T) -> &mut Self {
self.stderr = Some(stdio.into());
self
}
#[must_use]
pub fn get_program(&self) -> &OsStr {
&self.program
}
#[must_use]
pub fn get_current_dir(&self) -> Option<&Path> {
self.cwd.as_deref()
}
pub fn spawn(&mut self) -> io::Result<Child> {
self.spawn_with(SpawnOptions::new())
}
pub fn spawn_with(&mut self, options: SpawnOptions<'_>) -> io::Result<Child> {
let plan = SpawnPlan::new_running(self, options, IoMode::Spawn)?;
Ok(SpawnTransaction::new(&plan)?.commit_child())
}
pub fn spawn_suspended(&mut self) -> io::Result<SuspendedChild> {
self.spawn_suspended_with(SpawnOptions::new())
}
pub fn spawn_suspended_with(
&mut self,
options: SpawnOptions<'_>,
) -> io::Result<SuspendedChild> {
let plan = SpawnPlan::new_suspended(self, options, IoMode::Spawn)?;
Ok(SpawnTransaction::new(&plan)?.commit_suspended())
}
pub fn status(&mut self) -> io::Result<ExitStatus> {
self.status_with(SpawnOptions::new())
}
pub fn status_with(&mut self, options: SpawnOptions<'_>) -> io::Result<ExitStatus> {
self.spawn_with(options)?.wait()
}
pub fn output(&mut self) -> io::Result<Output> {
self.output_with(SpawnOptions::new())
}
pub fn output_with(&mut self, options: SpawnOptions<'_>) -> io::Result<Output> {
let plan = SpawnPlan::new_running(self, options, IoMode::Output)?;
SpawnTransaction::new(&plan)?
.commit_child()
.wait_with_output()
}
}