use anyhow::Result;
use clap::{Parser, Subcommand};
use supercli::clap::create_help_styles;
fn format_version() -> &'static str {
const VERSION: &str = env!("CARGO_PKG_VERSION");
const GIT_SHA: &str = env!("GIT_SHA");
Box::leak(format!("{} ({})", VERSION, GIT_SHA).into_boxed_str())
}
pub mod config;
pub mod hooks;
pub mod scan;
pub mod status;
pub mod sync;
pub mod version;
#[derive(Parser)]
#[command(
name = "guardy",
version = format_version(),
about = "Fast, secure git hooks in Rust with secret scanning and file synchronization",
long_about = "Guardy provides native Rust implementations of git hooks with security scanning \
and protected file synchronization across repositories.",
styles = create_help_styles()
)]
pub struct Cli {
#[arg(short = 'C', long = "directory", global = true)]
pub directory: Option<String>,
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
pub verbose: u8,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub config: Option<String>,
#[arg(long, global = true)]
pub recursive_config: Option<bool>,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand)]
pub enum Commands {
Hooks(hooks::HooksArgs),
Scan(scan::ScanArgs),
Config(config::ConfigArgs),
Status(status::StatusArgs),
Sync(sync::SyncArgs),
Version(version::VersionArgs),
}
impl Cli {
pub async fn run(&self) -> Result<()> {
if let Some(dir) = &self.directory {
std::env::set_current_dir(dir)?;
}
let early_verbose = detect_early_verbosity(self);
setup_logging(early_verbose, self.quiet);
match &self.command {
Some(Commands::Hooks(args)) => hooks::execute(args.clone()).await,
Some(Commands::Scan(args)) => {
scan::execute(args.clone(), crate::config::CONFIG.general.verbose).await
}
Some(Commands::Config(args)) => {
config::execute(args.clone(), self.config.as_deref(), self.verbose).await
}
Some(Commands::Status(args)) => status::execute(args.clone()).await,
Some(Commands::Sync(args)) => sync::execute(args.clone()).await,
Some(Commands::Version(args)) => version::execute(args.clone()).await,
None => {
if crate::git::GitRepo::discover().is_ok() {
status::execute(status::StatusArgs::default()).await
} else {
println!("Run 'guardy --help' for usage information");
Ok(())
}
}
}
}
}
fn detect_early_verbosity(cli: &Cli) -> u8 {
if cli.verbose > 0 {
return cli.verbose;
}
if let Ok(env_verbose) = std::env::var("GUARDY_VERBOSE")
&& let Ok(val) = env_verbose.parse::<u8>()
{
return val;
}
0
}
fn setup_logging(verbose: u8, quiet: bool) {
if quiet {
return;
}
let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
match verbose {
0 => tracing_subscriber::EnvFilter::new("warn"),
1 => tracing_subscriber::EnvFilter::new("info,ignore=warn,globset=warn"),
2 => tracing_subscriber::EnvFilter::new("debug,ignore=warn,globset=warn"),
_ => tracing_subscriber::EnvFilter::new("trace"),
}
});
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_target(false)
.with_file(true)
.with_line_number(true)
.compact()
.init();
}