use crate::CommandBuilder;
pub use bstr;
use bstr::BString;
use bstr::ByteSlice;
use cloud_terrastodon_relative_location::RelativeLocation;
use eyre::Error;
use eyre::Result;
use serde::Deserialize;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::future::Future;
#[cfg(not(windows))]
use std::os::unix::process::ExitStatusExt;
#[cfg(windows)]
use std::os::windows::process::ExitStatusExt;
use std::panic::Location;
use std::process::ExitStatus;
use std::process::Output;
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct CommandOutput {
pub stdout: BString,
pub stderr: BString,
pub status: i32,
}
impl CommandOutput {
pub fn success(&self) -> bool {
#[cfg(windows)]
return ExitStatus::from_raw(self.status as u32).success();
#[cfg(not(windows))]
return ExitStatus::from_raw(self.status).success();
}
#[track_caller]
pub fn try_interpret<'a, T: DeserializeOwned + 'a>(
&'a self,
command: &'a CommandBuilder,
) -> impl Future<Output = eyre::Result<T>> + 'a {
self.try_interpret_from(command, Location::caller())
}
async fn try_interpret_from<T: DeserializeOwned>(
&self,
command: &CommandBuilder,
caller: &'static Location<'static>,
) -> eyre::Result<T> {
match serde_json::from_slice(self.stdout.to_str_lossy().as_bytes()) {
Ok(results) => Ok(results),
Err(e) => {
let dir = command.write_failure(self).await?;
Err(eyre::Error::new(e)
.wrap_err(format!("Called from {}", RelativeLocation::from(caller)))
.wrap_err(format!(
"deserializing `{}` failed, dumped to {:?}",
command.summarize().await,
dir
)))
}
}
}
pub fn shorten(&mut self) {
fn keep_first_and_last_500_lines_with_warning(output: BString) -> BString {
let lines: Vec<&[u8]> = output.lines().collect();
let total = lines.len();
if total <= 1000 {
output
} else {
let mut trimmed = Vec::new();
trimmed.extend_from_slice(&lines[..500]);
let warning = b"...[output truncated: middle lines omitted]...";
trimmed.push(warning);
trimmed.extend_from_slice(&lines[total - 500..]);
BString::from(trimmed.join(&b'\n'))
}
}
let stdout = std::mem::take(&mut self.stdout);
self.stdout = keep_first_and_last_500_lines_with_warning(stdout);
let stderr = std::mem::take(&mut self.stderr);
self.stderr = keep_first_and_last_500_lines_with_warning(stderr);
}
}
impl std::error::Error for CommandOutput {}
impl std::fmt::Display for CommandOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"status={}\nstdout={}\nstderr={}",
self.status, self.stdout, self.stderr
))
}
}
impl TryFrom<Output> for CommandOutput {
type Error = Error;
fn try_from(value: Output) -> Result<Self> {
Ok(CommandOutput {
stdout: BString::from(value.stdout),
stderr: BString::from(value.stderr),
status: match value.status.code().unwrap_or(1) {
x if x < 0 => 1,
x => x,
},
})
}
}