gitcore 1.5.0

A secure, zero-friction Git identity manager for developers who juggle multiple accounts.
Documentation
use std::io;
use std::process::{Command, Output};

pub(crate) trait CommandRunner: Send + Sync {
    fn run(&self, command: &str, args: &[&str]) -> io::Result<Output>;

    fn run_checked(&self, command: &str, args: &[&str]) -> io::Result<Output> {
        let output = self.run(command, args)?;
        if output.status.success() {
            Ok(output)
        } else {
            Err(command_error(command, &output))
        }
    }
}

#[derive(Debug, Default)]
pub(crate) struct SystemCommandRunner;

impl CommandRunner for SystemCommandRunner {
    fn run(&self, command: &str, args: &[&str]) -> io::Result<Output> {
        Command::new(command).args(args).output().map_err(|err| {
            if err.kind() == io::ErrorKind::NotFound {
                io::Error::new(
                    io::ErrorKind::NotFound,
                    format!(
                        "Executable '{}' not found in PATH. Please make sure it is installed.",
                        command
                    ),
                )
            } else {
                err
            }
        })
    }
}

fn command_error(command: &str, output: &Output) -> io::Error {
    let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
    let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
    let detail = if !stderr.is_empty() {
        stderr
    } else if !stdout.is_empty() {
        stdout
    } else {
        format!("exit status: {}", output.status)
    };

    io::Error::other(format!("{command}: {detail}"))
}

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

    #[test]
    fn test_system_command_runner_not_found() {
        let runner = SystemCommandRunner;
        let err = runner.run("nonexistent-executable-12345", &[]).unwrap_err();
        assert_eq!(err.kind(), io::ErrorKind::NotFound);
        assert!(
            err.to_string()
                .contains("Executable 'nonexistent-executable-12345' not found in PATH")
        );
    }
}