Skip to main content

cc_audit/reporter/
progress.rs

1//! Progress bar for terminal output during scanning.
2//!
3//! Uses Braille pattern characters for a modern, high-density display.
4
5use indicatif::{ProgressBar, ProgressStyle};
6
7/// Minimum number of files to display progress bar
8const MIN_FILES_FOR_PROGRESS: usize = 10;
9
10/// Progress bar manager for scan operations.
11pub struct ScanProgress {
12    bar: Option<ProgressBar>,
13}
14
15impl ScanProgress {
16    /// Create a new progress bar if conditions are met.
17    ///
18    /// Progress bar is only shown if:
19    /// - Total files >= 10
20    /// - Running in TTY (interactive terminal)
21    /// - Not in CI mode
22    pub fn new(total_files: usize, is_tty: bool, is_ci: bool) -> Self {
23        let bar = if should_show_progress(total_files, is_tty, is_ci) {
24            Some(create_progress_bar(total_files))
25        } else {
26            None
27        };
28
29        Self { bar }
30    }
31
32    /// Increment progress by one file.
33    pub fn inc(&self) {
34        if let Some(bar) = &self.bar {
35            bar.inc(1);
36        }
37    }
38
39    /// Finish and clear the progress bar.
40    pub fn finish(&self) {
41        if let Some(bar) = &self.bar {
42            bar.finish_and_clear();
43        }
44    }
45}
46
47/// Check if progress bar should be displayed.
48fn should_show_progress(total_files: usize, is_tty: bool, is_ci: bool) -> bool {
49    total_files >= MIN_FILES_FOR_PROGRESS && is_tty && !is_ci
50}
51
52/// Create a progress bar with Braille pattern style.
53fn create_progress_bar(total: usize) -> ProgressBar {
54    let pb = ProgressBar::new(total as u64);
55    pb.set_style(
56        ProgressStyle::with_template(
57            "Scanning {bar:40} {pos:>4}/{len:4} files ({percent:>3}%) [{elapsed_precise} < {eta_precise}]",
58        )
59        .expect("Invalid progress bar template")
60        .progress_chars("⣿⣀ "), // Braille pattern: filled, current, empty
61    );
62    pb
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_should_show_progress_below_threshold() {
71        // 9 files - below threshold
72        assert!(!should_show_progress(9, true, false));
73    }
74
75    #[test]
76    fn test_should_show_progress_at_threshold() {
77        // 10 files - at threshold, should show
78        assert!(should_show_progress(10, true, false));
79    }
80
81    #[test]
82    fn test_should_show_progress_above_threshold() {
83        // 100 files - above threshold, should show
84        assert!(should_show_progress(100, true, false));
85    }
86
87    #[test]
88    fn test_should_not_show_in_non_tty() {
89        // Non-TTY environment (e.g., piped output)
90        assert!(!should_show_progress(100, false, false));
91    }
92
93    #[test]
94    fn test_should_not_show_in_ci() {
95        // CI environment
96        assert!(!should_show_progress(100, true, true));
97    }
98
99    #[test]
100    fn test_should_not_show_non_tty_and_ci() {
101        // Both non-TTY and CI
102        assert!(!should_show_progress(100, false, true));
103    }
104
105    #[test]
106    fn test_new_creates_bar_when_conditions_met() {
107        let progress = ScanProgress::new(10, true, false);
108        assert!(progress.bar.is_some());
109    }
110
111    #[test]
112    fn test_new_no_bar_when_below_threshold() {
113        let progress = ScanProgress::new(9, true, false);
114        assert!(progress.bar.is_none());
115    }
116
117    #[test]
118    fn test_new_no_bar_when_non_tty() {
119        let progress = ScanProgress::new(100, false, false);
120        assert!(progress.bar.is_none());
121    }
122
123    #[test]
124    fn test_new_no_bar_when_ci() {
125        let progress = ScanProgress::new(100, true, true);
126        assert!(progress.bar.is_none());
127    }
128
129    #[test]
130    fn test_inc_with_bar() {
131        let progress = ScanProgress::new(10, true, false);
132        // Should not panic
133        progress.inc();
134    }
135
136    #[test]
137    fn test_inc_without_bar() {
138        let progress = ScanProgress::new(5, true, false);
139        // Should not panic even without bar
140        progress.inc();
141    }
142
143    #[test]
144    fn test_finish_with_bar() {
145        let progress = ScanProgress::new(10, true, false);
146        // Should not panic
147        progress.finish();
148    }
149
150    #[test]
151    fn test_finish_without_bar() {
152        let progress = ScanProgress::new(5, true, false);
153        // Should not panic even without bar
154        progress.finish();
155    }
156
157    #[test]
158    fn test_create_progress_bar() {
159        // Verify that create_progress_bar doesn't panic and creates a valid bar
160        let pb = create_progress_bar(100);
161        assert_eq!(pb.length(), Some(100));
162    }
163}