mkargs 1.0.0

Build command arguments
Documentation
/*!
Build command arguments.

```
use std::process::Command;
use mkargs::mkargs;

let world = "world".to_string();
let mut args = mkargs!["hello", world];

args.push_pair("foo", "bar");

Command::new("echo")
    .args(&*args)
    .spawn()
    .expect("echo command failed to start");
```
*/
use std::{ffi::OsString, fmt::{Display, self}, ops::Deref};

/// Represents list of command arguments.
#[derive(Debug, Default, Clone)]
pub struct MkArgs(Vec<OsString>);

impl MkArgs {
    /// Crates empty list of arguments.
    pub fn new() -> MkArgs {
        Default::default()
    }

    /// Appends argument returning modified Self.
    pub fn with(mut self, arg: impl Into<OsString>) -> Self {
        self.push(arg);
        self
    }

    /// Appends pair of arguments returning modified Self.
    pub fn with_pair(mut self, arg1: impl Into<OsString>, arg2: impl Into<OsString>) -> Self {
        self.push_pair(arg1, arg2);
        self
    }

    /// Appends another list of arguments returning modified Self.
    pub fn with_append(mut self, args: MkArgs) -> Self {
        self.append(args);
        self
    }

    /// Appends argument if condition is true returning modified Self.
    pub fn with_cond(mut self, arg: impl Into<OsString>, cond: bool) -> Self {
        if cond {
            self.push(arg);
        }
        self
    }

    /// Appends pair of arguments if condition is true returning modified Self.
    pub fn with_pair_cond(mut self, arg1: impl Into<OsString>, arg2: impl Into<OsString>, cond: bool) -> Self {
        if cond {
            self.push_pair(arg1, arg2);
        }
        self
    }

    /// Appends another list of arguments if condition is true returning modified Self.
    pub fn with_append_cond(mut self, args: MkArgs, cond: bool) -> Self {
        if cond {
            self.append(args);
        }
        self
    }

    /// Appends argument.
    pub fn push(&mut self, arg: impl Into<OsString>) {
        self.0.push(arg.into())
    }

    /// Appends pair of arguments.
    pub fn push_pair(&mut self, arg1: impl Into<OsString>, arg2: impl Into<OsString>) {
        self.0.push(arg1.into());
        self.0.push(arg2.into());
    }

    /// Appends another list of arguments.
    pub fn append(&mut self, args: impl Into<MkArgs>) {
        self.0.extend(args.into().0.into_iter());
    }

    /// Returns constructed argument list.
    pub fn into_inner(self) -> Vec<OsString> {
        self.0
    }

    /// Returns slice of constructed argument list.
    pub fn as_slice(&self) -> &[OsString] {
        self.0.as_slice()
    }

    /// Converts constructed argument list into vector of UTF-8 strings.
    ///
    /// Returns `Err(OsString)` for first argument that could not be converted to UTF-8 encoding.
    pub fn into_strings(self) -> Result<Vec<String>, OsString> {
        self.0.into_iter().map(|a| a.into_string()).collect()
    }
}

impl Deref for MkArgs {
    type Target = [OsString];

    fn deref(&self) -> &Self::Target {
        self.as_slice()
    }
}

impl From<MkArgs> for Vec<OsString> {
    fn from(args: MkArgs) -> Vec<OsString> {
        args.into_inner()
    }
}

impl TryFrom<MkArgs> for Vec<String> {
    type Error = OsString;

    fn try_from(args: MkArgs) -> Result<Vec<String>, OsString> {
        args.into_strings()
    }
}

impl From<Vec<OsString>> for MkArgs {
    fn from(args: Vec<OsString>) -> MkArgs {
        MkArgs(args)
    }
}

impl<'i> From<&'i [OsString]> for MkArgs {
    fn from(args: &'i [OsString]) -> MkArgs {
        MkArgs(args.to_vec())
    }
}

impl Display for MkArgs {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.0)
    }
}

#[macro_export]
macro_rules! mkargs {
    ($($arg:expr),*) => {{
        use std::ffi::OsString;
        let args: &[OsString] = &[$(Into::<OsString>::into($arg)),*];
        $crate::MkArgs::from(args)
    }};
}

#[cfg(target_family = "unix")]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_echo() {
        use std::process::Command;

        let world = "world".to_string();
        let mut args = mkargs!["hello", world];

        args.push_pair("foo", "bar");

        let out = Command::new("echo")
            .args(&*args)
            .output()
            .expect("echo command failed to start");

        assert_eq!(out.stdout, b"hello world foo bar\n");
    }
}