gitcore 1.4.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()
    }
}

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}"))
}