use clap::Parser; use status::ResultExt; use std::path::{Path, PathBuf}; use xatu::SourceFile; fn is_nix_file(path: &Path) -> bool { path.extension().is_some_and(|extension| extension == "nix") } // TODO: Add configurable exclusions. fn is_excluded_dir(path: &Path) -> bool { path.file_name() .and_then(|name| name.to_str()) .is_some_and(|name| { matches!( name, ".git" | "bazel-bin" | "bazel-dev" | "bazel-out" | "bazel-testlogs" | "target" | "third_party" ) || name == "result" || name.starts_with("result-") }) } fn collect_source_files(path: &Path) -> status::Result> { let metadata = std::fs::symlink_metadata(path) .with_context(|| format!("failed to read metadata for {}", path.display()))?; if metadata.file_type().is_dir() { if is_excluded_dir(path) { return Ok(Vec::new()); } let mut files = Vec::new(); for entry in std::fs::read_dir(path) .with_context(|| format!("failed to read directory {}", path.display()))? { let entry = entry.with_context(|| format!("failed to read entry in {}", path.display()))?; files.extend(collect_source_files(&entry.path())?); } return Ok(files); } if metadata.file_type().is_file() && is_nix_file(path) { return Ok(vec![SourceFile { path: path.to_path_buf(), source: std::fs::read_to_string(path) .with_context(|| format!("failed to read source file {}", path.display()))?, }]); } Ok(Vec::new()) } #[derive(Debug, Parser)] struct Args { #[arg(value_name = "PATH", default_value = ".")] paths: Vec, } fn main() -> status::Result<()> { let args = Args::parse(); let paths: Vec = args .paths .into_iter() .map(monorepo::workspace::resolve_workspace_path) .collect(); let files: Vec = paths .iter() .map(|path| collect_source_files(path)) .collect::>>()? .into_iter() .flatten() .collect(); xatu::run(files) }