use colored::Colorize;
use std::path::PathBuf;
use std::time::Duration;
use super::{ReportArgs, ReportFormat};
use crate::cache::{AuditCache, delete_cache_directory};
use crate::cli::output::{HtmlReport, JsonOutput, MarkdownReport, ReportRenderer};
use crate::config::Config;
use crate::error::RepoLensError;
use crate::exit_codes;
use crate::rules::engine::RulesEngine;
use crate::rules::filter_valid_categories;
use crate::scanner::Scanner;
use crate::utils::format_duration;
pub async fn execute(args: ReportArgs) -> Result<i32, RepoLensError> {
let mut config = Config::load_or_default()?;
if let Some(ref cache_dir) = args.cache_dir {
config.cache.directory = cache_dir.display().to_string();
}
if args.no_cache {
config.cache.enabled = false;
}
let project_root = PathBuf::from(".");
if args.clear_cache {
if let Err(e) = delete_cache_directory(&project_root, &config.cache) {
eprintln!("{} Failed to clear cache: {}", "Warning:".yellow(), e);
}
}
let cache = if config.cache.enabled {
Some(AuditCache::load(&project_root, config.cache.clone()))
} else {
None
};
let scanner = Scanner::new(PathBuf::from("."));
let mut engine = RulesEngine::new(config);
if let Some(c) = cache {
engine.set_cache(c);
}
if let Some(only) = args.only {
let valid_only = filter_valid_categories(only);
if !valid_only.is_empty() {
engine.set_only_categories(valid_only);
}
}
if let Some(skip) = args.skip {
let valid_skip = filter_valid_categories(skip);
if !valid_skip.is_empty() {
engine.set_skip_categories(valid_skip);
}
}
let verbose = args.verbose;
engine.set_progress_callback(Box::new(move |category_name, current, total, timing| {
if let Some((findings_count, duration_ms)) = timing {
if verbose >= 1 {
let duration = Duration::from_millis(duration_ms);
let duration_str = format_duration(duration);
eprintln!(
" {} {} ({}/{}) - {} findings ({})",
"✓".green(),
category_name.cyan(),
current,
total,
findings_count,
duration_str.dimmed()
);
}
} else {
if verbose == 0 {
eprintln!(
" {} {} ({}/{})...",
"→".dimmed(),
category_name.cyan(),
current,
total
);
}
}
}));
let (audit_results, timing) = engine.run_with_timing(&scanner).await?;
if let Some(cache) = engine.take_cache() {
if let Err(e) = cache.save() {
eprintln!("{} Failed to save cache: {}", "Warning:".yellow(), e);
}
}
eprintln!(
"{} {} ({})",
"✓".green(),
"Audit completed.".green(),
timing.total_duration_formatted().dimmed()
);
if verbose >= 2 {
eprintln!("\n{}", "Timing breakdown:".dimmed());
for cat_timing in timing.categories() {
eprintln!(
" {} {}: {} findings ({})",
"•".dimmed(),
cat_timing.name.cyan(),
cat_timing.findings_count,
cat_timing.duration_formatted().dimmed()
);
}
eprintln!();
}
let output_path = args.output.clone().unwrap_or_else(|| {
let extension = match args.format {
ReportFormat::Html => "html",
ReportFormat::Markdown => "md",
ReportFormat::Json => "json",
};
PathBuf::from(format!("repolens-report.{extension}"))
});
let renderer: Box<dyn ReportRenderer> = match args.format {
ReportFormat::Html => Box::new(HtmlReport::new(args.detailed)),
ReportFormat::Markdown => Box::new(MarkdownReport::new(args.detailed)),
ReportFormat::Json => Box::new(
JsonOutput::new()
.with_schema(args.schema)
.with_validation(args.validate),
),
};
let report = renderer.render_report(&audit_results)?;
std::fs::write(&output_path, &report).map_err(|e| {
RepoLensError::Action(crate::error::ActionError::FileWrite {
path: output_path.display().to_string(),
source: e,
})
})?;
println!(
"{} Report written to: {}",
"Success:".green().bold(),
output_path.display().to_string().cyan()
);
let exit_code = if audit_results.has_critical() {
exit_codes::CRITICAL_ISSUES
} else if audit_results.has_warnings() {
exit_codes::WARNINGS
} else {
exit_codes::SUCCESS
};
Ok(exit_code)
}