use std::path::PathBuf;
use std::process::ExitCode;
use clap::Parser;
mod commands;
mod logging;
mod output;
use commands::Commands;
use sara_core::config::{OutputConfig, load_config};
const GLOBAL_OPTIONS: &str = "Global Options";
#[derive(Parser, Debug)]
#[command(name = "sara", version, about, long_about = None, disable_help_flag = true, disable_version_flag = true)]
pub struct Cli {
#[arg(short, long, global = true, default_value = "sara.toml", help_heading = GLOBAL_OPTIONS)]
pub config: PathBuf,
#[arg(short, long, action = clap::ArgAction::Help, global = true, help_heading = GLOBAL_OPTIONS)]
help: Option<bool>,
#[arg(long, global = true, help_heading = GLOBAL_OPTIONS)]
pub no_color: bool,
#[arg(long, global = true, help_heading = GLOBAL_OPTIONS)]
pub no_emoji: bool,
#[arg(short, long, global = true, help_heading = GLOBAL_OPTIONS)]
pub quiet: bool,
#[arg(short, long, global = true, action = clap::ArgAction::Count, help_heading = GLOBAL_OPTIONS)]
pub verbose: u8,
#[arg(short, long, global = true, help_heading = GLOBAL_OPTIONS)]
pub repository: Vec<PathBuf>,
#[arg(short = 'V', long, action = clap::ArgAction::Version, help_heading = GLOBAL_OPTIONS)]
version: Option<bool>,
#[command(subcommand)]
pub command: Commands,
}
impl Cli {
pub fn verbosity(&self) -> logging::Verbosity {
if self.quiet {
logging::Verbosity::Quiet
} else {
match self.verbose {
0 => logging::Verbosity::Normal,
1 => logging::Verbosity::Verbose,
2 => logging::Verbosity::Debug,
_ => logging::Verbosity::Trace,
}
}
}
pub fn output_config(&self, file_config: Option<&sara_core::config::Config>) -> OutputConfig {
let env_no_color = std::env::var("NO_COLOR").is_ok();
let (config_colors, config_emojis) = file_config
.map(|c| (c.output.colors, c.output.emojis))
.unwrap_or((true, true));
let colors = config_colors && !self.no_color && !env_no_color;
let emojis = config_emojis && !self.no_emoji;
OutputConfig { colors, emojis }
}
pub fn config_path(&self) -> PathBuf {
if let Ok(env_config) = std::env::var("SARA_CONFIG") {
PathBuf::from(env_config)
} else {
self.config.clone()
}
}
pub fn repositories(&self) -> Result<Vec<PathBuf>, std::io::Error> {
if self.repository.is_empty() {
Ok(vec![std::env::current_dir()?])
} else {
Ok(self.repository.clone())
}
}
}
fn main() -> ExitCode {
let cli = Cli::parse();
logging::init(cli.verbosity());
let config_path = cli.config_path();
let file_config = if config_path.exists() {
match load_config(&config_path) {
Ok(config) => Some(config),
Err(e) => {
tracing::warn!(
"Failed to load config file {}: {}",
config_path.display(),
e
);
None
}
}
} else {
None
};
let result = commands::run(&cli, file_config.as_ref());
match result {
Ok(code) => code,
Err(e) => {
let config = cli.output_config(file_config.as_ref());
output::print_error(&config, &format!("{}", e));
ExitCode::from(1)
}
}
}