use std::ffi::{OsStr, OsString};
use crate::CommandError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandSpec {
program: OsString,
args: Vec<OsString>,
}
impl CommandSpec {
pub fn new(program: impl Into<OsString>) -> Result<Self, CommandError> {
let program = program.into();
validate_program(&program)?;
Ok(Self {
program,
args: Vec::new(),
})
}
pub fn with_arg(mut self, argument: impl Into<OsString>) -> Result<Self, CommandError> {
let argument = argument.into();
validate_argument(&argument)?;
self.args.push(argument);
Ok(self)
}
pub fn with_args<I, S>(mut self, arguments: I) -> Result<Self, CommandError>
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
for argument in arguments {
let argument = argument.into();
validate_argument(&argument)?;
self.args.push(argument);
}
Ok(self)
}
pub fn program(&self) -> &OsStr {
&self.program
}
pub fn args(&self) -> &[OsString] {
&self.args
}
pub fn into_parts(self) -> (OsString, Vec<OsString>) {
(self.program, self.args)
}
}
fn validate_program(program: &OsStr) -> Result<(), CommandError> {
if program.is_empty() {
return Err(CommandError::EmptyProgram);
}
if contains_nul(program) {
return Err(CommandError::ProgramContainsNul);
}
Ok(())
}
fn validate_argument(argument: &OsStr) -> Result<(), CommandError> {
if contains_nul(argument) {
return Err(CommandError::ArgumentContainsNul);
}
Ok(())
}
pub(crate) fn contains_nul(value: &OsStr) -> bool {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
value.as_bytes().contains(&0)
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
value.encode_wide().any(|unit| unit == 0)
}
#[cfg(not(any(unix, windows)))]
{
value.to_string_lossy().contains('\0')
}
}