Skip to main content

cli_denoiser/filters/
generic.rs

1use super::{Filter, FilterResult};
2
3/// Generic fallback filter for unknown commands.
4/// Only strips patterns that are UNIVERSALLY noise regardless of context:
5/// - Lines that are purely whitespace
6/// - Lines that are purely box-drawing characters (decorative borders)
7///
8/// This filter is maximally conservative. When in doubt, keep everything.
9pub struct GenericFilter;
10
11impl Filter for GenericFilter {
12    fn name(&self) -> &'static str {
13        "generic"
14    }
15
16    fn filter_line(&self, line: &str) -> FilterResult {
17        let trimmed = line.trim();
18
19        // Keep empty lines (they're structural)
20        if trimmed.is_empty() {
21            return FilterResult::Keep;
22        }
23
24        // Drop lines that are purely decorative box-drawing
25        if is_decorative_border(trimmed) {
26            return FilterResult::Drop;
27        }
28
29        FilterResult::Keep
30    }
31}
32
33/// Check if a line is purely decorative (box-drawing chars, dashes, equals).
34/// Must be at least 4 chars to avoid false positives on short dashes.
35fn is_decorative_border(line: &str) -> bool {
36    if line.len() < 10 {
37        return false;
38    }
39
40    let total = line.chars().count();
41    let border_chars = line
42        .chars()
43        .filter(|c| {
44            matches!(
45                c,
46                '─' | '━'
47                    | '│'
48                    | '┃'
49                    | '┌'
50                    | '┐'
51                    | '└'
52                    | '┘'
53                    | '├'
54                    | '┤'
55                    | '┬'
56                    | '┴'
57                    | '┼'
58                    | '═'
59                    | '║'
60                    | '╔'
61                    | '╗'
62                    | '╚'
63                    | '╝'
64                    | '╠'
65                    | '╣'
66                    | '╦'
67                    | '╩'
68                    | '╬'
69                    | '-'
70                    | '='
71                    | '+'
72                    | '|'
73                    | '*'
74                    | ' '
75            )
76        })
77        .count();
78
79    // 95%+ border characters = decorative
80    border_chars * 100 / total >= 95
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn drops_dashed_borders() {
89        let filter = GenericFilter;
90        assert_eq!(
91            filter.filter_line("----------------------------------------"),
92            FilterResult::Drop
93        );
94        assert_eq!(
95            filter.filter_line("========================================"),
96            FilterResult::Drop
97        );
98    }
99
100    #[test]
101    fn drops_box_drawing() {
102        let filter = GenericFilter;
103        assert_eq!(
104            filter.filter_line("┌──────────────────────────────────────┐"),
105            FilterResult::Drop
106        );
107        assert_eq!(
108            filter.filter_line("└──────────────────────────────────────┘"),
109            FilterResult::Drop
110        );
111    }
112
113    #[test]
114    fn keeps_short_dashes() {
115        let filter = GenericFilter;
116        // Short dashes could be flags or list items
117        assert_eq!(filter.filter_line("---"), FilterResult::Keep);
118        assert_eq!(filter.filter_line("--help"), FilterResult::Keep);
119    }
120
121    #[test]
122    fn keeps_text_with_dashes() {
123        let filter = GenericFilter;
124        assert_eq!(
125            filter.filter_line("error: something -- failed here"),
126            FilterResult::Keep
127        );
128    }
129
130    #[test]
131    fn keeps_normal_text() {
132        let filter = GenericFilter;
133        assert_eq!(filter.filter_line("hello world"), FilterResult::Keep);
134    }
135}