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
use std::{ffi::OsString, fmt};
use serde::{Deserialize, Serialize};
use tokio::process::Command;
/// Shell command to execute.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct ShCmd {
/// Command to run.
program: OsString,
/// Arguments to pass to the command.
args: Vec<OsString>,
}
impl ShCmd {
/// Constructs a new `ShCmd` for launching the program at
/// path `program`, with the following default configuration:
///
/// * No arguments to the program
/// * Inherit the current process's environment
/// * Inherit the current process's working directory
/// * Inherit stdin/stdout/stderr for [`spawn`] or [`status`], but create
/// pipes for [`output`]
///
/// [`spawn`]: Self::spawn
/// [`status`]: Self::status
/// [`output`]: Self::output
///
/// Builder methods are provided to change these defaults and
/// otherwise configure the process.
///
/// If `program` is not an absolute path, the `PATH` will be searched in
/// an OS-defined way.
///
/// The search path to be used may be controlled by setting the
/// `PATH` environment variable on the ShCmd,
/// but this has some implementation limitations on Windows
/// (see issue #37519).
///
/// # Examples
///
/// Basic usage:
///
/// ```rust
/// use peace_item_spec_sh_cmd::ShCmd;
///
/// let sh_cmd = ShCmd::new("sh");
/// ```
pub fn new<S: Into<OsString>>(program: S) -> Self {
Self {
program: program.into(),
args: Vec::new(),
}
}
/// Adds an argument to pass to the program.
///
/// Only one argument can be passed per use. So instead of:
///
/// ```rust,no_run
/// # peace_item_spec_sh_cmd::ShCmd::new("sh")
/// .arg("-C /path/to/repo")
/// # ;
/// ```
///
/// usage would be:
///
/// ```rust,no_run
/// # peace_item_spec_sh_cmd::ShCmd::new("sh")
/// .arg("-C")
/// .arg("/path/to/repo")
/// # ;
/// ```
///
/// To pass multiple arguments see [`args`].
///
/// [`args`]: ShCmd::args
///
/// Note that the argument is not passed through a shell, but given
/// literally to the program. This means that shell syntax like quotes,
/// escaped characters, word splitting, glob patterns, substitution, etc.
/// have no effect.
///
/// # Examples
///
/// Basic usage:
///
/// ```rust,no_run
/// use peace_item_spec_sh_cmd::ShCmd;
///
/// let sh_cmd = ShCmd::new("ls").arg("-l").arg("-a");
/// ```
pub fn arg<S: Into<OsString>>(mut self, arg: S) -> Self {
self.args.push(arg.into());
self
}
/// Adds multiple arguments to pass to the program.
///
/// To pass a single argument see [`arg`].
///
/// [`arg`]: ShCmd::arg
///
/// Note that the arguments are not passed through a shell, but given
/// literally to the program. This means that shell syntax like quotes,
/// escaped characters, word splitting, glob patterns, substitution, etc.
/// have no effect.
///
/// # Examples
///
/// Basic usage:
///
/// ```rust,no_run
/// use peace_item_spec_sh_cmd::ShCmd;
///
/// let sh_cmd = ShCmd::new("ls").args(["-l", "-a"]);
/// ```
pub fn args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
args.into_iter().for_each(|arg| {
self.args.push(arg.into());
});
self
}
}
impl From<&ShCmd> for Command {
fn from(sh_cmd: &ShCmd) -> Command {
let mut command = Command::new(&sh_cmd.program);
command.args(&sh_cmd.args);
command
}
}
impl fmt::Display for ShCmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.program.to_string_lossy().fmt(f)?;
self.args
.iter()
.map(|arg| arg.to_string_lossy())
.try_for_each(|arg| write!(f, " {arg}"))?;
Ok(())
}
}