mod argp_version; mod cmd; mod util; use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, str::FromStr}; use anyhow::{Error, Result}; use argp::{FromArgValue, FromArgs}; use enable_ansi_support::enable_ansi_support; use supports_color::Stream; use tracing_subscriber::{filter::LevelFilter, EnvFilter}; #[derive(Debug, Eq, PartialEq, Copy, Clone)] enum LogLevel { Error, Warn, Info, Debug, Trace, } impl FromStr for LogLevel { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "error" => Self::Error, "warn" => Self::Warn, "info" => Self::Info, "debug" => Self::Debug, "trace" => Self::Trace, _ => return Err(()), }) } } impl Display for LogLevel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { LogLevel::Error => "error", LogLevel::Warn => "warn", LogLevel::Info => "info", LogLevel::Debug => "debug", LogLevel::Trace => "trace", }) } } impl FromArgValue for LogLevel { fn from_arg_value(value: &OsStr) -> Result { String::from_arg_value(value) .and_then(|s| Self::from_str(&s).map_err(|_| "Invalid log level".to_string())) } } #[derive(FromArgs, PartialEq, Debug)] /// Yet another GameCube/Wii decompilation toolkit. struct TopLevel { #[argp(subcommand)] command: SubCommand, #[argp(option, short = 'C')] /// Change working directory. chdir: Option, #[argp(option, short = 'L')] /// Minimum logging level. (Default: info) /// Possible values: error, warn, info, debug, trace log_level: Option, /// Print version information and exit. #[argp(switch, short = 'V')] version: bool, /// Disable color output. (env: NO_COLOR) #[argp(switch)] no_color: bool, } #[derive(FromArgs, PartialEq, Debug)] #[argp(subcommand)] enum SubCommand { Diff(cmd::diff::Args), Report(cmd::report::Args), } // Duplicated from supports-color so we can check early. fn env_no_color() -> bool { match env::var("NO_COLOR").as_deref() { Ok("") | Ok("0") | Err(_) => false, Ok(_) => true, } } fn main() { let args: TopLevel = argp_version::from_env(); let use_colors = if args.no_color || env_no_color() { false } else { // Try to enable ANSI support on Windows. let _ = enable_ansi_support(); // Disable isatty check for supports-color. (e.g. when used with ninja) unsafe { env::set_var("IGNORE_IS_TERMINAL", "1") }; supports_color::on(Stream::Stdout).is_some_and(|c| c.has_basic) }; let format = tracing_subscriber::fmt::format().with_ansi(use_colors).with_target(false).without_time(); let builder = tracing_subscriber::fmt().event_format(format).with_writer(std::io::stderr); if let Some(level) = args.log_level { builder .with_max_level(match level { LogLevel::Error => LevelFilter::ERROR, LogLevel::Warn => LevelFilter::WARN, LogLevel::Info => LevelFilter::INFO, LogLevel::Debug => LevelFilter::DEBUG, LogLevel::Trace => LevelFilter::TRACE, }) .init(); } else { builder .with_env_filter( EnvFilter::builder() .with_default_directive(LevelFilter::INFO.into()) .from_env_lossy(), ) .init(); } let mut result = Ok(()); if let Some(dir) = &args.chdir { result = env::set_current_dir(dir).map_err(|e| { Error::new(e) .context(format!("Failed to change working directory to '{}'", dir.display())) }); } result = result.and_then(|_| match args.command { SubCommand::Diff(c_args) => cmd::diff::run(c_args), SubCommand::Report(c_args) => cmd::report::run(c_args), }); if let Err(e) = result { eprintln!("Failed: {e:?}"); std::process::exit(1); } }