use std::{io, path::PathBuf};
use getset::{CopyGetters, Getters, Setters, WithSetters};
use tap::{Pipe, Tap};
use crate::{
bool_ext::BoolExt,
os_cmd::{
CommandRepr, CommandSpawner, MiniStr, cow_str_into_cow_osstr,
process::{err_failed_to_run, run_os_cmd},
repr::TinyCmds,
},
};
#[derive(Debug, Clone, WithSetters, Getters, Setters, CopyGetters)]
#[getset(set = "pub", set_with = "pub", get = "pub with_prefix")]
pub struct Runner<'a> {
pub command: CommandRepr<'a>,
pub stdin_data: Option<&'a [u8]>,
pub(crate) remove_comments: bool,
inspect_mode: RunnerInspection,
}
#[derive(Debug, Clone, Default, Copy)]
pub enum RunnerInspection {
#[default]
Stderr,
LogDebug,
None,
}
pub trait RunnableCommand<'a>: Sized
where
Runner<'a>: From<Self>,
{
fn run(self) -> io::Result<()> {
Runner::from(self).run_command()
}
fn into_spawner(
self,
envs: Option<Box<[(MiniStr, MiniStr)]>>,
working_dir: Option<PathBuf>,
) -> CommandSpawner<'a> {
CommandSpawner::from(self)
.with_envs(envs)
.with_working_dir(working_dir)
}
}
impl<'a> RunnableCommand<'a> for Runner<'a> {}
impl Runner<'_> {
pub fn run_command(self) -> io::Result<()> {
use RunnerInspection::{LogDebug, Stderr};
let Self { inspect_mode, .. } = self;
if self.get_stdin_data().is_some() {
return self
.pipe(CommandSpawner::from)
.tap(|x| match inspect_mode {
LogDebug => log::debug!("{x:#?}"),
Stderr => eprintln!("{x:#?}"),
_ => {}
})
.spawn()?
.wait()?
.success()
.then_ok_or_else(|| err_failed_to_run(None));
}
self
.into_tinyvec()
.tap(|v| match inspect_mode {
Stderr => eprintln!("{v:?}"),
LogDebug => log::debug!("{v:?}"),
_ => {}
})
.into_iter()
.map(cow_str_into_cow_osstr)
.pipe(run_os_cmd)
}
}
impl<'a> Runner<'a> {
pub fn into_tinyvec(self) -> TinyCmds<'a> {
let Self {
command,
remove_comments,
..
} = self;
command.into_tinyvec(remove_comments)
}
}
impl Default for Runner<'_> {
fn default() -> Self {
Self {
command: CommandRepr::default(),
remove_comments: true,
inspect_mode: RunnerInspection::default(),
stdin_data: None,
}
}
}
impl<'a, T> From<T> for Runner<'a>
where
T: Into<CommandRepr<'a>>,
{
fn from(value: T) -> Self {
Self {
command: value.into(),
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[ignore]
#[test]
#[cfg(feature = "print_ext")]
fn show_default_runner() {
Runner::default().pipe(|x| crate::dbg!(x));
}
}