use std::{
process::{self, Stdio},
time::Duration,
};
use crate::{process::TIMEOUT, Env, ExitResult, Location, Result, RunningProcess};
#[derive(Clone)]
pub struct Cmd<Loc> {
pub exe: String,
pub env: Env,
pub pwd: Loc,
pub msg: Option<String>,
}
pub enum Output {
Data(Vec<u8>),
Interrupted,
}
impl Output {
pub fn unwrap(self) -> Vec<u8> {
match self {
Self::Data(bytes) => bytes,
Self::Interrupted => process::exit(0),
}
}
pub fn unwrap_string(self) -> Result<String> {
let bytes = self.unwrap();
let string = String::from_utf8(bytes)?;
Ok(string)
}
}
impl<Loc> Cmd<Loc>
where
Loc: Location + Send + Sync,
{
pub fn exe(&self) -> &str {
&self.exe
}
pub fn env(&self) -> &Env {
&self.env
}
pub fn pwd(&self) -> &Loc {
&self.pwd
}
pub fn msg(&self) -> Option<&String> {
self.msg.as_ref()
}
pub async fn run(&self) -> Result<()> {
eprintln!("{}", crate::headline!(self));
self.spawn(Stdio::inherit(), Stdio::inherit()).await?;
Ok(())
}
pub async fn silent(&self) -> Result<()> {
self.spawn(Stdio::null(), Stdio::null()).await?;
Ok(())
}
pub async fn output(&self) -> Result<Output> {
let res = self.spawn(Stdio::piped(), Stdio::piped()).await?;
match res {
ExitResult::Output(output) => Ok(Output::Data(output.stdout)),
ExitResult::Interrupted | ExitResult::Killed { pid: _ } => Ok(Output::Interrupted),
}
}
async fn spawn(&self, stdout: Stdio, stderr: Stdio) -> Result<ExitResult> {
let cmd = self;
RunningProcess::spawn(cmd, stdout, stderr, Duration::from_secs(*TIMEOUT))
.await?
.wait()
.await
}
#[cfg(unix)]
pub(crate) const SHELL: &'static str = "/bin/sh";
#[cfg(windows)]
pub(crate) const SHELL: &'static str = "cmd";
#[cfg(unix)]
pub(crate) fn shelled(cmd: &str) -> Vec<&str> {
vec!["-c", cmd]
}
#[cfg(windows)]
pub(crate) fn shelled(cmd: &str) -> Vec<&str> {
vec!["/c", cmd]
}
}
#[macro_export]
macro_rules! cmd {
{
exe: $exe:literal,
env: $env:expr,
pwd: $pwd:expr,
msg: $msg:literal$(,)?
} => {
$crate::Cmd {
exe: $exe.to_string(),
env: $env,
pwd: $pwd,
msg: Some($msg.to_string()),
}
};
{
exe: $exe:literal,
env: $env:expr,
pwd: $pwd:expr,
msg: Some($msg:expr)$(,)?
} => {
$crate::Cmd {
exe: $exe.to_string(),
env: $env,
pwd: $pwd,
msg: Some($msg),
}
};
{
exe: $exe:literal,
env: $env:expr,
pwd: $pwd:expr,
msg: None$(,)?
} => {
$crate::Cmd {
exe: $exe.to_string(),
env: $env,
pwd: $pwd,
msg: None,
}
};
{
exe: $exe:literal,
env: $env:expr,
pwd: $pwd:expr,
msg: $msg:expr$(,)?
} => {
$crate::Cmd {
exe: $exe.to_string(),
env: $env,
pwd: $pwd,
msg: Some($msg),
}
};
{
exe: $exe:expr,
env: $env:expr,
pwd: $pwd:expr,
msg: $msg:literal$(,)?
} => {
$crate::Cmd {
exe: $exe,
env: $env,
pwd: $pwd,
msg: Some($msg.to_string()),
}
};
{
exe: $exe:expr,
env: $env:expr,
pwd: $pwd:expr,
msg: Some($msg:expr)$(,)?
} => {
$crate::Cmd {
exe: $exe,
env: $env,
pwd: $pwd,
msg: Some($msg),
}
};
{
exe: $exe:expr,
env: $env:expr,
pwd: $pwd:expr,
msg: None$(,)?
} => {
$crate::Cmd {
exe: $exe,
env: $env,
pwd: $pwd,
msg: None,
}
};
{
exe: $exe:expr,
env: $env:expr,
pwd: $pwd:expr,
msg: $msg:expr$(,)?
} => {
$crate::Cmd {
exe: $exe,
env: $env,
pwd: $pwd,
msg: Some($msg),
}
};
{
exe: $exe:literal,
env: $env:expr,
pwd: $pwd:expr$(,)?
} => {
$crate::Cmd {
exe: $exe.to_string(),
env: $env,
pwd: $pwd,
msg: None,
}
};
{
exe: $exe:expr,
env: $env:expr,
pwd: $pwd:expr$(,)?
} => {
$crate::Cmd {
exe: $exe,
env: $env,
pwd: $pwd,
msg: None,
}
};
}
#[cfg(test)]
mod tests {
use crate::{Cmd, Env, Location};
#[allow(dead_code)]
fn cmd_macro_exe_literal_msg_literal<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: "ls",
env: env,
pwd: loc,
msg: "!",
}
}
#[allow(dead_code)]
fn cmd_macro_exe_expr_msg_literal<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: format!("ls {}", "."),
env: env,
pwd: loc,
msg: "!",
}
}
#[allow(dead_code)]
fn cmd_macro_exe_expr_msg_expr<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: format!("ls {}", "."),
env: env,
pwd: loc,
msg: format!("!"),
}
}
#[allow(dead_code)]
fn cmd_macro_exe_literal_msg_expr<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: "ls",
env: env,
pwd: loc,
msg: format!("!"),
}
}
#[allow(dead_code)]
fn cmd_macro_exe_literal_msg_some_expr<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: "ls",
env: env,
pwd: loc,
msg: Some(format!("!")),
}
}
#[allow(dead_code)]
fn cmd_macro_exe_expr_msg_some_expr<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: format!("ls {}", "."),
env: env,
pwd: loc,
msg: Some(format!("!")),
}
}
#[allow(dead_code)]
fn cmd_macro_exe_literal_msg_none<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: "ls",
env: env,
pwd: loc,
msg: None,
}
}
#[allow(dead_code)]
fn cmd_macro_exe_expr_msg_none<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: format!("ls {}", "."),
env: env,
pwd: loc,
msg: None,
}
}
#[allow(dead_code)]
fn cmd_macro_exe_literal_no_msg<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: "ls",
env: env,
pwd: loc,
}
}
#[allow(dead_code)]
fn cmd_macro_exe_expr_no_msg<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! {
exe: format!("ls {}", "."),
env: env,
pwd: loc,
}
}
#[allow(dead_code)]
fn cmd_macro_no_trailing_comma<Loc: Location>(env: Env, loc: Loc) -> Cmd<Loc> {
cmd! { exe: "ls", env: env, pwd: loc }
}
}