zinit 0.3.7

Process supervisor with dependency management
Documentation
//! Command execution wrapper with logging

use super::error::StorageError;
use std::process::{Command, Output};

/// Execute a command and return its output
///
/// Logs the command being executed and its output at debug level.
/// Returns an error if the command fails to execute or exits with non-zero status.
pub fn run(cmd: &str, args: &[&str]) -> Result<Output, StorageError> {
    let full_cmd = format!("{} {}", cmd, args.join(" "));
    log::debug!("executing: {}", full_cmd);

    let output =
        Command::new(cmd)
            .args(args)
            .output()
            .map_err(|e| StorageError::CommandFailed {
                cmd: full_cmd.clone(),
                error: e.to_string(),
            })?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    if !stdout.is_empty() {
        log::debug!("stdout: {}", stdout.trim());
    }
    if !stderr.is_empty() {
        log::debug!("stderr: {}", stderr.trim());
    }

    if !output.status.success() {
        return Err(StorageError::CommandFailed {
            cmd: full_cmd,
            error: stderr.to_string(),
        });
    }

    Ok(output)
}

/// Execute a command and return its stdout as a string
pub fn run_output(cmd: &str, args: &[&str]) -> Result<String, StorageError> {
    let output = run(cmd, args)?;
    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

/// Execute a command, allowing failure (returns Option)
///
/// Useful for probe commands where failure is expected in some cases.
pub fn run_allow_fail(cmd: &str, args: &[&str]) -> Option<Output> {
    let full_cmd = format!("{} {}", cmd, args.join(" "));
    log::debug!("executing (allow fail): {}", full_cmd);

    match Command::new(cmd).args(args).output() {
        Ok(output) => {
            let stdout = String::from_utf8_lossy(&output.stdout);
            let stderr = String::from_utf8_lossy(&output.stderr);

            if !stdout.is_empty() {
                log::debug!("stdout: {}", stdout.trim());
            }
            if !stderr.is_empty() {
                log::debug!("stderr: {}", stderr.trim());
            }

            Some(output)
        }
        Err(e) => {
            log::debug!("command failed to execute: {}", e);
            None
        }
    }
}

/// Check if a command exists and is executable
pub fn command_exists(cmd: &str) -> bool {
    Command::new("which")
        .arg(cmd)
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}