#![deny(missing_docs)]
pub use error::*;
use std::{
ffi::OsStr,
path::Path,
process::{Command, CommandArgs, CommandEnvs, ExitStatus, Stdio},
};
pub mod error;
pub struct Cmd {
pub inner: Command,
summary: String,
log: Option<LogStrategy>,
check_status: bool,
#[cfg(not(feature = "stdout_to_stderr_modern"))]
stdout_to_stderr_polyfill: bool,
}
impl Cmd {
pub fn new(command: impl AsRef<OsStr>, summary: impl Into<String>) -> Self {
let inner = Command::new(command);
Self {
summary: summary.into(),
inner,
log: Some(LogStrategy::Tracing(tracing::Level::INFO)),
check_status: true,
#[cfg(not(feature = "stdout_to_stderr_modern"))]
stdout_to_stderr_polyfill: false,
}
}
}
impl Cmd {
pub fn stdout_to_stderr(&mut self) -> &mut Self {
#[cfg(not(feature = "stdout_to_stderr_modern"))]
{
self.stdout_to_stderr_polyfill = true;
}
#[cfg(feature = "stdout_to_stderr_modern")]
{
self.inner.stdout(std::io::stderr());
}
self
}
pub fn log(&mut self, strategy: impl Into<Option<LogStrategy>>) -> &mut Self {
self.log = strategy.into();
self
}
pub fn check(&mut self, checked: bool) -> &mut Self {
self.check_status = checked;
self
}
}
impl Cmd {
pub fn run(&mut self) -> Result<()> {
self.status()?;
Ok(())
}
pub fn spawn(&mut self) -> Result<std::process::Child> {
self.log_command();
self.inner.spawn().map_err(|cause| AxoprocessError::Exec {
summary: self.summary.clone(),
cause,
})
}
pub fn output(&mut self) -> Result<std::process::Output> {
#[cfg(not(feature = "stdout_to_stderr_modern"))]
if self.stdout_to_stderr_polyfill {
self.inner.stdout(Stdio::piped());
}
self.log_command();
let res = self.inner.output().map_err(|cause| AxoprocessError::Exec {
summary: self.summary.clone(),
cause,
})?;
#[cfg(not(feature = "stdout_to_stderr_modern"))]
if self.stdout_to_stderr_polyfill {
use std::io::Write;
let mut stderr = std::io::stderr().lock();
let _ = stderr.write_all(&res.stdout);
let _ = stderr.flush();
}
self.maybe_check_status(res.status)?;
Ok(res)
}
pub fn status(&mut self) -> Result<ExitStatus> {
#[cfg(not(feature = "stdout_to_stderr_modern"))]
if self.stdout_to_stderr_polyfill {
self.inner.stderr(std::process::Stdio::inherit());
let out = self.output()?;
return Ok(out.status);
}
self.status_inner()
}
fn status_inner(&mut self) -> Result<ExitStatus> {
self.log_command();
let res = self.inner.status().map_err(|cause| AxoprocessError::Exec {
summary: self.summary.clone(),
cause,
})?;
self.maybe_check_status(res)?;
Ok(res)
}
}
impl Cmd {
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
self.inner.arg(arg);
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.inner.env(key, val);
self
}
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.inner.envs(vars);
self
}
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
self.inner.env_remove(key);
self
}
pub fn env_clear(&mut self) -> &mut Self {
self.inner.env_clear();
self
}
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
self.inner.current_dir(dir);
self
}
pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
self.inner.stdin(cfg);
self
}
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
self.inner.stdout(cfg);
self
}
pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
self.inner.stderr(cfg);
self
}
pub fn get_program(&self) -> &OsStr {
self.inner.get_program()
}
pub fn get_args(&self) -> CommandArgs<'_> {
self.inner.get_args()
}
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.inner.get_envs()
}
pub fn get_current_dir(&self) -> Option<&Path> {
self.inner.get_current_dir()
}
}
macro_rules! log {
($lvl:expr, $fmt:expr, $($arg:tt)*) => {
match $lvl {
tracing::Level::TRACE => {
tracing::trace!($fmt, $($arg)*);
}
tracing::Level::DEBUG => {
tracing::debug!($fmt, $($arg)*);
}
tracing::Level::INFO => {
tracing::info!($fmt, $($arg)*);
}
tracing::Level::WARN => {
tracing::warn!($fmt, $($arg)*);
}
tracing::Level::ERROR => {
tracing::error!($fmt, $($arg)*);
}
}
}
}
impl Cmd {
pub fn check_status(&self, status: ExitStatus) -> Result<()> {
if status.success() {
Ok(())
} else {
Err(AxoprocessError::Status {
summary: self.summary.clone(),
status,
})
}
}
pub fn maybe_check_status(&self, status: ExitStatus) -> Result<()> {
if self.check_status {
self.check_status(status)?;
}
Ok(())
}
pub fn log_command(&self) {
let Some(strategy) = self.log else {
return;
};
match strategy {
LogStrategy::Stdout => {
println!("exec {:?}", self.inner);
}
LogStrategy::Stderr => {
eprintln!("exec {:?}", self.inner);
}
LogStrategy::Tracing(level) => {
log!(level, "exec {:?}", self.inner);
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LogStrategy {
Stdout,
Stderr,
Tracing(tracing::Level),
}
impl From<tracing::Level> for LogStrategy {
fn from(level: tracing::Level) -> Self {
Self::Tracing(level)
}
}
impl From<std::io::Stdout> for LogStrategy {
fn from(_stdout: std::io::Stdout) -> Self {
Self::Stdout
}
}
impl From<std::io::Stderr> for LogStrategy {
fn from(_stderr: std::io::Stderr) -> Self {
Self::Stderr
}
}