diff --git a/Cargo.lock b/Cargo.lock index 1d4e35e..a2e0814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -140,6 +140,46 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "colorchoice" version = "1.0.1" @@ -192,6 +232,29 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -244,6 +307,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "humantime" version = "2.1.0" @@ -294,6 +363,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.2" @@ -616,6 +691,12 @@ dependencies = [ "anstream", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "supports-color" version = "3.0.0" @@ -865,7 +946,10 @@ name = "unicop" version = "0.1.0" dependencies = [ "anyhow", + "clap", + "env_logger", "glob", + "log", "miette", "phf", "serde", diff --git a/Cargo.toml b/Cargo.toml index b886adc..2501db8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,9 @@ anyhow = "1.0.86" glob = "0.3.1" phf = { version = "0.11.2", features = ["macros"] } tree-sitter-rust = "0.21.2" +clap = { version = "4.5.16", features = ["derive"] } +log = "0.4.22" +env_logger = "0.11.5" [dev-dependencies] trycmd = "0.15.5" diff --git a/README.md b/README.md index 7502b4f..ae214e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ unicop [FILES]... ``` -Where `[FILES]...` is a list of files or directory to check, default: `.`. +Where `[FILES]...` is a list of files or directory to check. ## Example diff --git a/src/main.rs b/src/main.rs index ccca318..dca313d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; -use std::env; use std::fs; use std::io; use std::path::Path; +use std::path::PathBuf; use anyhow::Context; +use clap::Parser; use config::CodeType; use config::Config; use config::Language; @@ -105,24 +106,33 @@ impl RuleDispatcher { } } +#[derive(Debug, clap::Parser)] +#[command(arg_required_else_help = true)] +struct Args { + /// One or more files or directories to scan. Directories are scanned recursively. + paths: Vec, +} + fn main() -> anyhow::Result<()> { - let mut args: Vec = env::args().skip(1).collect(); - if args.is_empty() { - args = vec![String::from(".")] - } + env_logger::init(); + + let args = Args::parse(); let default_config = get_default_config(); - let user_config = get_user_config()?; - let dispatcher = RuleDispatcher { - user_config, + let mut dispatcher = RuleDispatcher { + user_config: None, default_config, }; - for arg in args { - for entry in walkdir::WalkDir::new(arg) { + for path in args.paths { + for entry in walkdir::WalkDir::new(path) { match entry { Err(err) => eprintln!("{:}", err), - Ok(entry) if entry.file_type().is_file() => check_file(&dispatcher, entry.path()), + Ok(entry) if entry.file_type().is_file() => { + let entry_path = entry.path(); + dispatcher.user_config = get_user_config(entry_path)?; + check_file(&dispatcher, entry_path); + } Ok(_) => {} } } @@ -178,11 +188,39 @@ fn check_file(dispatcher: &RuleDispatcher, path: &Path) { } } -fn get_user_config() -> anyhow::Result> { - match std::fs::read_to_string("./unicop.toml") { - Ok(config_str) => toml::from_str(&config_str).context("Failed to parse config"), - Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), - Err(e) => Err(e).context("Failed to read config file"), +fn get_user_config(path: &Path) -> anyhow::Result> { + let absolute_path = path + .canonicalize() + .with_context(|| format!("Failed to resolve absolute path for {}", path.display()))?; + let mut config_dir = if absolute_path.is_file() { + // If scanning a file, then check for the config file in the same directory. + absolute_path.parent().unwrap() + } else { + // And if scanning a dir, look for the config file in the dir. + &absolute_path + }; + + loop { + let config_path = config_dir.join("unicop.toml"); + + match std::fs::read_to_string(&config_path) { + Ok(config_str) => { + log::debug!( + "Using config {} for scan path {}", + config_path.display(), + absolute_path.display() + ); + break toml::from_str(&config_str).context("Failed to parse config"); + } + Err(e) if e.kind() == io::ErrorKind::NotFound => match config_dir.parent() { + Some(parent_dir) => config_dir = parent_dir, + None => { + log::debug!("No user config for scan path {}", absolute_path.display()); + break Ok(None); + } + }, + Err(e) => break Err(e).context("Failed to read config file"), + } } }