use std::io::{self, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
pub trait ProgressReporter: Send + Sync {
fn report_file(&self, current: usize, total: usize, file_path: &str, rule_id: &str);
fn report_complete(&self, total_violations: usize);
fn is_cancelled(&self) -> bool;
fn report_prescan_start(&self, _num_directories: usize) {}
fn report_prescan_complete(&self, _num_functions: usize) {}
fn report_include_resolve_start(&self, _num_include_paths: usize) {}
fn report_include_resolve_complete(&self, _num_headers: usize) {}
}
pub struct CLIProgressReporter {
last_line_length: Arc<std::sync::Mutex<usize>>,
last_file: Arc<std::sync::Mutex<usize>>,
verbosity: u8,
}
impl Default for CLIProgressReporter {
fn default() -> Self {
Self {
last_line_length: Arc::new(std::sync::Mutex::new(0)),
last_file: Arc::new(std::sync::Mutex::new(0)),
verbosity: 0,
}
}
}
impl CLIProgressReporter {
pub fn new(verbosity: u8) -> Self {
Self {
verbosity,
..Self::default()
}
}
fn clear_line(&self) {
let last_len = *self
.last_line_length
.lock()
.unwrap_or_else(|e| e.into_inner());
if last_len > 0 {
print!("\r{}\r", " ".repeat(last_len));
io::stdout().flush().unwrap_or(());
}
}
fn write_progress(&self, message: &str) {
*self
.last_line_length
.lock()
.unwrap_or_else(|e| e.into_inner()) = message.len();
print!("\r{}", message);
io::stdout().flush().unwrap_or(());
}
}
impl ProgressReporter for CLIProgressReporter {
fn report_prescan_start(&self, num_directories: usize) {
println!(
"Pre-scanning {} {} for function definitions...",
num_directories,
if num_directories == 1 {
"directory"
} else {
"directories"
}
);
}
fn report_prescan_complete(&self, num_functions: usize) {
println!(
"Pre-scan complete: found {} function definitions",
num_functions
);
}
fn report_include_resolve_start(&self, num_include_paths: usize) {
println!(
"Resolving #include directives against {} search {}...",
num_include_paths,
if num_include_paths == 1 {
"path"
} else {
"paths"
}
);
}
fn report_include_resolve_complete(&self, num_headers: usize) {
println!(
"Include resolution complete: parsed {} header {}",
num_headers,
if num_headers == 1 { "file" } else { "files" }
);
}
fn report_file(&self, current: usize, total: usize, file_path: &str, rule_id: &str) {
if self.verbosity >= 1 {
let message = format!(
"Scanning: [file {}/{}] {} - Checking: {}",
current, total, file_path, rule_id
);
self.write_progress(&message);
} else {
let mut last = self.last_file.lock().unwrap_or_else(|e| e.into_inner());
if *last != current {
*last = current;
drop(last);
let pct = current * 100 / total;
let bar_width = 30;
let filled = current * bar_width / total;
let bar: String = "█".repeat(filled) + &"░".repeat(bar_width - filled);
let max_path = 40;
let display_path = if file_path.len() > max_path {
&file_path[file_path.len() - max_path..]
} else {
file_path
};
let message = format!(
"Scanning: [{}] {}/{} ({}%) {}",
bar, current, total, pct, display_path
);
self.write_progress(&message);
}
}
}
fn report_complete(&self, total_violations: usize) {
self.clear_line();
println!("Scan complete. Found {} violations", total_violations);
}
fn is_cancelled(&self) -> bool {
false
}
}
#[allow(dead_code)]
pub struct GUIProgressReporter {
pub current_file: Arc<std::sync::Mutex<usize>>,
pub total_files: Arc<std::sync::Mutex<usize>>,
pub file_path: Arc<std::sync::Mutex<String>>,
pub rule_id: Arc<std::sync::Mutex<String>>,
pub cancellation: Arc<AtomicBool>,
}
impl GUIProgressReporter {
#[allow(dead_code)]
pub fn new(cancellation: Arc<AtomicBool>) -> Self {
Self {
current_file: Arc::new(std::sync::Mutex::new(0)),
total_files: Arc::new(std::sync::Mutex::new(0)),
file_path: Arc::new(std::sync::Mutex::new(String::new())),
rule_id: Arc::new(std::sync::Mutex::new(String::new())),
cancellation,
}
}
#[allow(dead_code)]
pub fn get_state(&self) -> (usize, usize, String, String) {
let current = *self.current_file.lock().unwrap_or_else(|e| e.into_inner());
let total = *self.total_files.lock().unwrap_or_else(|e| e.into_inner());
let file = self
.file_path
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone();
let rule = self
.rule_id
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone();
(current, total, file, rule)
}
}
impl ProgressReporter for GUIProgressReporter {
fn report_file(&self, current: usize, total: usize, file_path: &str, rule_id: &str) {
*self.current_file.lock().unwrap_or_else(|e| e.into_inner()) = current;
*self.total_files.lock().unwrap_or_else(|e| e.into_inner()) = total;
*self.file_path.lock().unwrap_or_else(|e| e.into_inner()) = file_path.to_string();
*self.rule_id.lock().unwrap_or_else(|e| e.into_inner()) = rule_id.to_string();
}
fn report_complete(&self, _total_violations: usize) {
}
fn is_cancelled(&self) -> bool {
self.cancellation.load(Ordering::Relaxed)
}
}