use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::mpsc;
use std::thread;
mod chunk;
use chunk::pump;
pub use chunk::{Chunk, CliError, ExitInfo, work_dir_fault};
pub(crate) mod resolve;
pub use resolve::Binary;
pub(crate) mod self_exe;
pub(crate) use self_exe::self_exe;
mod stream;
pub use stream::{Stream, StreamPoll};
mod streamed;
pub use streamed::{
Streamed, StreamedLine, StreamedOutcome, StreamedPoll, stderr_text, stdout_text,
};
mod exec;
mod piped;
mod detach;
mod wrap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cli {
program: PathBuf,
prefix: Vec<String>,
env: Vec<(String, String)>,
wrapper: Vec<String>,
}
impl Cli {
#[must_use]
pub fn with_env(mut self, env: Vec<(String, String)>) -> Self {
self.env = env;
self
}
pub(crate) fn and_env(&self, extra: Vec<(String, String)>) -> Self {
let mut cli = self.clone();
cli.env.extend(extra);
cli
}
pub(crate) fn binary(&self) -> &Path {
self.prefix
.first()
.map_or(self.program.as_path(), |ns| Path::new(ns))
}
pub(crate) fn exec_words(&self) -> Vec<String> {
let mut words = vec![self.program.to_string_lossy().into_owned()];
words.extend(self.prefix.iter().cloned());
words
}
#[cfg(test)]
pub(crate) fn program(&self) -> &Path {
&self.program
}
#[cfg(test)]
pub(crate) fn prefix(&self) -> &[String] {
&self.prefix
}
pub fn run(&self, args: &[&str]) -> Result<Stream, CliError> {
self.run_streaming(None, &[], args, None)
}
pub fn run_in(&self, dir: &Path, args: &[&str]) -> Result<Stream, CliError> {
self.run_streaming(Some(dir), &[], args, None)
}
pub fn run_env(&self, env: &[(&str, &str)], args: &[&str]) -> Result<Stream, CliError> {
self.run_streaming(None, env, args, None)
}
fn run_streaming(
&self,
cwd: Option<&Path>,
env: &[(&str, &str)],
args: &[&str],
stdin: Option<&[u8]>,
) -> Result<Stream, CliError> {
let mut cmd = self.spawn_base();
cmd.args(args)
.envs(self.standing_env())
.envs(env.iter().copied())
.stdin(match stdin {
Some(_) => Stdio::piped(),
None => Stdio::null(),
})
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if let Some(dir) = cwd {
cmd.current_dir(dir);
}
let mut child = crate::git_env::spawn(&mut cmd)
.map_err(|e| CliError::spawn(self.exec_target(), cwd, e))?;
if let (Some(bytes), Some(mut pipe)) = (stdin, child.stdin.take()) {
let _ = std::io::Write::write_all(&mut pipe, bytes);
}
let stdout = child.stdout.take().ok_or(CliError::Stdio("stdout"))?;
let stderr = child.stderr.take().ok_or(CliError::Stdio("stderr"))?;
let (tx, rx) = mpsc::channel();
let tx_err = tx.clone();
thread::spawn(move || pump(stdout, tx, Chunk::Stdout));
thread::spawn(move || pump(stderr, tx_err, Chunk::Stderr));
Ok(Stream::new(child, rx))
}
fn standing_env(&self) -> impl Iterator<Item = (&str, &str)> {
self.env.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
}
pub(crate) mod sys;
#[cfg(test)]
mod tests;