#![deny(clippy::pedantic)]
use clap::{Parser, Subcommand};
use console::style;
use std::fs;
use std::path::{Path, PathBuf};
use swt::Config;
use swt::analyzer::AnalysisEngine;
const ASCII: &str = r"
__
______ _____ ___ / /_
/ ___/ | /| / / _ \/ _ \/ __/
/__ /| |/ |/ / __/ __/ /_
/____/ |__/|__/\___/\___/\__/ ";
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Option<Commands>,
#[arg(default_value = ".")]
path: PathBuf,
#[allow(clippy::option_option)]
#[arg(long, value_name = "FILE")]
json: Option<Option<PathBuf>>,
#[arg(short, long)]
quiet: bool,
}
#[derive(Subcommand, Debug)]
enum Commands {
Update,
CheckUpdates,
Inspect {
#[arg(default_value = ".")]
path: PathBuf,
},
Uncomment {
#[arg(value_name = "FILE")]
path: PathBuf,
#[arg(long, short)]
aggressive: bool,
},
}
fn main() -> std::process::ExitCode {
let args = Args::parse();
match args.command {
Some(Commands::Update) => {
return match swt::update::handle_update() {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(_) => std::process::ExitCode::FAILURE,
};
}
Some(Commands::CheckUpdates) => {
swt::update::check_for_updates();
return std::process::ExitCode::SUCCESS;
}
Some(Commands::Inspect { path }) => {
return run_analysis(&path, args.json.as_ref(), args.quiet, true);
}
Some(Commands::Uncomment { path, aggressive }) => {
if handle_uncomment(&path, aggressive) {
return std::process::ExitCode::SUCCESS;
}
return std::process::ExitCode::FAILURE;
}
None => {}
}
run_analysis(&args.path, args.json.as_ref(), args.quiet, false)
}
fn run_analysis(
path: &Path,
#[allow(clippy::option_option)] json: Option<&Option<PathBuf>>,
quiet: bool,
inspect: bool,
) -> std::process::ExitCode {
let config = Config::load(path);
let engine = AnalysisEngine::new(path.to_path_buf(), config);
if !quiet && json.is_none() {
show_branding();
}
let reports = engine.run(quiet, json.is_none(), inspect);
if reports.is_empty() {
if !quiet {
println!(
"\n{}",
style(" 📭 No supported files found.").yellow().bold()
);
}
return std::process::ExitCode::SUCCESS;
}
let bitter_count = reports.iter().filter(|r| !r.is_sweet).count();
if let Some(json_opt) = json {
handle_json_reporting(&reports, json_opt.as_ref());
} else {
swt::report::print_reports(&reports, quiet, None);
}
if bitter_count > 0 {
std::process::ExitCode::FAILURE
} else {
std::process::ExitCode::SUCCESS
}
}
fn handle_json_reporting(reports: &[swt::FileReport], json_opt: Option<&PathBuf>) {
if let Some(path) = json_opt {
swt::report::json::write_json_report(reports, path);
} else if let Ok(json) = serde_json::to_string_pretty(&reports) {
println!("{json}");
}
}
fn handle_uncomment(path: &Path, aggressive: bool) -> bool {
match fs::read_to_string(path) {
Ok(content) => {
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");
let clean = swt::analyzer::uncomment::remove_comments(&content, extension, aggressive);
if fs::write(path, clean).is_ok() {
println!("{}", style("Uncommented!").cyan().bold());
true
} else {
eprintln!("{}", style("Error: Could not write to file").red());
false
}
}
Err(e) => {
eprintln!("Error: Could not read file {}: {}", path.display(), e);
false
}
}
}
fn show_branding() {
println!("{}", style(ASCII).magenta().bold());
println!(
"\n{}",
style("— A blazing-fast code health analyzer :)")
.italic()
.cyan()
);
}