use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::{Command, ExitStatus};
use crate::error::{Error, Result};
pub fn subcommand_binary(app_name: &str, subcommand: &str) -> String {
format!("{app_name}-{subcommand}")
}
pub fn resolve(app_name: &str, subcommand: &str) -> Option<PathBuf> {
let binary = subcommand_binary(app_name, subcommand);
which::which(&binary).ok()
}
#[tracing::instrument(skip(args), fields(app = %app_name, subcommand = %subcommand))]
pub fn run<I, S>(app_name: &str, subcommand: &str, args: I) -> Result<Option<ExitStatus>>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let Some(binary_path) = resolve(app_name, subcommand) else {
return Ok(None);
};
tracing::debug!(binary = %binary_path.display(), "dispatching to external command");
let status = Command::new(&binary_path)
.args(args)
.status()
.map_err(|e| {
Error::Dispatch(std::io::Error::new(
e.kind(),
format!("failed to execute {}: {e}", binary_path.display()),
))
})?;
tracing::debug!(exit_code = ?status.code(), "external command finished");
Ok(Some(status))
}