use std::collections::HashSet;
use super::{Ctx, FORCE_PRIORITY, MIN_KEEP, priority, query_bonus, rebuild, select_keep, template};
use crate::stages::sizing::optimal_keep;
const MIN_LINES: usize = 30;
pub fn compress(text: &str, ctx: &Ctx, query: &HashSet<String>) -> Option<String> {
if text.lines().count() < MIN_LINES {
return None;
}
let (collapsed, folded) = template::collapse_global(text);
if !folded {
return None;
}
let lines: Vec<&str> = collapsed.lines().collect();
if lines.len() <= ctx.max_lines {
return Some(collapsed); }
let scores: Vec<f64> = lines
.iter()
.map(|l| priority(l) + query_bonus(l, query))
.collect();
let k = optimal_keep(&lines, MIN_KEEP, ctx.max_lines);
let keep = select_keep(&scores, k, FORCE_PRIORITY);
if keep.iter().all(|&x| x) {
return Some(collapsed);
}
Some(rebuild(&lines, &keep))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stages::toolout::test_ctx;
fn repetitive_dump() -> String {
let mut lines: Vec<String> = (0..80)
.map(|i| format!("checking dependency package-{i} resolved ok cached"))
.collect();
lines.insert(
40,
"ERROR version conflict on left-pad needs manual resolution".to_string(),
);
lines.join("\n")
}
#[test]
fn folds_repetitive_output_and_keeps_error() {
let dump = repetitive_dump();
let out = compress(&dump, &test_ctx(), &HashSet::new()).expect("fires");
assert!(out.lines().count() < dump.lines().count(), "got shorter");
assert!(
out.contains("version conflict on left-pad"),
"error survives"
);
assert!(out.contains("[×"), "repetitive runs folded");
}
#[test]
fn declines_diverse_prose() {
const W: &[&str] = &[
"alpha", "bravo", "cobalt", "dune", "ember", "flint", "granite", "harbor", "ivory",
"jade", "kelp", "lotus", "maple", "nectar", "opal", "pearl", "quartz", "rust", "slate",
"topaz",
];
let prose: String = (0..40)
.map(|i| {
format!(
"The {} review of {} examined {} across the {} division.",
W[i % W.len()],
W[(i + 3) % W.len()],
W[(i + 7) % W.len()],
W[(i + 11) % W.len()]
)
})
.collect::<Vec<_>>()
.join("\n");
assert_eq!(compress(&prose, &test_ctx(), &HashSet::new()), None);
}
#[test]
fn declines_short_segments() {
let short = (0..10)
.map(|i| format!("line {i} same"))
.collect::<Vec<_>>()
.join("\n");
assert_eq!(compress(&short, &test_ctx(), &HashSet::new()), None);
}
#[test]
fn query_relevant_lines_beat_indentation_prior() {
let mut lines: Vec<String> = (0..120)
.map(|i| format!(" <td class=\"row-{i}\">status value pending</td>"))
.collect();
lines.insert(58, " svg dark variant uses theme tokens".to_string());
lines.insert(
59,
" prefers-color-scheme light maps the svg palette".to_string(),
);
lines.insert(
60,
" light theme fallback for the embedded svg".to_string(),
);
let dump = lines.join("\n");
let query: HashSet<String> = ["svg".to_string(), "light".to_string(), "theme".to_string()]
.into_iter()
.collect();
let out = compress(&dump, &test_ctx(), &query).expect("fires");
assert!(
out.lines().count() < dump.lines().count(),
"windowing happened"
);
for needle in [
"svg dark variant",
"prefers-color-scheme light",
"light theme fallback",
] {
assert!(out.contains(needle), "ask-relevant line kept: {needle}");
}
}
#[test]
fn query_relevant_line_survives() {
let mut lines: Vec<String> = (0..80)
.map(|i| format!("scanning module-{i} clean nothing to report"))
.collect();
lines.insert(
50,
"module billing references the widget gateway endpoint".to_string(),
);
let dump = lines.join("\n");
let query: HashSet<String> = ["widget".to_string()].into_iter().collect();
let out = compress(&dump, &test_ctx(), &query).expect("fires");
assert!(out.contains("widget gateway"), "query-relevant line kept");
}
}