use colored::Colorize;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::ExitCode;
use super::commands::Cli;
use linthis::ai::provider::{
detect_available_providers, try_fallback_provider, AiProviderKind, ALL_AI_PROVIDERS,
};
use linthis::LintIssue;
pub fn run_benchmark(cli: &Cli) -> ExitCode {
use linthis::benchmark::{format_benchmark_table, run_python_benchmark};
use linthis::utils::walker::{walk_paths, WalkerConfig};
use linthis::Language;
println!(
"{}",
"Running Python linting/formatting benchmark...".cyan()
);
println!("Comparing ruff vs flake8+black\n");
let paths = if cli.paths.is_empty() {
vec![PathBuf::from(".")]
} else {
cli.paths.clone()
};
let walker_config = WalkerConfig {
exclude_patterns: cli.exclude.clone().unwrap_or_default(),
languages: vec![Language::Python],
..Default::default()
};
let (files, _) = walk_paths(&paths, &walker_config);
if files.is_empty() {
println!("{}", "No Python files found to benchmark.".yellow());
return ExitCode::SUCCESS;
}
println!("Found {} Python files", files.len());
let file_refs: Vec<&std::path::Path> = files.iter().map(|p| p.as_path()).collect();
let comparison = run_python_benchmark(&file_refs);
println!("{}", format_benchmark_table(&comparison));
ExitCode::SUCCESS
}
pub fn strip_ansi_codes(s: &str) -> String {
let ansi_regex = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap();
ansi_regex.replace_all(s, "").to_string()
}
pub fn find_latest_result_file() -> Option<PathBuf> {
use std::fs;
let project_root = linthis::utils::get_project_root();
let result_dir = project_root.join(".linthis").join("result");
if !result_dir.exists() {
return None;
}
let entries = fs::read_dir(&result_dir).ok()?;
let mut result_files: Vec<_> = entries
.filter_map(|e| e.ok())
.filter(|e| {
let name = e.file_name().to_string_lossy().to_string();
name.starts_with("result-") && (name.ends_with(".json") || name.ends_with(".txt"))
})
.collect();
if result_files.is_empty() {
return None;
}
result_files.sort_by(|a, b| {
let a_time = a.metadata().and_then(|m| m.modified()).ok();
let b_time = b.metadata().and_then(|m| m.modified()).ok();
b_time.cmp(&a_time)
});
Some(result_files[0].path())
}
pub fn resolve_ai_provider(cli_value: Option<&str>, config_value: Option<&str>) -> String {
let resolved = if let Some(value) = cli_value {
value.to_string()
} else if let Ok(value) = std::env::var("LINTHIS_AI_PROVIDER") {
if !value.is_empty() {
value
} else {
resolve_default_provider(config_value)
}
} else {
resolve_default_provider(config_value)
};
if let Ok(kind) = resolved.parse::<AiProviderKind>() {
if let Some((fallback_kind, message)) = try_fallback_provider(&kind) {
eprintln!("{} {}", "âš Warning:".yellow().bold(), message);
return fallback_kind.cli_name().to_string();
}
}
resolved
}
fn resolve_default_provider(config_value: Option<&str>) -> String {
if let Some(value) = config_value {
if !value.is_empty() {
return value.to_string();
}
}
"claude".to_string()
}
pub fn print_fix_hint(issues: &[LintIssue]) {
let clang_tidy_count = issues
.iter()
.filter(|i| i.source.as_deref() == Some("clang-tidy"))
.count();
if clang_tidy_count >= 10 {
eprintln!();
eprintln!(
" {} Found {} clang-tidy issues. To skip clang-tidy checks:",
"Tip:".yellow().bold(),
clang_tidy_count,
);
eprintln!(" {}", "LINTHIS_SKIP_CLANG_TIDY=1 linthis".yellow());
}
eprintln!();
eprintln!(
" {} To review and fix issues:",
"Tip:".cyan().bold()
);
for (cmd, desc) in linthis::utils::output::fix_tip_lines() {
eprintln!(" {:<36} : {}", cmd.cyan(), desc);
}
}
pub fn select_ai_provider_interactive() -> Option<String> {
let providers = detect_available_providers();
let mut ordered: Vec<_> = providers.iter().filter(|(_, avail)| *avail).collect();
let unavailable: Vec<_> = providers.iter().filter(|(_, avail)| !*avail).collect();
ordered.extend(unavailable);
eprintln!("{}", "Select AI provider:".cyan().bold());
eprintln!();
for (i, (kind, available)) in ordered.iter().enumerate() {
let (_, name, desc) = ALL_AI_PROVIDERS.iter().find(|(k, _, _)| k == kind).unwrap();
if *available {
eprintln!(
" {} {}. {} - {}{}",
"\u{2713}".green(),
i + 1,
name,
desc,
" (available)".cyan()
);
} else {
eprintln!(" {}. {} - {}", i + 1, name, desc);
}
}
let cancel_num = ordered.len() + 1;
eprintln!(" {}. Cancel", cancel_num);
eprintln!();
eprint!("Choose [1]: ");
io::stderr().flush().ok();
let mut choice = String::new();
io::stdin().read_line(&mut choice).ok();
let choice = choice.trim();
let num: usize = if choice.is_empty() {
1
} else if let Ok(n) = choice.parse() {
n
} else {
return None;
};
if num == cancel_num || num == 0 {
return None;
}
if num >= 1 && num <= ordered.len() {
let (kind, _) = ordered[num - 1];
let (_, name, _) = ALL_AI_PROVIDERS.iter().find(|(k, _, _)| k == kind).unwrap();
Some(name.to_string())
} else {
None
}
}