use std::{ffi::OsStr, process::Command as StdCommand};
use tokio::process::Command as TokioCommand;
#[derive(Debug)]
pub enum Command {
Std(StdCommand),
Tokio(TokioCommand),
}
impl Command {
pub fn std<S: AsRef<OsStr>>(program: S) -> Self {
StdCommand::new(program).into()
}
pub fn tokio_default<S: AsRef<OsStr>>(program: S) -> Self {
TokioCommand::new(program).into()
}
pub fn tokio_config<S: AsRef<OsStr>>(program: S, kill_on_drop: bool) -> Self {
let mut cmd = TokioCommand::new(program);
if kill_on_drop {
cmd.kill_on_drop(true);
}
cmd.into()
}
pub fn wrapping_std(&self) -> bool {
matches!(self, Self::Std(_))
}
pub fn wrapping_tokio(&self) -> bool {
matches!(self, Self::Tokio(_))
}
pub fn as_std(&self) -> &StdCommand {
match self {
Self::Std(v) => v,
Self::Tokio(v) => v.as_std(),
}
}
pub fn as_std_mut(&mut self) -> &mut StdCommand {
match self {
Self::Std(v) => v,
Self::Tokio(v) => v.as_std_mut(),
}
}
pub fn as_tokio(&self) -> Option<&TokioCommand> {
match self {
Self::Std(_) => None,
Self::Tokio(v) => Some(v),
}
}
pub fn as_tokio_mut(&mut self) -> Option<&mut TokioCommand> {
match self {
Self::Std(_) => None,
Self::Tokio(v) => Some(v),
}
}
pub fn into_std(self) -> StdCommand {
self.into()
}
pub fn into_tokio(self) -> TokioCommand {
self.into()
}
pub fn convert_to_std(self) -> Self {
self.into_std().into()
}
pub fn convert_to_tokio(self) -> Self {
self.into_tokio().into()
}
}
impl From<StdCommand> for Command {
fn from(value: StdCommand) -> Self {
Self::Std(value)
}
}
impl From<Command> for StdCommand {
fn from(value: Command) -> Self {
match value {
Command::Std(v) => v,
Command::Tokio(v) => v.into_std(),
}
}
}
impl From<TokioCommand> for Command {
fn from(value: TokioCommand) -> Self {
Self::Tokio(value)
}
}
impl From<Command> for TokioCommand {
fn from(value: Command) -> Self {
match value {
Command::Std(v) => v.into(),
Command::Tokio(v) => v,
}
}
}
impl Clone for Command {
fn clone(&self) -> Self {
let kill_on_drop = self.as_tokio().map(TokioCommand::get_kill_on_drop);
let cmd = self.as_std();
let mut cloned = StdCommand::new(cmd.get_program());
cloned.args(cmd.get_args());
for (k, v) in cmd.get_envs() {
match v {
Some(v) => cloned.env(k, v),
None => cloned.env_remove(k),
};
}
if let Some(current_dir) = cmd.get_current_dir() {
cloned.current_dir(current_dir);
}
match kill_on_drop {
None => cloned.into(),
Some(kill_on_drop) => {
let mut cmd: TokioCommand = cloned.into();
if kill_on_drop {
cmd.kill_on_drop(true);
}
cmd.into()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrapping() {
let cmd = Command::Std(StdCommand::new("echo"));
assert!(cmd.wrapping_std());
assert!(!cmd.wrapping_tokio());
let cmd = Command::Tokio(TokioCommand::new("echo"));
assert!(!cmd.wrapping_std());
assert!(cmd.wrapping_tokio());
}
#[test]
fn construct() {
let cmd = Command::std("echo");
assert!(cmd.wrapping_std());
let cmd = Command::tokio_default("echo");
assert!(cmd.wrapping_tokio());
assert!(!cmd.as_tokio().unwrap().get_kill_on_drop());
let cmd = Command::tokio_config("echo", false);
assert!(cmd.wrapping_tokio());
assert!(!cmd.as_tokio().unwrap().get_kill_on_drop());
let cmd = Command::tokio_config("echo", true);
assert!(cmd.wrapping_tokio());
assert!(cmd.as_tokio().unwrap().get_kill_on_drop());
}
#[test]
fn as_std() {
let mut cmd = Command::std("echo");
assert_eq!(cmd.as_std().get_program(), "echo");
assert_eq!(cmd.as_std_mut().get_program(), "echo");
let mut cmd = Command::tokio_default("echo");
assert_eq!(cmd.as_std().get_program(), "echo");
assert_eq!(cmd.as_std_mut().get_program(), "echo");
}
#[test]
fn as_tokio() {
let mut cmd = Command::std("echo");
assert!(cmd.as_tokio().is_none());
assert!(cmd.as_tokio_mut().is_none());
let mut cmd = Command::tokio_default("echo");
assert!(cmd.as_tokio().is_some());
assert!(cmd.as_tokio_mut().is_some());
}
#[test]
fn into_std() {
let cmd = Command::std("echo").into_std();
assert_eq!(cmd.get_program(), "echo");
let cmd: TokioCommand = cmd.into();
assert!(!cmd.get_kill_on_drop());
let cmd = Command::tokio_default("echo").into_std();
assert_eq!(cmd.get_program(), "echo");
let cmd: TokioCommand = cmd.into();
assert!(!cmd.get_kill_on_drop());
let cmd = Command::tokio_config("echo", true).into_std();
assert_eq!(cmd.get_program(), "echo");
let cmd: TokioCommand = cmd.into();
assert!(!cmd.get_kill_on_drop());
}
#[test]
fn into_tokio() {
let cmd = Command::std("echo").into_tokio();
assert_eq!(cmd.as_std().get_program(), "echo");
assert!(!cmd.get_kill_on_drop());
let cmd = Command::tokio_default("echo").into_tokio();
assert_eq!(cmd.as_std().get_program(), "echo");
assert!(!cmd.get_kill_on_drop());
let cmd = Command::tokio_config("echo", true).into_tokio();
assert_eq!(cmd.as_std().get_program(), "echo");
assert!(cmd.get_kill_on_drop());
}
#[test]
fn convert() {
let cmd = Command::std("echo");
assert!(cmd.wrapping_std());
let cmd = cmd.convert_to_std();
assert!(cmd.wrapping_std());
let cmd = Command::tokio_default("echo");
assert!(cmd.wrapping_tokio());
let cmd = cmd.convert_to_std();
assert!(cmd.wrapping_std());
let cmd = Command::std("echo");
assert!(cmd.wrapping_std());
let cmd = cmd.convert_to_tokio();
assert!(cmd.wrapping_tokio());
let cmd = Command::tokio_default("echo");
assert!(cmd.wrapping_tokio());
let cmd = cmd.convert_to_tokio();
assert!(cmd.wrapping_tokio());
}
fn std_command() -> StdCommand {
let mut cmd = StdCommand::new("echo");
cmd.args(["a1", "a2"]);
cmd.envs([("k1", "v1"), ("k2", "v2")]);
cmd.current_dir("/tmp");
cmd
}
fn eq_std(a: &StdCommand, b: &StdCommand) -> bool {
a.get_program() == b.get_program()
&& a.get_args().collect::<Vec<_>>() == b.get_args().collect::<Vec<_>>()
&& a.get_envs().collect::<Vec<_>>() == b.get_envs().collect::<Vec<_>>()
&& a.get_current_dir() == b.get_current_dir()
}
fn eq_tokio(a: &TokioCommand, b: &TokioCommand) -> bool {
eq_std(&a.as_std(), &b.as_std()) && a.get_kill_on_drop() == b.get_kill_on_drop()
}
fn eq_command(a: &Command, b: &Command) -> bool {
match (a, b) {
(Command::Std(a), Command::Std(b)) => eq_std(a, b),
(Command::Tokio(a), Command::Tokio(b)) => eq_tokio(a, b),
_ => false,
}
}
#[test]
fn clone() {
let cmd: Command = std_command().into();
let cloned = cmd.clone();
assert!(eq_command(&cmd, &cloned));
let cmd: TokioCommand = std_command().into();
let cmd: Command = cmd.into();
let cloned = cmd.clone();
assert!(eq_command(&cmd, &cloned));
let mut cmd: TokioCommand = std_command().into();
cmd.kill_on_drop(false);
let cmd: Command = cmd.into();
let cloned = cmd.clone();
assert!(eq_command(&cmd, &cloned));
let mut cmd: TokioCommand = std_command().into();
cmd.kill_on_drop(true);
let cmd: Command = cmd.into();
let cloned = cmd.clone();
assert!(eq_command(&cmd, &cloned));
}
}