use std::{
ffi::{
OsStr,
OsString,
},
path::{
Path,
PathBuf,
},
};
use crate::command_env::env_key_eq;
use crate::command_stdin::CommandStdin;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Command {
program: OsString,
args: Vec<OsString>,
working_directory: Option<PathBuf>,
clear_environment: bool,
envs: Vec<(OsString, OsString)>,
removed_envs: Vec<OsString>,
stdin: CommandStdin,
}
impl Command {
#[inline]
pub fn new(program: &str) -> Self {
Self::new_os(program)
}
#[inline]
pub fn new_os<S>(program: S) -> Self
where
S: AsRef<OsStr>,
{
Self {
program: program.as_ref().to_owned(),
args: Vec::new(),
working_directory: None,
clear_environment: false,
envs: Vec::new(),
removed_envs: Vec::new(),
stdin: CommandStdin::Null,
}
}
#[cfg(not(windows))]
#[inline]
pub fn shell(command_line: &str) -> Self {
Self::new("sh").arg("-c").arg(command_line)
}
#[cfg(windows)]
#[inline]
pub fn shell(command_line: &str) -> Self {
Self::new("cmd").arg("/C").arg(command_line)
}
#[inline]
pub fn arg(mut self, arg: &str) -> Self {
self.args.push(OsString::from(arg));
self
}
#[inline]
pub fn arg_os<S>(mut self, arg: S) -> Self
where
S: AsRef<OsStr>,
{
self.args.push(arg.as_ref().to_owned());
self
}
#[inline]
pub fn args(mut self, args: &[&str]) -> Self {
self.args.extend(args.iter().map(OsString::from));
self
}
pub fn args_os<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.args
.extend(args.into_iter().map(|arg| arg.as_ref().to_owned()));
self
}
#[inline]
pub fn working_directory<P>(mut self, working_directory: P) -> Self
where
P: Into<PathBuf>,
{
self.working_directory = Some(working_directory.into());
self
}
#[inline]
pub fn env(mut self, key: &str, value: &str) -> Self {
self = self.env_os(key, value);
self
}
pub fn env_os<K, V>(mut self, key: K, value: V) -> Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let key = key.as_ref().to_owned();
let value = value.as_ref().to_owned();
self.removed_envs
.retain(|removed| !env_key_eq(removed, &key));
self.envs
.retain(|(existing_key, _)| !env_key_eq(existing_key, &key));
self.envs.push((key, value));
self
}
#[inline]
pub fn env_remove(mut self, key: &str) -> Self {
self = self.env_remove_os(key);
self
}
pub fn env_remove_os<S>(mut self, key: S) -> Self
where
S: AsRef<OsStr>,
{
let key = key.as_ref().to_owned();
self.envs
.retain(|(existing_key, _)| !env_key_eq(existing_key, &key));
self.removed_envs
.retain(|removed| !env_key_eq(removed, &key));
self.removed_envs.push(key);
self
}
pub fn env_clear(mut self) -> Self {
self.clear_environment = true;
self.envs.clear();
self.removed_envs.clear();
self
}
pub fn stdin_null(mut self) -> Self {
self.stdin = CommandStdin::Null;
self
}
pub fn stdin_inherit(mut self) -> Self {
self.stdin = CommandStdin::Inherit;
self
}
pub fn stdin_bytes<B>(mut self, bytes: B) -> Self
where
B: Into<Vec<u8>>,
{
self.stdin = CommandStdin::Bytes(bytes.into());
self
}
pub fn stdin_file<P>(mut self, path: P) -> Self
where
P: Into<PathBuf>,
{
self.stdin = CommandStdin::File(path.into());
self
}
#[inline]
pub fn program(&self) -> &OsStr {
&self.program
}
#[inline]
pub fn arguments(&self) -> &[OsString] {
&self.args
}
#[inline]
pub fn working_directory_override(&self) -> Option<&Path> {
self.working_directory.as_deref()
}
#[inline]
pub fn environment(&self) -> &[(OsString, OsString)] {
&self.envs
}
#[inline]
pub fn removed_environment(&self) -> &[OsString] {
&self.removed_envs
}
#[inline]
pub const fn clears_environment(&self) -> bool {
self.clear_environment
}
#[inline]
pub(crate) fn into_stdin_configuration(self) -> CommandStdin {
self.stdin
}
pub(crate) fn display_command(&self) -> String {
let mut parts = Vec::with_capacity(self.args.len() + 1);
parts.push(self.program.as_os_str());
for arg in &self.args {
parts.push(arg.as_os_str());
}
format!("{parts:?}")
}
}