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
//! Launch commands very similarly to `Command`, but with `git` specific capabilities and adjustments.
#![deny(rust_2018_idioms, missing_docs)]
#![forbid(unsafe_code)]
use std::ffi::OsString;
/// A structure to keep settings to use when invoking a command via [`spawn()`][Prepare::spawn()], after creating it with [`prepare()`].
pub struct Prepare {
/// The command to invoke (either with or without shell depending on `use_shell`.
pub command: OsString,
/// The way standard input is configured.
pub stdin: std::process::Stdio,
/// The way standard output is configured.
pub stdout: std::process::Stdio,
/// The way standard error is configured.
pub stderr: std::process::Stdio,
/// The arguments to pass to the spawned process.
pub args: Vec<OsString>,
/// environment variables to set in the spawned process.
pub env: Vec<(OsString, OsString)>,
/// If `true`, we will use `sh` to execute the `command`.
pub use_shell: bool,
}
mod prepare {
use std::{
ffi::OsString,
process::{Command, Stdio},
};
use bstr::ByteSlice;
use crate::Prepare;
/// Builder
impl Prepare {
/// If called, the command will not be executed directly, but with `sh`.
///
/// This also allows to pass shell scripts as command, or use commands that contain arguments which are subsequently
/// parsed by `sh`.
pub fn with_shell(mut self) -> Self {
self.use_shell = self.command.to_str().map_or(true, |cmd| {
cmd.as_bytes().find_byteset(b"|&;<>()$`\\\"' \t\n*?[#~=%").is_some()
});
self
}
/// Unconditionally turn off using the shell when spawning the command.
/// Note that not using the shell is the default so an effective use of this method
/// is some time after [`with_shell()`][Prepare::with_shell()] was called.
pub fn without_shell(mut self) -> Self {
self.use_shell = false;
self
}
/// Configure the process to use `stdio` for _stdin.
pub fn stdin(mut self, stdio: Stdio) -> Self {
self.stdin = stdio;
self
}
/// Configure the process to use `stdio` for _stdout_.
pub fn stdout(mut self, stdio: Stdio) -> Self {
self.stdout = stdio;
self
}
/// Configure the process to use `stdio` for _stderr.
pub fn stderr(mut self, stdio: Stdio) -> Self {
self.stderr = stdio;
self
}
/// Add `arg` to the list of arguments to call the command with.
pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}
/// Add `args` to the list of arguments to call the command with.
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<OsString>>) -> Self {
self.args
.append(&mut args.into_iter().map(Into::into).collect::<Vec<_>>());
self
}
/// Add `key` with `value` to the environment of the spawned command.
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.env.push((key.into(), value.into()));
self
}
}
/// Finalization
impl Prepare {
/// Spawn the command as configured.
pub fn spawn(self) -> std::io::Result<std::process::Child> {
Command::from(self).spawn()
}
}
impl From<Prepare> for Command {
fn from(mut prep: Prepare) -> Command {
let mut cmd = if prep.use_shell {
let mut cmd = Command::new(if cfg!(windows) { "sh" } else { "/bin/sh" });
cmd.arg("-c");
if !prep.args.is_empty() {
prep.command.push(" \"$@\"")
}
cmd.arg(prep.command);
cmd.arg("--");
cmd
} else {
Command::new(prep.command)
};
cmd.stdin(prep.stdin)
.stdout(prep.stdout)
.stderr(prep.stderr)
.envs(prep.env)
.args(prep.args);
cmd
}
}
}
/// Prepare `cmd` for [spawning][std::process::Command::spawn()] by configuring it with various builder methods.
///
/// Note that the default IO is configured for typical API usage, that is
///
/// - `stdin` is null to prevent blocking unexpectedly on consumption of stdin
/// - `stdout` is captured for consumption by the caller
/// - `stderr` is inherited to allow the command to provide context to the user
pub fn prepare(cmd: impl Into<OsString>) -> Prepare {
Prepare {
command: cmd.into(),
stdin: std::process::Stdio::null(),
stdout: std::process::Stdio::piped(),
stderr: std::process::Stdio::inherit(),
args: Vec::new(),
env: Vec::new(),
use_shell: false,
}
}