diff --git a/src/main.rs b/src/main.rs index 5b77d25..504e5ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use clap::{Parser, Subcommand}; @@ -14,7 +15,7 @@ struct Args { struct Repo { path: PathBuf, - remotes: Vec<(String, String)> + remotes: Vec<(String, String)>, } #[derive(Subcommand, Debug)] @@ -24,18 +25,22 @@ enum Command { /// Directory to start from #[arg(short, long)] directory: PathBuf, + + /// Print repositories flat rather than indentend + #[arg(short, long)] + flat: bool, }, } fn main() -> Result<(), std::io::Error> { let args = Args::parse(); match args.command { - Command::List { directory } => list_repos(directory)?, + Command::List { directory, flat } => list_repos(directory, flat)?, } Ok(()) } -fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> { +fn list_repos(directory: PathBuf, flat: bool) -> Result<(), std::io::Error> { use std::fs; let start = fs::canonicalize(directory)?; @@ -45,7 +50,7 @@ fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> { ); println!(); - fn gather_repos(dir: &Path, recurse_level: usize) -> Result, std::io::Error> { + fn gather_repos(dir: &Path) -> Result, std::io::Error> { let mut repos = Vec::new(); let dir = fs::read_dir(dir)?; @@ -62,41 +67,36 @@ fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> { if let Ok(repository) = Repository::open(&path) { let remotes_array = repository.remotes().unwrap(); - let remotes = remotes_array.into_iter().map(|remote_name| { - let name = remote_name.unwrap(); - let remote = repository.find_remote(name).unwrap(); - let url = remote.url().unwrap(); + let remotes = remotes_array + .into_iter() + .map(|remote_name| { + let name = remote_name.unwrap(); + let remote = repository.find_remote(name).unwrap(); + let url = remote.url().unwrap(); - (name.to_string(), url.to_string()) - }).collect(); + (name.to_string(), url.to_string()) + }) + .collect(); - let repo = Repo { - path, - remotes, - }; + let repo = Repo { path, remotes }; repos.push(repo); } else if path.is_dir() && !hidden { - repos.extend(gather_repos(&path, recurse_level + 1)?.into_iter()); + repos.extend(gather_repos(&path)?.into_iter()); } } Ok(repos) } - let mut repos = gather_repos(&start, 0)?; - repos.sort_unstable_by_key(|repo| repo.path.clone()); - for repo in &repos { - let path = repo.path.strip_prefix(&start).unwrap(); - print!("Repository: {}", path.display().to_string().yellow()); + let repos = { + let mut r = gather_repos(&start)?; + r.sort_unstable_by_key(|repo| repo.path.clone()); + r + }; - print!(" "); - for (name, url) in &repo.remotes { - print!("[{name} {url}]"); - } - println!(); - /* - let indent = recurse_level * INDENT_INCREMENT; - print!("{: <1$}", "", indent); - */ + if flat { + print_flat(&repos, &start); + } else { + print_indented(&repos, &start); } println!(); @@ -104,3 +104,66 @@ fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> { Ok(()) } + +fn print_flat(repos: &[Repo], start: &Path) { + for repo in repos { + let path = repo.path.strip_prefix(start).unwrap(); + println!("{}", path.display().to_string().yellow()); + } +} + +fn print_indented(repos: &[Repo], start: &Path) { + const INDENT_SIZE: usize = 4; + + // Create a nested structure with proper path components + let mut path_structure: BTreeMap, Vec<&Repo>> = BTreeMap::new(); + + for repo in repos { + let path = repo.path.strip_prefix(start).unwrap(); + + let components: Vec = path + .parent() + .map(|p| { + p.components() + .map(|c| c.as_os_str().to_string_lossy().to_string()) + .collect() + }) + .unwrap_or_else(Vec::new); + + path_structure.entry(components).or_default().push(repo); + } + + // Print hierarchical structure with proper indentation + fn print_path(components: &[String], indent_level: usize, indent_size: usize) { + if components.is_empty() { + println!("."); + return; + } + + let indent = " ".repeat(indent_level * indent_size); + let line = if indent_level == 0 { "" } else { "└──" }; + println!("{indent}{line}{}", components.last().unwrap().white()); + } + + for (path_components, dir_repos) in &path_structure { + // Print the path with proper indentation + print_path( + path_components, + path_components.len().saturating_sub(1), + INDENT_SIZE, + ); + + // Print repositories with one more level of indentation + let repo_indent = " ".repeat(path_components.len() * INDENT_SIZE); + for repo in dir_repos { + let repo_name = repo.path.file_name().unwrap().to_string_lossy(); + print!("{}└── {}", repo_indent, repo_name.yellow()); + + print!(" "); + for (name, url) in &repo.remotes { + print!("[{name} {url}]"); + } + println!(); + } + } +}