use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[allow(dead_code)]
pub enum CompactionLevel {
Raw = 0,
Daily = 1,
Weekly = 2,
Monthly = 3,
Root = 4,
}
impl CompactionLevel {
#[allow(dead_code)]
pub fn threshold(&self) -> usize {
match self {
CompactionLevel::Raw => 200,
CompactionLevel::Daily => 300,
CompactionLevel::Weekly => 500,
CompactionLevel::Monthly => usize::MAX,
CompactionLevel::Root => usize::MAX,
}
}
#[allow(dead_code)]
pub fn dir_name(&self) -> &'static str {
match self {
CompactionLevel::Raw => "raw",
CompactionLevel::Daily => "daily",
CompactionLevel::Weekly => "weekly",
CompactionLevel::Monthly => "monthly",
CompactionLevel::Root => "root",
}
}
#[allow(dead_code)]
pub fn all() -> &'static [CompactionLevel] {
&[
CompactionLevel::Raw,
CompactionLevel::Daily,
CompactionLevel::Weekly,
CompactionLevel::Monthly,
CompactionLevel::Root,
]
}
#[allow(dead_code)]
pub fn next(&self) -> Option<CompactionLevel> {
match self {
CompactionLevel::Raw => Some(CompactionLevel::Daily),
CompactionLevel::Daily => Some(CompactionLevel::Weekly),
CompactionLevel::Weekly => Some(CompactionLevel::Monthly),
CompactionLevel::Monthly => Some(CompactionLevel::Root),
CompactionLevel::Root => None,
}
}
}
pub struct CompactionTree {
pub line_threshold: usize,
}
impl CompactionTree {
pub fn new(line_threshold: usize) -> Self {
Self { line_threshold }
}
pub fn default_tree() -> Self {
Self::new(200)
}
pub fn should_compact(&self, content: &str) -> bool {
content.lines().count() >= self.line_threshold
}
pub fn rule_based_compact(&self, content: &str) -> String {
let lines: Vec<&str> = content.lines().collect();
if lines.len() < 5 {
return content.to_string();
}
let mut compacted: Vec<String> = Vec::new();
compacted.extend(lines.iter().take(2).map(|l| l.to_string()));
if lines.len() > 10 {
compacted.push(format!("... ({} lines omitted) ...", lines.len() - 4));
}
let tail: Vec<String> = lines.iter().rev().take(2).map(|l| l.to_string()).collect();
compacted.extend(tail.into_iter().rev());
compacted.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compaction_level_next() {
assert_eq!(CompactionLevel::Raw.next(), Some(CompactionLevel::Daily));
assert_eq!(CompactionLevel::Daily.next(), Some(CompactionLevel::Weekly));
assert_eq!(
CompactionLevel::Weekly.next(),
Some(CompactionLevel::Monthly)
);
assert_eq!(CompactionLevel::Monthly.next(), Some(CompactionLevel::Root));
assert_eq!(CompactionLevel::Root.next(), None);
}
#[test]
fn test_should_compact_short() {
let tree = CompactionTree::new(10);
let content = "line 1\nline 2\nline 3";
assert!(!tree.should_compact(content));
}
#[test]
fn test_should_compact_long() {
let tree = CompactionTree::new(5);
let content = (0..10)
.map(|i| format!("line {}", i))
.collect::<Vec<_>>()
.join("\n");
assert!(tree.should_compact(&content));
}
#[test]
fn test_rule_based_compact_short() {
let tree = CompactionTree::new(10);
let content = "line 1\nline 2\nline 3";
let result = tree.rule_based_compact(content);
assert_eq!(result, content);
}
#[test]
fn test_rule_based_compact_long() {
let tree = CompactionTree::new(10);
let content = (0..20)
.map(|i| format!("line {}", i))
.collect::<Vec<_>>()
.join("\n");
let result = tree.rule_based_compact(&content);
assert!(result.lines().count() < 20, "Should be compacted");
assert!(result.contains("line 0"), "Should preserve first line");
assert!(result.contains("line 19"), "Should preserve last line");
assert!(
result.contains("omitted"),
"Should indicate omitted content"
);
}
}