use std::io::Write;
use std::{io::Read, process::Stdio};
use crate::error::Error;
pub(crate) type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct Shell {
exe: String,
}
impl Shell {
pub(crate) fn new(exe: String) -> Self {
Self { exe }
}
pub(crate) fn exec(&self, args: Vec<String>) -> Result<String> {
self.exec_with_pipe(String::default(), args)
}
pub(crate) fn exec_with_pipe(&self, input: String, args: Vec<String>) -> Result<String> {
let cfg = if input.is_empty() {
Stdio::null()
} else {
Stdio::piped()
};
let program = self.exe.clone();
let mut process = std::process::Command::new(program.as_str())
.args(args)
.stdin(cfg)
.stdout(Stdio::piped())
.spawn()
.map_err(Error::Io)?;
if !input.is_empty()
&& let Some(mut stdin) = process.stdin.take()
{
stdin.write_all(input.as_bytes()).map_err(Error::Io)?;
};
let output = if let Some(mut stdout) = process.stdout.take() {
let mut buf = String::new();
stdout.read_to_string(&mut buf).map_err(Error::Io)?;
buf
} else {
String::default()
};
let status = process.wait().map_err(Error::Io)?;
if !status.success() {
return Err(Error::ChildExitStatusFailed(
program.clone(),
status.code().unwrap_or(1),
));
}
Ok(output)
}
}
macro_rules! shell {
($StructName:ident, $exe_path:expr) => {
#[allow(unused_imports)]
use crate::shell::Shell;
#[doc = concat!("A wrapper of [`Shell`] around `", $exe_path, "`
[`Shell`]: crate::shell::Shell")]
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct $StructName {
inner: Shell,
}
impl<'a> Default for $StructName {
#[doc = concat!("Returns an instance of [`", stringify!($StructName), "`]")]
fn default() -> Self {
Self {
inner: Shell::new(String::from($exe_path)),
}
}
}
#[cfg(test)]
impl $StructName {
pub(crate) fn new(exe: String) -> Self {
Self {
inner: Shell::new(exe),
}
}
}
};
}
pub(crate) use shell;
#[cfg(test)]
mod test {
use super::*;
use std::io::ErrorKind;
#[test]
fn test_exec_error_not_found() {
let shell = Shell::new(String::from("test/scripts/nothing"));
let result = shell.exec(vec![]);
assert!(result.is_err());
if let Err(Error::Io(err)) = result {
assert_eq!(ErrorKind::NotFound, err.kind())
}
}
#[test]
fn test_exec_error_failure() {
let shell = Shell::new(String::from("/usr/bin/false"));
let result = shell.exec(vec![]);
assert!(result.is_err());
if let Err(err) = result {
assert_eq!(
Error::ChildExitStatusFailed(String::from("/usr/bin/false"), 1),
err
)
}
}
#[test]
fn test_exec_with_pipe() {
let wc = Shell::new(String::from("wc"));
let result = wc.exec_with_pipe("foo bar baz".into(), vec!["-w".into()]);
match result {
Ok(output) => assert_eq!("3\n", output.as_str()),
Err(error) => panic!("{:?}", error),
}
}
}
pub(crate) mod fuzzel;
pub(crate) mod pass;
pub(crate) mod wtype;