1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
//! Command construction, configuration, and tracking.
use std::fmt;
use tokio::process::Command as TokioCommand;
use tracing::trace;
use crate::error::RuntimeError;
#[doc(inline)]
pub use process::Process;
#[doc(inline)]
pub use supervisor::Supervisor;
mod process;
mod supervisor;
#[cfg(test)]
mod tests;
/// A command to execute.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Command {
/// A raw command which will be executed as-is.
Exec {
/// The program to run.
prog: String,
/// The arguments to pass.
args: Vec<String>,
},
/// A shelled command line.
Shell {
/// The shell to run.
shell: Shell,
/// Additional options or arguments to pass to the shell.
///
/// These will be inserted before the `-c` (or equivalent) option immediately preceding the
/// command line string.
args: Vec<String>,
/// The command line to pass to the shell.
command: String,
},
}
/// Shell to use to run shelled commands.
///
/// `Cmd` and `Powershell` are special-cased because they have different calling conventions. Also
/// `Cmd` is only available in Windows, while `Powershell` is also available on unices (provided the
/// end-user has it installed, of course).
///
/// There is no default implemented: as consumer of this library you are encouraged to set your own
/// default as makes sense in your application / for your platform.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Shell {
/// Use the given string as a unix shell invocation.
///
/// This is invoked with `-c` followed by the command.
Unix(String),
/// Use the Windows CMD.EXE shell.
///
/// This is `cmd.exe` invoked with `/C` followed by the command.
#[cfg(windows)]
Cmd,
/// Use Powershell, on Windows or elsewhere.
///
/// This is `powershell.exe` invoked with `-Command` followed by the command on Windows.
/// On unices, it is equivalent to `Unix("pwsh")`.
Powershell,
}
impl Command {
/// Obtain a [`tokio::process::Command`] from a [`Command`].
///
/// Behaves as described in the [`Command`] and [`Shell`] documentation.
///
/// # Errors
///
/// - Errors if the `command` of a `Command::Shell` is empty.
/// - Errors if the `shell` of a `Shell::Unix(shell)` is empty.
pub fn to_spawnable(&self) -> Result<TokioCommand, RuntimeError> {
trace!(cmd=?self, "constructing command");
match self {
Self::Exec { prog, args } => {
let mut c = TokioCommand::new(prog);
c.args(args);
Ok(c)
}
Self::Shell {
shell,
args,
command,
} => {
if command.is_empty() {
return Err(RuntimeError::CommandShellEmptyCommand);
}
let (shcmd, shcliopt) = match shell {
#[cfg(windows)]
Shell::Cmd => {
use std::os::windows::process::CommandExt as _;
use std::process::Command as StdCommand;
// TODO this is a workaround until TokioCommand has a raw_arg method. See tokio-rs/tokio#5810.
let mut std_command = StdCommand::new("cmd.exe");
std_command.args(args).arg("/C").raw_arg(command);
return Ok(TokioCommand::from(std_command));
}
#[cfg(windows)]
Shell::Powershell => ("powershell.exe", "-Command"),
#[cfg(not(windows))]
Shell::Powershell => ("pwsh", "-c"),
Shell::Unix(cmd) => {
if cmd.is_empty() {
return Err(RuntimeError::CommandShellEmptyShell);
}
(cmd.as_str(), "-c")
}
};
let mut c = TokioCommand::new(shcmd);
c.args(args);
c.arg(shcliopt).arg(command);
Ok(c)
}
}
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Exec { prog, args } => {
write!(f, "{prog}")?;
for arg in args {
write!(f, " {arg}")?;
}
Ok(())
}
Self::Shell { command, .. } => {
write!(f, "{command}")
}
}
}
}