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 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<Vec<Repo>, std::io::Error> {
fn gather_repos(dir: &Path) -> Result<Vec<Repo>, 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 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();
})
.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<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!();
}
}
}