use log::{debug, error, warn};
use rayon::prelude::*;
use std::fs::DirEntry;
use std::path::PathBuf;
use std::{fs, io};
type UnprocessedTarget = io::Result<MaybeTarget>;
pub(crate) struct TargetCollector;
impl TargetCollector {
pub(crate) fn run(path: PathBuf) -> io::Result<Vec<PathBuf>> {
let entries: Vec<DirEntry> = match fs::read_dir(&path) {
Ok(read_dir) => read_dir.filter_map(|r| r.ok()).collect(),
Err(e) => {
match e.kind() {
io::ErrorKind::PermissionDenied => warn!("{}: {}", e, &path.display()),
_ => error!("{}: {}", e, &path.display()),
}
return Ok(Vec::with_capacity(0));
}
};
let unprocessed = entries
.par_iter()
.map(Self::determine_target)
.collect::<Vec<UnprocessedTarget>>();
let mut results = Vec::new();
for entry in unprocessed {
let entry = entry?;
if let MaybeTarget::Multiple(targets) = entry {
results.extend(targets);
} else if let MaybeTarget::Single(target) = entry {
results.push(target);
}
}
Ok(results)
}
fn determine_target(entry: &DirEntry) -> io::Result<MaybeTarget> {
if entry.file_type()?.is_dir()
&& !entry
.file_name()
.to_str()
.is_some_and(|file_name| file_name.starts_with('.'))
{
let path = entry.path();
let git_sub_item = path.join(".git");
if git_sub_item.exists() {
if git_sub_item.is_dir() {
debug!("found target: {:?}", &path.display());
return Ok(MaybeTarget::Single(path));
} else if git_sub_item.is_file() {
debug!("found a worktree: {:?}", &path.display());
return Ok(MaybeTarget::Single(path));
}
}
Ok(MaybeTarget::Multiple(Self::run(path)?))
} else {
Ok(MaybeTarget::None)
}
}
}
#[remain::sorted]
enum MaybeTarget {
Multiple(Vec<PathBuf>),
None,
Single(PathBuf),
}