rsbash 2.1.0

run bash commands from rust.
Documentation
use std::str;

use crate::{command::BashCommand, error::RashError, process::Process};

type Out = (i32, String, String);

#[cfg(unix)]
pub fn __command<S: AsRef<str>>(c: S) -> Result<Out, RashError> {
    let command = BashCommand::new(c)?;
    let mut process = Process::new();
    Ok(unsafe {
        process.open(command)?;
        let ret = process.close()?;
        let stdout = process.stdout()?;
        let stderr = process.stderr()?;
        (ret, stdout, stderr)
    })
}

#[cfg(test)]
mod tests {
    use tempfile::TempDir;

    use super::*;

    lazy_static! {
        static ref EMPTY_STRING: String = String::default();
    }

    #[test]
    fn test_commands_return_zero() {
        [
            "ls",
            "ls -l | cat -",
            "ls | cat $(echo '-')",
            "[[ 5 -eq $((3 + 2)) ]]",
            "/bin/sh -c 'echo hi'",
            "exit 0",
        ]
        .iter()
        .for_each(|c| {
            let (r, _, _) = __command(c).unwrap();
            assert_eq!(r, 0);
        });
    }

    #[test]
    fn test_commands_return_non_zero() {
        [("i_am_not_a_valid_executable", 127), ("echo hi | grep 'bye'", 1), ("exit 54;", 54)]
            .iter()
            .for_each(move |(c, ret)| {
                let (r, _, _) = __command(c).unwrap();
                assert_eq!(r, *ret);
            });
    }

    #[test]
    fn test_commands_stdout() {
        [
            ("", ""),
            ("echo hi", "hi\n"),
            ("echo hi >/dev/null", ""),
            ("echo -n $((3 + 2 - 1))", "4"),
        ]
        .iter()
        .for_each(move |(c, out)| {
            default_assertions(__command(c).unwrap(), out);
        });
    }

    #[test]
    fn test_commands_stderr() -> Result<(), RashError> {
        Ok(assert_eq!(__command("echo -n 'hi' >&2")?, (0, EMPTY_STRING.clone(), "hi".to_string())))
    }

    #[test]
    fn test_combined_stdout() -> Result<(), RashError> {
        Ok(default_assertions(__command("echo -n hi; echo -n bye 2>&1;")?, "hibye"))
    }

    #[test]
    fn test_redirect_to_and_read_from_file() -> anyhow::Result<()> {
        let temp_dir = TempDir::new()?;
        let path = temp_dir.path().to_str().unwrap();
        let script = format!("cd {path}; echo -n 'foo' > bar.txt; cat bar.txt;");

        default_assertions(__command(script)?, "foo");

        Ok(temp_dir.close()?)
    }

    #[test]
    fn test_run_script() -> anyhow::Result<()> {
        let temp_dir = TempDir::new()?;
        let path = temp_dir.path().to_str().unwrap();
        let message = "hi from within the script!";
        let script = format!(
            "cd {path}; echo -n \"echo -n '{message}'\" > bar.sh; chmod u+x bar.sh; ./bar.sh;"
        );

        default_assertions(__command(script)?, message);

        Ok(temp_dir.close()?)
    }

    #[test]
    fn test_quotes() -> Result<(), RashError> {
        let message = "a new line \n a day keeps the doctors away";
        default_assertions(__command(format!("echo -n '{}'", message))?, message);

        let message = "\"\"'blah' \'blah\' 'blah'''";
        let expected = "blah blah blah";
        default_assertions(__command(format!("echo -n {}", message))?, expected);

        let message = "hello world";
        let expected = "hello world\n";
        default_assertions(__command(format!("echo {}", message))?, expected);
        Ok(())
    }

    #[test]
    fn test_comments() -> Result<(), RashError> {
        Ok(default_assertions(__command("#echo 'i am silent'")?, EMPTY_STRING.as_ref()))
    }

    #[test]
    fn test_backslashes() -> Result<(), RashError> {
        let c = "echo \
        -n \
        hi \
        there";

        Ok(default_assertions(__command(c)?, "hi there"))
    }

    fn default_assertions(o: Out, expected_stdout: &str) -> () {
        assert_eq!(o, (0, expected_stdout.to_string(), EMPTY_STRING.clone()))
    }
}