use std::path::PathBuf;
use super::redaction::redact_text;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommandResultSummary {
pub exit_status: Option<ExitStatusSummary>,
pub spawn_error: Option<String>,
pub stdout: StreamSummary,
pub stderr: StreamSummary,
}
impl Default for CommandResultSummary {
fn default() -> Self {
Self {
exit_status: None,
spawn_error: None,
stdout: StreamSummary::empty(),
stderr: StreamSummary::empty(),
}
}
}
impl CommandResultSummary {
pub(super) fn apply_retention(&mut self, retention: &OutputRetention) {
self.stdout.apply_limit(retention.stdout_limit());
self.stderr.apply_limit(retention.stderr_limit());
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExitStatusSummary {
pub code: Option<i32>,
pub signal: Option<i32>,
pub success: bool,
}
impl ExitStatusSummary {
#[must_use]
pub const fn code(code: i32) -> Self {
Self {
code: Some(code),
signal: None,
success: code == 0,
}
}
#[must_use]
pub const fn signal(signal: i32) -> Self {
Self {
code: None,
signal: Some(signal),
success: false,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OutputRetention {
SummaryOnly {
stdout_limit: usize,
stderr_limit: usize,
},
Artifact {
stdout_limit: usize,
stderr_limit: usize,
full_output_path: PathBuf,
},
}
impl OutputRetention {
#[must_use]
pub const fn summary_only(stdout_limit: usize, stderr_limit: usize) -> Self {
Self::SummaryOnly {
stdout_limit,
stderr_limit,
}
}
const fn stdout_limit(&self) -> usize {
match self {
Self::SummaryOnly { stdout_limit, .. } | Self::Artifact { stdout_limit, .. } => {
*stdout_limit
}
}
}
const fn stderr_limit(&self) -> usize {
match self {
Self::SummaryOnly { stderr_limit, .. } | Self::Artifact { stderr_limit, .. } => {
*stderr_limit
}
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct StreamSummary {
pub byte_len: usize,
pub line_count: usize,
pub snippet: String,
pub truncated: bool,
pub redacted: bool,
}
impl StreamSummary {
#[must_use]
pub const fn empty() -> Self {
Self {
byte_len: 0,
line_count: 0,
snippet: String::new(),
truncated: false,
redacted: false,
}
}
#[must_use]
pub fn from_bytes(bytes: &[u8], limit: usize) -> Self {
Self::from_text(&String::from_utf8_lossy(bytes), limit).with_byte_len(bytes.len())
}
#[must_use]
pub fn from_text(text: &str, limit: usize) -> Self {
let byte_len = text.len();
let line_count = text.lines().count();
let (redacted_text, redacted) = redact_text(text);
let (snippet, truncated) = truncate_to_boundary(&redacted_text, limit);
Self {
byte_len,
line_count,
snippet,
truncated,
redacted,
}
}
const fn with_byte_len(mut self, byte_len: usize) -> Self {
self.byte_len = byte_len;
self
}
fn apply_limit(&mut self, limit: usize) {
let (snippet, truncated) = truncate_to_boundary(&self.snippet, limit);
self.truncated |= truncated;
self.snippet = snippet;
}
}
fn truncate_to_boundary(text: &str, limit: usize) -> (String, bool) {
if text.len() <= limit {
return (text.to_owned(), false);
}
if limit == 0 {
return (String::new(), true);
}
let mut end = limit;
while !text.is_char_boundary(end) {
end -= 1;
}
(text[..end].to_owned(), true)
}