Print flat or indented

This commit is contained in:
Greg Shuflin
2025-05-19 22:24:01 -07:00
parent c7be05cfcc
commit 7e52339745

View File

@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@@ -14,7 +15,7 @@ struct Args {
struct Repo { struct Repo {
path: PathBuf, path: PathBuf,
remotes: Vec<(String, String)> remotes: Vec<(String, String)>,
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
@@ -24,18 +25,22 @@ enum Command {
/// Directory to start from /// Directory to start from
#[arg(short, long)] #[arg(short, long)]
directory: PathBuf, directory: PathBuf,
/// Print repositories flat rather than indentend
#[arg(short, long)]
flat: bool,
}, },
} }
fn main() -> Result<(), std::io::Error> { fn main() -> Result<(), std::io::Error> {
let args = Args::parse(); let args = Args::parse();
match args.command { match args.command {
Command::List { directory } => list_repos(directory)?, Command::List { directory, flat } => list_repos(directory, flat)?,
} }
Ok(()) Ok(())
} }
fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> { fn list_repos(directory: PathBuf, flat: bool) -> Result<(), std::io::Error> {
use std::fs; use std::fs;
let start = fs::canonicalize(directory)?; let start = fs::canonicalize(directory)?;
@@ -45,7 +50,7 @@ fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> {
); );
println!(); println!();
fn gather_repos(dir: &Path, recurse_level: usize) -> Result<Vec<Repo>, std::io::Error> { fn gather_repos(dir: &Path) -> Result<Vec<Repo>, std::io::Error> {
let mut repos = Vec::new(); let mut repos = Vec::new();
let dir = fs::read_dir(dir)?; 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) { if let Ok(repository) = Repository::open(&path) {
let remotes_array = repository.remotes().unwrap(); let remotes_array = repository.remotes().unwrap();
let remotes = remotes_array.into_iter().map(|remote_name| { let remotes = remotes_array
.into_iter()
.map(|remote_name| {
let name = remote_name.unwrap(); let name = remote_name.unwrap();
let remote = repository.find_remote(name).unwrap(); let remote = repository.find_remote(name).unwrap();
let url = remote.url().unwrap(); let url = remote.url().unwrap();
(name.to_string(), url.to_string()) (name.to_string(), url.to_string())
}).collect(); })
.collect();
let repo = Repo { let repo = Repo { path, remotes };
path,
remotes,
};
repos.push(repo); repos.push(repo);
} else if path.is_dir() && !hidden { } 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) Ok(repos)
} }
let mut repos = gather_repos(&start, 0)?; let repos = {
repos.sort_unstable_by_key(|repo| repo.path.clone()); let mut r = gather_repos(&start)?;
for repo in &repos { r.sort_unstable_by_key(|repo| repo.path.clone());
let path = repo.path.strip_prefix(&start).unwrap(); r
print!("Repository: {}", path.display().to_string().yellow()); };
print!(" "); if flat {
for (name, url) in &repo.remotes { print_flat(&repos, &start);
print!("[{name} {url}]"); } else {
} print_indented(&repos, &start);
println!();
/*
let indent = recurse_level * INDENT_INCREMENT;
print!("{: <1$}", "", indent);
*/
} }
println!(); println!();
@@ -104,3 +104,66 @@ fn list_repos(directory: PathBuf) -> Result<(), std::io::Error> {
Ok(()) 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<String>, Vec<&Repo>> = BTreeMap::new();
for repo in repos {
let path = repo.path.strip_prefix(start).unwrap();
let components: Vec<String> = 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!();
}
}
}