#![allow(
clippy::print_stdout,
clippy::print_stderr,
clippy::exit,
clippy::expect_used
)]
use std::path::PathBuf;
use std::process::ExitCode;
use clap::Parser;
use gts_validator::output;
use gts_validator::{DiscoveryMode, FsSourceConfig, ValidationConfig, VendorPolicy};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[allow(clippy::struct_excessive_bools)]
struct Cli {
#[arg(value_name = "PATH")]
paths: Vec<PathBuf>,
#[arg(long, action = clap::ArgAction::Append)]
vendor: Vec<String>,
#[arg(long, short = 'e', action = clap::ArgAction::Append)]
exclude: Vec<String>,
#[arg(long)]
json: bool,
#[arg(long, short = 'v')]
verbose: bool,
#[arg(long, default_value = "10485760")]
max_file_size: u64,
#[arg(long)]
scan_keys: bool,
#[arg(long)]
strict: bool,
#[arg(long = "skip-token", action = clap::ArgAction::Append)]
skip_tokens: Vec<String>,
}
const DEFAULT_SCAN_DIRS: &[&str] = &["docs", "modules", "libs", "examples"];
fn main() -> ExitCode {
let cli = Cli::parse();
let paths: Vec<PathBuf> = if cli.paths.is_empty() {
DEFAULT_SCAN_DIRS
.iter()
.map(PathBuf::from)
.filter(|path| path.exists())
.collect()
} else {
cli.paths
};
if paths.is_empty() {
eprintln!("No existing paths to scan. Provide paths explicitly.");
return ExitCode::FAILURE;
}
let mut fs_config = FsSourceConfig::default();
fs_config.paths = paths;
fs_config.exclude = cli.exclude;
fs_config.max_file_size = cli.max_file_size;
let mut validation_config = ValidationConfig::default();
validation_config.scan_keys = cli.scan_keys;
validation_config.discovery_mode = if cli.strict {
DiscoveryMode::Heuristic
} else {
DiscoveryMode::StrictSpecOnly
};
validation_config.skip_tokens = cli.skip_tokens;
let vendors: Vec<String> = cli
.vendor
.iter()
.flat_map(|v| v.split(','))
.map(|v| v.trim().to_owned())
.filter(|v| !v.is_empty())
.collect();
validation_config.vendor_policy = match vendors.len() {
0 => VendorPolicy::Any,
1 => VendorPolicy::MustMatch(vendors.into_iter().next().expect("checked len==1")),
_ => VendorPolicy::AllowList(vendors),
};
if cli.verbose {
let path_list: Vec<String> = fs_config
.paths
.iter()
.map(|path| path.display().to_string())
.collect();
eprintln!("Scanning paths: {}", path_list.join(", "));
match &validation_config.vendor_policy {
VendorPolicy::MustMatch(vendor) => eprintln!("Expected vendor: {vendor}"),
VendorPolicy::AllowList(vendors) => {
eprintln!("Allowed vendors: {}", vendors.join(", "));
}
_ => {}
}
}
let report = match gts_validator::validate_fs(&fs_config, &validation_config) {
Ok(report) => report,
Err(error) => {
eprintln!("Error: {error}");
return ExitCode::FAILURE;
}
};
if cli.verbose {
eprintln!("Scanned {} files", report.scanned_files);
}
let mut stdout = std::io::stdout();
let result = if cli.json {
output::write_json(&report, &mut stdout)
} else {
output::write_human(&report, &mut stdout)
};
if let Err(error) = result {
eprintln!("Error writing output: {error}");
return ExitCode::FAILURE;
}
if report.ok {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}