use crate::{cmd::Command, error::APipeError, output::Output};
use std::{
ffi::OsStr,
ops,
process::{Child, Stdio},
};
type Result<T> = std::result::Result<T, APipeError>;
#[derive(Debug, Default)]
pub struct CommandPipe {
pub(crate) pipeline: Vec<Command>,
last_spawned: Option<Child>,
}
impl ops::BitOr<Command> for CommandPipe {
type Output = CommandPipe;
fn bitor(mut self, rhs: Command) -> Self {
self.pipeline.push(rhs);
self
}
}
#[cfg(feature = "parser")]
impl TryFrom<&str> for CommandPipe {
type Error = APipeError;
fn try_from(value: &str) -> Result<Self> {
let mut pipe = CommandPipe::new();
for cmd in value.split_terminator("|") {
match Command::parse_str(cmd) {
Ok(c) => pipe.pipeline.push(c),
Err(e) => return Err(e),
}
}
Ok(pipe)
}
}
impl CommandPipe {
pub fn new() -> Self {
CommandPipe {
pipeline: Vec::new(),
last_spawned: None,
}
}
pub fn add_command<S>(&mut self, c: S) -> &mut Self
where
S: AsRef<OsStr>,
{
let command = c.into();
self.pipeline.push(command);
self
}
pub fn arg<S>(&mut self, arg: S) -> &mut Self
where
S: AsRef<OsStr>,
{
let command = self
.pipeline
.pop()
.expect("No Command in pipe to add args to.");
self.pipeline.push(command.arg(arg));
self
}
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let command = self
.pipeline
.pop()
.expect("No Command in pipe to add args to.");
self.pipeline.push(command.args(args));
self
}
pub fn spawn(&mut self) -> Result<()> {
for command in self.pipeline.iter_mut() {
let stdin = self.last_spawned.take().map_or(Stdio::null(), |mut std| {
std.stdout.take().map_or(Stdio::null(), Stdio::from)
});
let mut child = command
.0
.stdin(stdin)
.stdout(Stdio::piped())
.spawn()
.map_err(|e| APipeError::ChildProcess(e, "Failed to spawn child command"))?;
child.wait().map_err(|e| {
APipeError::ChildProcess(e, "Child process exited with error code.")
})?;
self.last_spawned.replace(child);
}
Ok(())
}
pub fn spawn_with_output(&mut self) -> Result<Output> {
self.spawn()?;
self.output()
}
pub fn output(&mut self) -> Result<Output> {
if let Some(last_proc) = self.last_spawned.take() {
let output = last_proc.wait_with_output().map_err(|e| {
APipeError::ChildProcess(e, "Child process exited with error code.")
})?;
return Ok(Output::from(output));
} else {
Err(APipeError::NoRunningProcesses)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arg() {
let mut pipe = CommandPipe::new();
pipe.add_command("ls").arg("-la").arg("~/Documents");
let args: Vec<&OsStr> = pipe.pipeline[0].0.get_args().collect();
assert_eq!(args, &["-la", "~/Documents"])
}
#[test]
fn test_args() {
let mut pipe = CommandPipe::new();
pipe.add_command("ls").args(vec!["-la", "~/Documents"]);
let args: Vec<&OsStr> = pipe.pipeline[0].0.get_args().collect();
assert_eq!(args, &["-la", "~/Documents"])
}
#[test]
fn test_pipe() {
let mut pipe = CommandPipe::new();
pipe.add_command("echo")
.arg("This is a test.")
.add_command("grep")
.arg("-Eo")
.arg(r"\w\w\sa[^.]*")
.spawn()
.expect("Failed to spawn pipe.");
let output = pipe.output().unwrap();
assert_eq!(output.stdout(), "is a test\n".as_bytes());
}
#[test]
#[should_panic]
fn test_add_arg_without_command() {
let mut pipe = CommandPipe::new();
pipe.arg("ls");
}
#[test]
fn test_spawn_with_output() {
let output = CommandPipe::new()
.add_command("echo")
.arg("This is a test.")
.add_command("grep")
.arg("-Eo")
.arg(r"\w\w\sa[^.]*")
.spawn_with_output()
.unwrap();
assert_eq!(output.stdout(), "is a test\n".as_bytes());
}
#[test]
fn test_overload() {
let mut pipe = CommandPipe::new();
pipe = pipe | Command::new("grep");
assert_eq!(pipe.pipeline[0].0.get_program(), "grep");
let output = (Command::new("echo").arg("This is a test.")
| Command::new("grep").args(&["-Eo", r"\w\w\sa[^.]*"]))
.spawn_with_output()
.unwrap();
assert_eq!(output.stdout(), "is a test\n".as_bytes());
}
#[cfg(feature = "parser")]
#[test]
fn test_try_from() {
let mut pipe =
CommandPipe::try_from(r#"echo "This is a test." | grep -Eo \w\w\sa[^.]*"#).unwrap();
let output = pipe.spawn_with_output().unwrap();
assert_eq!(output.stdout(), "is a test\n".as_bytes());
}
#[cfg(feature = "parser")]
#[test]
fn test_try_from_command() {
let mut pipe = CommandPipe::try_from(r#"echo "This is a test."#).unwrap();
let output = pipe.spawn_with_output().unwrap();
assert_eq!(output.stdout(), "This is a test.\n".as_bytes());
}
#[cfg(feature = "parser")]
#[test]
fn test_try_from_empty_str() {
let pipe = CommandPipe::try_from("");
if let Err(_) = pipe {
panic!("Pipe should be empty!")
};
}
#[cfg(feature = "parser")]
#[test]
fn test_try_from_invalid_pipe() {
let pipe = CommandPipe::try_from(" | ");
if let Ok(_) = pipe {
panic!("Shouldn't be able to parse invalid pipe!")
};
}
}