use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::process::{Command, ExitStatus, Output, Stdio};
use std::io::Write;
use std::iter::IntoIterator;
pub struct OutputString {
pub status: ExitStatus,
pub stdout: String,
pub stderr: String,
}
#[derive(Debug)]
pub enum ExecError {
Io(std::io::Error),
SpawnFailed(String, std::io::Error),
}
impl std::fmt::Display for ExecError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExecError::Io(err) => write!(f, "IO error: {}", err),
ExecError::SpawnFailed(cmd, err) => write!(f, "failed to spawn {}: {}", cmd, err),
}
}
}
impl std::error::Error for ExecError {}
impl From<std::io::Error> for ExecError {
fn from(err: std::io::Error) -> Self {
ExecError::Io(err)
}
}
pub type Result<T> = std::result::Result<T, ExecError>;
fn setup_envs<I, K, V>(cmd: &mut Command, vars: I) -> &mut Command
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let filtered_env: HashMap<String, String> = env::vars()
.filter(|(k, _)| k == "TERM" || k == "TZ" || k == "PATH" || k == "LD_LIBRARY_PATH")
.collect();
cmd.env_clear()
.envs(filtered_env)
.envs(vars)
.env("LANG", "C")
}
fn exec_internal<I, S, IKV, K, V>(
target_exe: &str,
args: I,
env: IKV,
in_bytes: Option<&[u8]>,
) -> Result<OutputString>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
IKV: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut cmd: Command = Command::new(target_exe);
setup_envs(&mut cmd, env).args(args);
if in_bytes.is_some() {
cmd.stdin(Stdio::piped());
}
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = cmd
.spawn()
.map_err(|e| ExecError::SpawnFailed(target_exe.to_string(), e))?;
if let Some(bytes) = in_bytes {
let stdin = child
.stdin
.as_mut()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "failed to get stdin"))?;
let r = stdin.write_all(bytes);
match r {
Err(ioe) if ioe.kind() == std::io::ErrorKind::BrokenPipe => {
}
_ => {
r?;
}
}
}
let output: Output = child.wait_with_output()?;
Ok(OutputString {
status: output.status,
stdout: String::from(String::from_utf8_lossy(&output.stdout)),
stderr: String::from(String::from_utf8_lossy(&output.stderr)),
})
}
pub fn exec_target<I, S>(target_exe: &str, args: I) -> Result<OutputString>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
exec_internal(target_exe, args, Vec::<(&str, &str)>::new(), None)
}
pub fn exec_target_with_env<I, S, IKV, K, V>(
target_exe: &str,
args: I,
env: IKV,
) -> Result<OutputString>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
IKV: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
exec_internal(target_exe, args, env, None)
}
pub fn exec_target_with_in<I, S>(target_exe: &str, args: I, in_bytes: &[u8]) -> Result<OutputString>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
exec_internal(target_exe, args, Vec::<(&str, &str)>::new(), Some(in_bytes))
}
pub fn exec_target_with_env_in<I, S, IKV, K, V>(
target_exe: &str,
args: I,
env: IKV,
in_bytes: &[u8],
) -> Result<OutputString>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
IKV: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
exec_internal(target_exe, args, env, Some(in_bytes))
}
pub fn args_from(s: &str) -> Vec<String> {
let mut v: Vec<String> = Vec::new();
let mut ss = String::new();
let mut enter_q: bool = false;
let mut enter_qq: bool = false;
let mut back_slash: bool = false;
for c in s.chars() {
if back_slash {
ss.push(c);
back_slash = false;
continue;
}
if c == '\\' {
back_slash = true;
continue;
}
if enter_q {
if c == '\'' {
v.push(ss.clone());
ss.clear();
enter_q = false;
} else {
ss.push(c);
}
continue;
}
if enter_qq {
if c == '\"' {
v.push(ss.clone());
ss.clear();
enter_qq = false;
} else {
ss.push(c);
}
continue;
}
match c {
'\'' => {
enter_q = true;
continue;
}
'\"' => {
enter_qq = true;
continue;
}
' ' => {
if !ss.is_empty() {
v.push(ss.clone());
ss.clear();
}
}
_ => {
ss.push(c);
}
}
}
if !ss.is_empty() {
v.push(ss.clone());
ss.clear();
}
v
}