Files
sausage/userland/box/src/excerpt.rs
T

109 lines
3.5 KiB
Rust

//! Presentation only. Never rewrite archives or change internal command output.
use crate::{Executed, Result};
use std::io::{Read, Seek, SeekFrom};
pub const DEFAULT_BYTES: u32 = 16 * 1024;
const MARKER: &str =
"\n... [inline output omitted; see stream archive path for full available output] ...\n";
fn head(text: &str, bytes: usize) -> &str {
let mut end = bytes.min(text.len());
while !text.is_char_boundary(end) {
end -= 1;
}
&text[..end]
}
fn tail(text: &str, bytes: usize) -> &str {
let mut start = text.len().saturating_sub(bytes);
while !text.is_char_boundary(start) {
start += 1;
}
&text[start..]
}
fn bounded(text: &str, budget: usize) -> (String, bool) {
if text.len() <= budget {
return (text.to_owned(), false);
}
let first = budget.div_ceil(2);
(
format!(
"{}{MARKER}{}",
head(text, first),
tail(text, budget - first)
),
true,
)
}
fn from_file(path: &str, budget: usize) -> Result<(String, bool)> {
let mut file = std::fs::File::open(path)?;
let size = file.metadata()?.len();
if size <= budget as u64 {
let mut bytes = Vec::new();
file.take(budget as u64).read_to_end(&mut bytes)?;
return Ok(bounded(&String::from_utf8_lossy(&bytes), budget));
}
let first = budget.div_ceil(2);
let last = budget - first;
let mut begin = Vec::new();
(&mut file).take(first as u64).read_to_end(&mut begin)?;
file.seek(SeekFrom::Start(size - last as u64))?;
let mut end = Vec::new();
file.take(last as u64).read_to_end(&mut end)?;
// UTF-8 replacement can expand binary input; bound displayed UTF-8 too.
Ok((
format!(
"{}{MARKER}{}",
head(&String::from_utf8_lossy(&begin), first),
tail(&String::from_utf8_lossy(&end), last)
),
true,
))
}
pub fn apply(result: &mut Executed, budget: u32) {
let service_cut = result.truncated;
result.truncated = result.service_truncated;
for (stream, text, path) in [
("stdout", &mut result.stdout, &result.stdout_path),
("stderr", &mut result.stderr, &result.stderr_path),
] {
let excerpt = path.as_deref().map(|p| from_file(p, budget as usize));
let (display, cut) = match excerpt {
Some(Ok(value)) => value,
Some(Err(e)) => {
result
.output_notice
.get_or_insert_with(String::new)
.push_str(&format!(
"{stream} excerpt could not read archive: {}; showing service excerpt. ",
e.message
));
let (display, cut) = bounded(text, budget as usize);
(display, cut || service_cut)
}
None => {
let (display, cut) = bounded(text, budget as usize);
(display, cut || service_cut)
}
};
*text = display;
result.truncated |= cut;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utf8_and_tiny_budgets_are_bounded() {
for budget in 0..40 {
let text = "🦀é".repeat(20);
let (value, cut) = bounded(&text, budget);
assert!(cut);
assert!(value.len() <= budget + MARKER.len());
assert!(value.contains(MARKER));
}
assert_eq!(bounded("", 0), (String::new(), false));
assert_eq!(bounded("small", 5), ("small".into(), false));
}
}