use std::{ffi::OsStr, process::Command};
use typed_builder::TypedBuilder;
use crate::{wrap::HasCommand, CommandWrap};
#[cfg(feature = "check")]
use crate::{CommandExtCheck, CommandExtError};
#[derive(TypedBuilder, Debug)]
pub struct CommandPrint<'a> {
command: &'a mut Command,
#[builder(default, setter(into))]
args: bool,
#[builder(default, setter(into))]
envs: bool,
#[builder(default, setter(into))]
current_dir: bool,
#[builder(default, setter(into))]
status: bool,
#[builder(default, setter(into))]
stdout: bool,
#[builder(default, setter(into))]
stderr: bool,
}
impl<'a> CommandPrint<'a> {
fn print_before(&mut self) {
if self.args {
println!(
"args: {} {}",
self.command().get_program().to_string_lossy(),
self.command()
.get_args()
.collect::<Vec<_>>()
.join(OsStr::new(" "))
.to_string_lossy()
);
}
if self.envs {
self.command().get_envs().for_each(|(k, v)| {
println!(
"envs: {}={}",
k.to_string_lossy(),
v.unwrap_or_default().to_string_lossy()
);
});
}
if self.current_dir {
println!(
"current_dir: {}",
self.command()
.get_current_dir()
.map(|d| d.to_string_lossy())
.unwrap_or_default()
);
}
}
}
impl<'a> HasCommand for CommandPrint<'a> {
fn command(&self) -> &Command {
self.command
}
fn command_mut(&mut self) -> &mut Command {
self.command
}
}
impl<'a> CommandWrap for CommandPrint<'a> {
fn on_spawn(&mut self) {
self.print_before();
}
fn on_output(&mut self) {
self.print_before();
}
fn on_status(&mut self) {
self.print_before();
}
fn after_output(&mut self, output: &std::io::Result<std::process::Output>) {
if let Ok(output) = output {
if self.status {
println!("status: {}", output.status);
}
if self.stdout {
let out = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !out.is_empty() {
println!("stdout: {out}",);
}
}
if self.stderr {
let err = String::from_utf8_lossy(&output.stderr).trim().to_string();
if !err.is_empty() {
println!("stderr: {err}",);
}
}
}
}
fn after_status(&mut self, status: &std::io::Result<std::process::ExitStatus>) {
if let Ok(status) = status {
if self.status {
println!("status: {}", status);
}
}
}
}
impl<'a> From<&'a mut Command> for CommandPrint<'a> {
fn from(value: &'a mut Command) -> Self {
Self::builder().command(value).build()
}
}
pub trait CommandExtPrint {
fn print_args(&mut self) -> CommandPrint;
fn print_envs(&mut self) -> CommandPrint;
fn print_current_dir(&mut self) -> CommandPrint;
fn print_status(&mut self) -> CommandPrint;
fn print_stdout(&mut self) -> CommandPrint;
fn print_stderr(&mut self) -> CommandPrint;
}
impl CommandExtPrint for Command {
fn print_args(&mut self) -> CommandPrint
{
CommandPrint::builder().command(self).args(true).build()
}
fn print_envs(&mut self) -> CommandPrint
{
CommandPrint::builder().command(self).envs(true).build()
}
fn print_current_dir(&mut self) -> CommandPrint
{
CommandPrint::builder()
.command(self)
.current_dir(true)
.build()
}
fn print_status(&mut self) -> CommandPrint
{
CommandPrint::builder().command(self).status(true).build()
}
fn print_stdout(&mut self) -> CommandPrint
{
CommandPrint::builder().command(self).stdout(true).build()
}
fn print_stderr(&mut self) -> CommandPrint
{
CommandPrint::builder().command(self).stderr(true).build()
}
}
impl<'a> CommandPrint<'a> {
pub fn print_args(&'a mut self) -> &'a mut CommandPrint
{
self.args = true;
self
}
pub fn print_envs(&'a mut self) -> &'a mut CommandPrint
{
self.envs = true;
self
}
pub fn print_current_dir(&'a mut self) -> &'a mut CommandPrint
{
self.current_dir = true;
self
}
pub fn print_status(&'a mut self) -> &'a mut CommandPrint
{
self.status = true;
self
}
pub fn print_stdout(&'a mut self) -> &'a mut CommandPrint
{
self.stdout = true;
self
}
pub fn print_stderr(&'a mut self) -> &'a mut CommandPrint
{
self.stderr = true;
self
}
}
#[cfg(feature = "check")]
impl<'a> CommandExtCheck for CommandPrint<'a> {
type Error = CommandExtError;
fn check(&mut self) -> Result<std::process::Output, Self::Error> {
self.output().map_err(CommandExtError::from).and_then(|r| {
r.status
.success()
.then_some(r.clone())
.ok_or_else(|| CommandExtError::Check {
status: r.status,
stdout: String::from_utf8_lossy(&r.stdout).to_string(),
stderr: String::from_utf8_lossy(&r.stderr).to_string(),
})
})
}
}
#[cfg(test)]
mod test {
use std::process::Command;
use test_log::test;
use crate::{CommandExtPrint, CommandWrap};
#[test]
#[cfg_attr(miri, ignore)]
fn test_args() -> anyhow::Result<()> {
Command::new("echo")
.arg("x")
.print_args()
.output()?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_envs() -> anyhow::Result<()> {
Command::new("echo")
.env("x", "y")
.print_envs()
.output()?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_current_dir() -> anyhow::Result<()> {
Command::new("echo")
.current_dir(env!("CARGO_MANIFEST_DIR"))
.print_current_dir()
.output()?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_status() -> anyhow::Result<()> {
Command::new("echo")
.arg("x")
.print_status()
.output()?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_stdout() -> anyhow::Result<()> {
Command::new("echo")
.arg("x")
.print_stdout()
.output()?;
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_stderr() -> anyhow::Result<()> {
Command::new("bash")
.args(["-c", "echo y 1>&2"])
.print_stderr()
.output()?;
Ok(())
}
}