pub struct LineFilter {
filters: Vec<FilterRule>,
pub is_active: bool,
}
pub struct FilterRule {
pub pattern: String,
pub regex: regex::Regex,
pub mode: FilterMode,
}
#[derive(Clone, Copy, PartialEq)]
pub enum FilterMode {
Include,
Exclude,
}
impl LineFilter {
pub fn new() -> Self {
Self {
filters: Vec::new(),
is_active: false,
}
}
pub fn add_include(&mut self, pattern: &str) -> Result<(), String> {
let regex = regex::Regex::new(pattern)
.map_err(|e| format!("Invalid regex: {}", e))?;
self.filters.push(FilterRule {
pattern: pattern.to_string(),
regex,
mode: FilterMode::Include,
});
self.is_active = true;
Ok(())
}
pub fn add_exclude(&mut self, pattern: &str) -> Result<(), String> {
let regex = regex::Regex::new(pattern)
.map_err(|e| format!("Invalid regex: {}", e))?;
self.filters.push(FilterRule {
pattern: pattern.to_string(),
regex,
mode: FilterMode::Exclude,
});
self.is_active = true;
Ok(())
}
pub fn should_display(&self, text: &str) -> bool {
if !self.is_active || self.filters.is_empty() {
return true;
}
let has_include_filters = self.filters.iter().any(|f| f.mode == FilterMode::Include);
if has_include_filters {
let matches_include = self.filters.iter()
.filter(|f| f.mode == FilterMode::Include)
.any(|f| f.regex.is_match(text));
if !matches_include {
return false;
}
}
let matches_exclude = self.filters.iter()
.filter(|f| f.mode == FilterMode::Exclude)
.any(|f| f.regex.is_match(text));
!matches_exclude
}
pub fn clear(&mut self) {
self.filters.clear();
self.is_active = false;
}
pub fn count(&self) -> usize {
self.filters.len()
}
pub fn descriptions(&self) -> Vec<String> {
self.filters.iter().map(|f| {
let prefix = match f.mode {
FilterMode::Include => "+",
FilterMode::Exclude => "-",
};
format!("{}{}", prefix, f.pattern)
}).collect()
}
pub fn remove(&mut self, index: usize) {
if index < self.filters.len() {
self.filters.remove(index);
self.is_active = !self.filters.is_empty();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_filters() {
let filter = LineFilter::new();
assert!(filter.should_display("anything"));
}
#[test]
fn test_include_filter() {
let mut filter = LineFilter::new();
filter.add_include("ERROR|WARN").unwrap();
assert!(filter.should_display("[ERROR] something broke"));
assert!(filter.should_display("[WARN] low battery"));
assert!(!filter.should_display("[INFO] all good"));
}
#[test]
fn test_exclude_filter() {
let mut filter = LineFilter::new();
filter.add_exclude("DEBUG").unwrap();
assert!(filter.should_display("[INFO] hello"));
assert!(!filter.should_display("[DEBUG] verbose stuff"));
}
#[test]
fn test_combined_filters() {
let mut filter = LineFilter::new();
filter.add_include("\\[.*\\]").unwrap(); filter.add_exclude("DEBUG").unwrap(); assert!(filter.should_display("[INFO] hello"));
assert!(!filter.should_display("[DEBUG] verbose"));
assert!(!filter.should_display("no brackets here"));
}
#[test]
fn test_clear() {
let mut filter = LineFilter::new();
filter.add_include("test").unwrap();
assert_eq!(filter.count(), 1);
filter.clear();
assert_eq!(filter.count(), 0);
assert!(filter.should_display("anything"));
}
#[test]
fn test_invalid_regex() {
let mut filter = LineFilter::new();
assert!(filter.add_include("[invalid").is_err());
}
}