use crate::{plan_types::ItemKind, FrenError, PlanOpts};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub(crate) struct DiscoveredItem {
pub path: PathBuf,
pub kind: ItemKind,
pub depth: usize,
}
pub fn walk(root: &Path, opts: &PlanOpts) -> Result<Vec<DiscoveredItem>, FrenError> {
let mut out = Vec::new();
walk_inner(root, root, 0, opts, &mut out)?;
Ok(out)
}
fn walk_inner(
root: &Path,
current: &Path,
depth: usize,
opts: &PlanOpts,
out: &mut Vec<DiscoveredItem>,
) -> Result<(), FrenError> {
let metadata = std::fs::symlink_metadata(current).map_err(|source| FrenError::Io {
path: current.to_path_buf(),
source,
})?;
let kind = if metadata.file_type().is_symlink() {
ItemKind::Symlink
} else if metadata.is_dir() {
ItemKind::Dir
} else {
ItemKind::File
};
out.push(DiscoveredItem {
path: current.to_path_buf(),
kind,
depth,
});
if kind != ItemKind::Dir {
return Ok(());
}
if !opts.recursive && depth > 0 {
return Ok(());
}
let entries = std::fs::read_dir(current).map_err(|source| FrenError::Io {
path: current.to_path_buf(),
source,
})?;
for entry in entries {
let entry = entry.map_err(|source| FrenError::Io {
path: current.to_path_buf(),
source,
})?;
let child_path = entry.path();
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.starts_with('.') {
continue;
}
if opts.exclude.iter().any(|excl| child_path.starts_with(excl)) {
continue;
}
let child_meta = entry.file_type().map_err(|source| FrenError::Io {
path: child_path.clone(),
source,
})?;
if child_meta.is_symlink() {
out.push(DiscoveredItem {
path: child_path,
kind: ItemKind::Symlink,
depth: depth + 1,
});
continue;
}
let _ = root; walk_inner(root, &child_path, depth + 1, opts, out)?;
}
Ok(())
}