use regex::Regex;
use std::sync::OnceLock;
pub const TRANSFORM_ID: &str = "log_compaction";
pub const TRANSFORM_VERSION: &str = "1.0.0";
const MIN_RUN_LEN: usize = 3;
fn iso8601_timestamp_pattern() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})[ \t]+")
.expect("iso8601_timestamp_pattern regex is a fixed valid literal")
})
}
fn syslog_timestamp_pattern() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r"^[A-Z][a-z]{2}[ \t]+\d{1,2}[ \t]+\d{2}:\d{2}:\d{2}[ \t]+")
.expect("syslog_timestamp_pattern regex is a fixed valid literal")
})
}
fn strip_timestamp(line: &str) -> &str {
if let Some(m) = iso8601_timestamp_pattern().find(line) {
return &line[m.end()..];
}
if let Some(m) = syslog_timestamp_pattern().find(line) {
return &line[m.end()..];
}
line
}
pub fn compact(input: &str, remove_timestamps: bool) -> String {
if input.is_empty() {
return String::new();
}
let lines: Vec<&str> = input.lines().collect();
if remove_timestamps {
let stripped: Vec<&str> = lines.iter().map(|line| strip_timestamp(line)).collect();
collapse_adjacent_runs(&stripped)
} else {
collapse_adjacent_runs(&lines)
}
}
fn collapse_adjacent_runs(lines: &[&str]) -> String {
let mut out: Vec<String> = Vec::new();
let mut iter = lines.iter();
let Some(&first) = iter.next() else {
return String::new();
};
let mut current = first;
let mut count = 1usize;
for &line in iter {
if line == current {
count += 1;
} else {
push_run(&mut out, current, count);
current = line;
count = 1;
}
}
push_run(&mut out, current, count);
out.join("\n")
}
fn push_run(out: &mut Vec<String>, line: &str, count: usize) {
if count >= MIN_RUN_LEN {
out.push(line.to_string());
out.push(format!("[repeated {count}x]"));
out.push(line.to_string());
} else {
for _ in 0..count {
out.push(line.to_string());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_of_four_collapses_to_first_marker_last() {
let input = "A\nA\nA\nA";
assert_eq!(compact(input, false), "A\n[repeated 4x]\nA");
}
#[test]
fn run_of_exactly_three_collapses_boundary_case() {
let input = "A\nA\nA";
assert_eq!(compact(input, false), "A\n[repeated 3x]\nA");
}
#[test]
fn run_of_exactly_two_does_not_collapse_boundary_case() {
let input = "A\nA";
assert_eq!(compact(input, false), "A\nA");
}
#[test]
fn non_adjacent_duplicates_are_never_collapsed() {
let input = "A\nB\nA";
assert_eq!(compact(input, false), "A\nB\nA");
}
#[test]
fn single_line_input_is_unchanged() {
let input = "only line";
assert_eq!(compact(input, false), "only line");
}
#[test]
fn empty_input_produces_empty_output() {
assert_eq!(compact("", false), "");
}
#[test]
fn mix_of_runs_and_singletons_preserves_relative_ordering() {
let input = "start\nA\nA\nA\nA\nA\nmiddle\nB\nB\nend";
let expected = "start\nA\n[repeated 5x]\nA\nmiddle\nB\nB\nend";
assert_eq!(compact(input, false), expected);
}
#[test]
fn remove_timestamps_true_collapses_lines_that_differ_only_by_timestamp() {
let input = "2026-07-11T10:22:03Z connection reset\n\
2026-07-11T10:22:04.123456Z connection reset\n\
2026-07-11T10:22:05+00:00 connection reset";
let output = compact(input, true);
assert_eq!(output, "connection reset\n[repeated 3x]\nconnection reset");
}
#[test]
fn remove_timestamps_true_strips_syslog_style_prefix_too() {
let input = "Jul 11 10:22:03 connection reset\nJul 11 10:22:04 connection reset\nJul 11 10:22:05 connection reset";
let output = compact(input, true);
assert_eq!(output, "connection reset\n[repeated 3x]\nconnection reset");
}
#[test]
fn remove_timestamps_false_keeps_timestamp_differing_lines_distinct() {
let input = "2026-07-11T10:22:03Z connection reset\n2026-07-11T10:22:04Z connection reset";
assert_eq!(compact(input, false), input);
}
#[test]
fn remove_timestamps_true_leaves_lines_without_a_recognized_timestamp_untouched() {
let input = "no timestamp here\nno timestamp here\nno timestamp here";
let output = compact(input, true);
assert_eq!(
output,
"no timestamp here\n[repeated 3x]\nno timestamp here"
);
}
}