use camino::{Utf8Path, Utf8PathBuf};
use color_eyre::eyre::{Context, Report};
use duct::{cmd, IntoExecutablePath};
use std::ffi::OsStr;
use std::fmt::Debug;
use std::io::{stdout, Read, Write};
use std::process::ExitStatus;
use std::str;
use std::{collections::HashMap, io::BufReader};
use tracing::{span, Level};
use super::errors::{ProcessError, ProcessIOError};
pub struct EntrypointOut {
pub out: String,
pub exit: ExitStatus,
}
pub(crate) fn process_out(bytes: Vec<u8>, info: String) -> Result<String, ProcessError> {
Ok(String::from_utf8(bytes)
.map_err(|_e| ProcessError::Decode(info))?
.trim_end()
.to_owned())
}
pub(crate) fn process_complete_output<P, E, S>(
working_dir: P,
program: E,
args: Vec<S>,
) -> Result<EntrypointOut, ProcessError>
where
P: Into<Utf8PathBuf> + Debug + Clone,
E: IntoExecutablePath + Debug + Clone,
S: AsRef<OsStr> + Debug,
{
let output = cmd(program.clone(), &args)
.dir(working_dir.clone().into())
.stderr_to_stdout()
.stdout_capture()
.unchecked()
.run()
.map_err(|e| ProcessIOError {
msg: format!(
"Process {:?} with args {:?} failed to run in {:?}",
program, args, working_dir
),
source: e,
})?;
let out = process_out(output.stdout, "stdout".to_owned())?;
Ok(EntrypointOut {
out,
exit: output.status,
})
}
pub(crate) fn run_entrypoint(
working_dir: &Utf8Path,
entrypoint: &Utf8Path,
envs: HashMap<String, String>,
input_bytes: Option<Vec<u8>>,
) -> Result<EntrypointOut, Report> {
println!(
"Running {:?} in working dir {:?}!",
&entrypoint, &working_dir
);
let mut combined_envs: HashMap<_, _> = std::env::vars().collect();
combined_envs.extend(envs);
let cmd_expr = cmd(entrypoint.as_std_path(), Vec::<String>::new())
.dir(working_dir)
.full_env(&combined_envs)
.stderr_to_stdout()
.unchecked();
let cmd_expr = if let Some(input_bytes) = input_bytes {
cmd_expr.stdin_bytes(input_bytes)
} else {
cmd_expr
};
let reader = cmd_expr.reader()?;
let entry_span = span!(Level::DEBUG, "entrypoint", path = entrypoint.as_str());
let _enter = entry_span.enter();
let mut out: String = String::with_capacity(128);
let mut reader = BufReader::new(reader);
let mut buffer_out = [0; 32];
loop {
let bytes_read_out = reader
.read(&mut buffer_out)
.wrap_err("Error reading stdout bytes!")?;
if bytes_read_out > 0 {
let string_buf = str::from_utf8(&buffer_out[..bytes_read_out])
.wrap_err("Error converting stdout bytes to UTF-8!")?;
print!("{}", string_buf);
let _ = stdout().flush();
out.push_str(string_buf);
} else {
break;
}
}
let inner_reader = reader.into_inner();
let maybe_output = inner_reader
.try_wait()
.wrap_err("Error trying to get reader exit status!")?;
let exit = maybe_output
.map(|out| out.status)
.unwrap_or(ExitStatus::default());
Ok(EntrypointOut { out, exit })
}