use clap::Parser; #[derive(Parser)] /// small utility to glance at codebase statistics struct Cli { /// initial directory, defaults to current root: Option, /// exclude files or folders from search #[arg(short, long)] exclude: Vec, } struct CodeStats { most_lines: FileStat, most_characters: FileStat, } struct FileStat { path: std::path::PathBuf, value: T, } fn main() { let args = Cli::parse(); let mut stats = CodeStats { most_lines: FileStat { path: std::path::PathBuf::default(), value: 0 }, most_characters: FileStat { path: std::path::PathBuf::default(), value: 0 }, }; println!("analyzing..."); let root = args.root.unwrap_or(std::env::current_dir().unwrap()); search_recursive(&mut stats, &args.exclude, &root); println!(" > most lines ({}): {}", stats.most_lines.value, stats.most_lines.path.to_string_lossy()); println!(" > most characters ({}): {}", stats.most_characters.value, stats.most_characters.path.to_string_lossy()); } fn search_recursive(stats: &mut CodeStats, excludes: &Vec, root: &std::path::PathBuf) { for dir in std::fs::read_dir(root).unwrap() { match dir { Err(e) => eprintln!("error reading: {e} - {e:?}"), Ok(d) => { if excludes.contains(&d.path().to_string_lossy().to_string()) { continue; } let f_type = d.file_type().unwrap(); if f_type.is_dir() { search_recursive(stats, excludes, &d.path()); } else if f_type.is_file() { analyze_file(stats, &d.path()); } // TODO follow symlinks } } } } fn analyze_file(stats: &mut CodeStats, path: &std::path::PathBuf) { let Ok(content) = std::fs::read_to_string(path) else { return }; let lines = content.lines().count(); if lines > stats.most_lines.value { stats.most_lines = FileStat { path: path.clone(), value: lines }; } let chars = content.chars().count(); if chars > stats.most_characters.value { stats.most_characters = FileStat { path: path.clone(), value: chars }; } }