serde_args 0.2.0

Command line argument parsing with serde.
Documentation
use std::{
    ffi::OsString,
    fmt,
    fmt::{
        Display,
        Formatter,
    },
    io,
    io::Read,
    path::PathBuf,
    process,
    process::Stdio,
};

#[derive(Debug)]
pub enum Error {
    IO(io::Error),
    Stdout(String),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Self::IO(error)
    }
}

impl From<String> for Error {
    fn from(error: String) -> Self {
        Self::Stdout(error)
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::IO(error) => write!(formatter, "IO error: {}", error),
            Self::Stdout(error) => write!(
                formatter,
                "failed with nonzero exit code; captured stdout: {}",
                error
            ),
        }
    }
}

pub struct Command {
    pub args: Vec<OsString>,
    pub path: PathBuf,
    pub binary_name: String,
}

impl Command {
    pub fn new<Path>(path: Path) -> Self
    where
        Path: Into<PathBuf>,
    {
        let path = path.into();
        Self {
            args: Vec::new(),
            #[cfg(not(target_os = "windows"))]
            binary_name: path.file_name().unwrap().to_string_lossy().into(),
            #[cfg(target_os = "windows")]
            binary_name: format!("{}.exe", path.file_name().unwrap().to_string_lossy()),
            path,
        }
    }

    pub fn args<Arg, Args>(&mut self, args: Args) -> &mut Self
    where
        Args: IntoIterator<Item = Arg>,
        Arg: Into<OsString>,
    {
        self.args.extend(args.into_iter().map(|arg| arg.into()));
        self
    }

    pub fn run(&self) -> Result<(), Error> {
        let mut program = process::Command::new("cargo")
            .stdout(Stdio::piped())
            .stderr(Stdio::null())
            .args(["run", "--"])
            .args(&self.args)
            .current_dir(&self.path)
            .spawn()?;
        if program.wait()?.success() {
            Ok(())
        } else {
            let mut error = String::new();
            program
                .stdout
                .ok_or(io::Error::other("no stdout"))?
                .read_to_string(&mut error)?;
            Err(Error::Stdout(error))
        }
    }
}

#[macro_export]
macro_rules! assert_run_ok {
    ($command:expr) => {
        ::claims::assert_ok!($command.run());
    };
}

#[macro_export]
macro_rules! assert_run_err {
    ($command:expr, $expected:literal) => {
        let name = $command.binary_name.clone();
        let error = ::claims::assert_err!($command.run());
        if let command::Error::Stdout(stdout) = error {
            assert_eq!(stdout, format!($expected, name = name));
        } else {
            panic!("command failed to execute: {}", error);
        }
    };
}

#[macro_export]
macro_rules! assert_run_err_literal {
    ($command:expr, $expected:literal) => {
        let error = ::claims::assert_err!($command.run());
        if let command::Error::Stdout(stdout) = error {
            assert_eq!(stdout, $expected);
        } else {
            panic!("command failed to execute: {}", error);
        }
    };
}