use std::io::Write;
use std::{io::Read, process::Stdio};
use crate::error::Error;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Shell {
exe: String,
}
impl Shell {
pub const fn new(exe: String) -> Self {
Self { exe }
}
pub fn exec(&self, args: Vec<String>) -> Result<String> {
self.exec_with_pipe("", args)
}
pub fn exec_with_pipe(&self, input: &str, 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,
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, Eq, Clone)]
pub 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 fn new(exe: String) -> Self {
Self {
inner: Shell::new(exe),
}
}
}
};
}
#[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", vec!["-w".into()]);
match result {
Ok(output) => assert_eq!("3\n", output.as_str()),
Err(error) => panic!("{error:?}"),
}
}
}
pub mod fuzzel;
pub mod pass;
pub mod wtype;