rsbash 2.1.0

run bash commands from rust.
Documentation
use std::ffi::{CString, NulError};

#[derive(Debug)]
pub(crate) struct BashCommand {
    command: CString,
}

impl BashCommand {
    pub fn new<S: AsRef<str>>(s: S) -> Result<Self, NulError> {
        let quoted = Self::quote(s.as_ref());
        Ok(Self {
            command: CString::new(Self::format(quoted))?,
        })
    }

    pub fn command(&self) -> CString {
        self.command.clone()
    }

    fn format(s: String) -> String {
        format!("/usr/bin/env bash -c {}", s)
    }

    fn quote(s: &str) -> String {
        shell_words::quote(s).to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::BashCommand;

    #[test]
    fn test_bash_command_formats_correctly() {
        let input = String::from("hi");
        let expected = String::from("/usr/bin/env bash -c hi");
        assert_eq!(BashCommand::format(input), expected);
    }

    #[test]
    fn test_bash_command_quotes_correctly() {
        assert_eq!(BashCommand::quote("hi"), "hi".to_string());

        let input = "\"\"'blah' \'blah\' 'blah'''";
        let expected = "'\"\"'\\''blah'\\'' '\\''blah'\\'' '\\''blah'\\'''\\'''\\'''";
        assert_eq!(BashCommand::quote(input), expected.to_string());
    }

    #[test]
    fn test_bash_command_formats_cstring_correctly() -> anyhow::Result<()> {
        let command = BashCommand::new("hello")?.command();
        Ok(assert_eq!(command.into_string()?, "/usr/bin/env bash -c hello".to_string()))
    }
}