pub mod implementations;
pub mod traits;
pub use implementations::{
CliProgressSink, ProgressEvent, RecordingProgressSink, SilentProgressSink,
};
pub use traits::{HasProgress, ProgressSink};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::sync::Arc;
pub const TEMPLATE_CALL_GRAPH: &str = "{msg} {pos}/{len} files ({percent}%) - {eta}";
pub const TEMPLATE_TRAIT_RESOLUTION: &str = "{msg} {pos}/{len} traits - {eta}";
pub const TEMPLATE_COVERAGE: &str = "{msg} {pos}/{len} files - {eta}";
pub const TEMPLATE_FUNCTION_ANALYSIS: &str =
"{msg} {pos}/{len} functions ({percent}%) - {per_sec}/sec - {eta}";
pub const TEMPLATE_FILE_ANALYSIS: &str = "{msg} {pos}/{len} files ({percent}%) - {eta}";
pub const TEMPLATE_SPINNER: &str = "{spinner} {msg}";
#[derive(Debug, Clone, Default)]
pub struct ProgressConfig {
pub quiet_mode: bool,
pub verbosity: u8,
}
impl ProgressConfig {
pub fn from_env(quiet: bool, verbosity: u8) -> Self {
let env_quiet = std::env::var("DEBTMAP_QUIET").is_ok();
Self {
quiet_mode: quiet || env_quiet,
verbosity,
}
}
pub fn should_show_progress(&self) -> bool {
if self.quiet_mode {
return false;
}
use std::io::IsTerminal;
std::io::stderr().is_terminal()
}
}
static GLOBAL_PROGRESS: Lazy<Arc<Mutex<Option<ProgressManager>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
#[derive(Clone)]
pub struct ProgressManager {
multi: Arc<MultiProgress>,
config: ProgressConfig,
tui_manager: Arc<Mutex<Option<crate::tui::TuiManager>>>,
tui_active: Arc<Mutex<bool>>,
}
impl ProgressManager {
pub fn new(config: ProgressConfig) -> Self {
let tui_manager = if config.should_show_progress() {
crate::tui::TuiManager::new().ok()
} else {
None
};
let tui_active = tui_manager.is_some();
Self {
multi: Arc::new(MultiProgress::new()),
config,
tui_manager: Arc::new(Mutex::new(tui_manager)),
tui_active: Arc::new(Mutex::new(tui_active)),
}
}
pub fn init_global(config: ProgressConfig) {
let manager = Self::new(config);
*GLOBAL_PROGRESS.lock() = Some(manager);
}
pub fn global() -> Option<Self> {
GLOBAL_PROGRESS.lock().clone()
}
pub fn create_bar(&self, len: u64, template: &str) -> ProgressBar {
if !self.config.should_show_progress() || *self.tui_active.lock() {
return ProgressBar::hidden();
}
let pb = self.multi.add(ProgressBar::new(len));
pb.set_style(
ProgressStyle::default_bar()
.template(template)
.expect("Invalid progress bar template")
.progress_chars("█▓▒░ "),
);
pb
}
pub fn create_spinner(&self, msg: &str) -> ProgressBar {
if !self.config.should_show_progress() || *self.tui_active.lock() {
return ProgressBar::hidden();
}
let pb = self.multi.add(ProgressBar::new_spinner());
pb.set_style(
ProgressStyle::default_spinner()
.template(TEMPLATE_SPINNER)
.expect("Invalid spinner template")
.tick_chars("|/-\\"),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(std::time::Duration::from_millis(100));
pb
}
pub fn create_counter(&self, template: &str, msg: &str) -> ProgressBar {
if !self.config.should_show_progress() || *self.tui_active.lock() {
return ProgressBar::hidden();
}
let pb = self.multi.add(ProgressBar::new_spinner());
pb.set_style(
ProgressStyle::default_spinner()
.template(template)
.expect("Invalid counter template")
.tick_chars("|/-\\"),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(std::time::Duration::from_millis(100));
pb
}
pub fn verbosity(&self) -> u8 {
self.config.verbosity
}
pub fn is_tui_active(&self) -> Option<bool> {
Some(*self.tui_active.lock())
}
pub fn clear(&self) -> std::io::Result<()> {
self.multi.clear()
}
pub fn tui_start_stage(&self, stage_index: usize) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.start_stage(stage_index);
}
}
pub fn tui_complete_stage(&self, stage_index: usize, metric: impl Into<String>) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.complete_stage(stage_index, metric);
}
}
pub fn tui_update_metric(&self, stage_index: usize, metric: impl Into<String>) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.update_stage_metric(stage_index, metric);
}
}
pub fn tui_update_subtask(
&self,
stage_index: usize,
subtask_index: usize,
status: crate::tui::app::StageStatus,
progress: Option<(usize, usize)>,
) {
self.tui_update_subtask_labeled(stage_index, subtask_index, status, progress, None);
}
pub fn tui_update_subtask_labeled(
&self,
stage_index: usize,
subtask_index: usize,
status: crate::tui::app::StageStatus,
progress: Option<(usize, usize)>,
label: Option<&str>,
) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.update_subtask_labeled(stage_index, subtask_index, status, progress, label);
}
}
pub fn tui_set_progress(&self, progress: f64) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.set_overall_progress(progress);
}
}
pub fn tui_update_stats(&self, functions: usize, debt: usize, coverage: f64) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.update_stats(functions, debt, coverage);
}
}
pub fn tui_update_counts(&self, functions: usize, debt: usize) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.functions_count = functions;
app.debt_count = debt;
}
}
pub fn tui_update_coverage(&self, coverage: f64) {
let guard = self.tui_manager.lock();
if let Some(ref tui) = *guard {
let app_arc = tui.app();
let mut app = app_arc.lock();
app.coverage_percent = coverage;
}
}
pub fn tui_render(&self) {
}
pub fn tui_cleanup(&self) {
let mut guard = self.tui_manager.lock();
if let Some(ref mut tui) = *guard {
let _ = tui.cleanup();
}
*self.tui_active.lock() = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quiet_mode_disables_progress() {
std::env::set_var("DEBTMAP_QUIET", "1");
let config = ProgressConfig::from_env(false, 0);
assert!(!config.should_show_progress());
std::env::remove_var("DEBTMAP_QUIET");
}
#[test]
fn test_explicit_quiet_flag() {
let config = ProgressConfig::from_env(true, 0);
assert!(!config.should_show_progress());
}
#[test]
fn test_verbosity_levels() {
let config = ProgressConfig::from_env(false, 0);
assert_eq!(config.verbosity, 0);
let config = ProgressConfig::from_env(false, 2);
assert_eq!(config.verbosity, 2);
}
#[test]
fn test_progress_manager_creates_hidden_bars_in_quiet_mode() {
let config = ProgressConfig {
quiet_mode: true,
verbosity: 0,
};
let manager = ProgressManager::new(config);
let pb = manager.create_bar(100, TEMPLATE_CALL_GRAPH);
assert!(pb.is_hidden());
let spinner = manager.create_spinner("Test");
assert!(spinner.is_hidden());
}
}