use std::path::Path;
use std::process::{Command, Stdio};
use tokio::process::Command as TokioCommand;
use crate::error::{GwmError, Result};
fn format_execution_error(program: &str, error: std::io::Error) -> String {
format!("failed to execute '{}': {}", program, error)
}
const COMMAND_FAILED_MESSAGE: &str = "command failed";
pub fn exec(program: &str, args: &[&str], cwd: Option<&Path>) -> Result<String> {
let mut cmd = Command::new(program);
cmd.args(args);
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let output = cmd
.output()
.map_err(|e| GwmError::GitCommand(format_execution_error(program, e)))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(GwmError::git_command(stderr.trim()))
}
}
pub fn exec_silent(program: &str, args: &[&str], cwd: Option<&Path>) -> Result<()> {
let mut cmd = Command::new(program);
cmd.args(args).stdout(Stdio::null()).stderr(Stdio::null());
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let status = cmd
.status()
.map_err(|e| GwmError::GitCommand(format_execution_error(program, e)))?;
if status.success() {
Ok(())
} else {
Err(GwmError::git_command(COMMAND_FAILED_MESSAGE))
}
}
pub async fn exec_async(program: &str, args: &[&str], cwd: Option<&Path>) -> Result<String> {
let mut cmd = TokioCommand::new(program);
cmd.args(args);
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let output = cmd
.output()
.await
.map_err(|e| GwmError::GitCommand(format_execution_error(program, e)))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(GwmError::git_command(stderr.trim()))
}
}
#[allow(dead_code)]
pub fn escape_shell_arg(arg: &str) -> String {
format!("'{}'", arg.replace('\'', "'\\''"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exec_echo() {
let result = exec("echo", &["hello"], None).unwrap();
assert_eq!(result.trim(), "hello");
}
#[test]
fn test_exec_with_args() {
let result = exec("echo", &["hello", "world"], None).unwrap();
assert_eq!(result.trim(), "hello world");
}
#[test]
fn test_exec_silent_success() {
assert!(exec_silent("true", &[], None).is_ok());
}
#[test]
fn test_exec_silent_failure() {
assert!(exec_silent("false", &[], None).is_err());
}
#[test]
fn test_exec_nonexistent_command() {
let result = exec("nonexistent_command_12345", &[], None);
assert!(result.is_err());
}
#[test]
fn test_escape_shell_arg_simple() {
assert_eq!(escape_shell_arg("test"), "'test'");
}
#[test]
fn test_escape_shell_arg_with_single_quote() {
assert_eq!(escape_shell_arg("it's"), "'it'\\''s'");
}
#[test]
fn test_escape_shell_arg_with_spaces() {
assert_eq!(escape_shell_arg("hello world"), "'hello world'");
}
#[test]
fn test_escape_shell_arg_empty() {
assert_eq!(escape_shell_arg(""), "''");
}
}