use std::path::{Path, PathBuf};
use haz_domain::path::{HazPath, PathAnchor, PathSegment, ProjectRoot};
use haz_vfs::{DirEntry, EntryKind, Filesystem, FsError};
pub(crate) trait GlobMatchAction<F: Filesystem> {
type Output;
type Error;
fn map_walk_error(&self, root: PathBuf, source: FsError) -> Self::Error;
fn on_match(
&self,
fs: &F,
host_path: &Path,
workspace_absolute_path: String,
out: &mut Vec<Self::Output>,
) -> Result<(), Self::Error>;
}
pub(crate) struct GlobWalk<'a, F: Filesystem, A: GlobMatchAction<F>> {
pub fs: &'a F,
pub matcher: &'a globset::GlobMatcher,
pub candidate_prefix: &'static str,
pub workspace_prefix: String,
pub action: &'a A,
}
impl<F: Filesystem, A: GlobMatchAction<F>> GlobWalk<'_, F, A> {
pub fn walk(
&self,
walk_dir: &Path,
walk_rel: &mut Vec<String>,
out: &mut Vec<A::Output>,
) -> Result<(), A::Error> {
let entries = self
.fs
.read_dir(walk_dir)
.map_err(|source| self.action.map_walk_error(walk_dir.to_path_buf(), source))?;
for entry in entries {
let Some(name) = entry
.path
.file_name()
.and_then(|n| n.to_str())
.map(str::to_owned)
else {
continue;
};
walk_rel.push(name);
let r = self.visit_entry(&entry, walk_rel, out);
walk_rel.pop();
r?;
}
Ok(())
}
fn visit_entry(
&self,
entry: &DirEntry,
walk_rel: &mut Vec<String>,
out: &mut Vec<A::Output>,
) -> Result<(), A::Error> {
match entry.metadata.kind {
EntryKind::Dir => self.walk(&entry.path, walk_rel, out),
EntryKind::File => self.maybe_match_file(&entry.path, walk_rel, out),
EntryKind::Symlink => {
let target_meta = self
.fs
.metadata(&entry.path)
.map_err(|source| self.action.map_walk_error(entry.path.clone(), source))?;
match target_meta.kind {
EntryKind::Dir => self.walk(&entry.path, walk_rel, out),
EntryKind::File => self.maybe_match_file(&entry.path, walk_rel, out),
EntryKind::Symlink
| EntryKind::BlockDevice
| EntryKind::CharDevice
| EntryKind::Fifo
| EntryKind::Socket => Ok(()),
}
}
EntryKind::BlockDevice
| EntryKind::CharDevice
| EntryKind::Fifo
| EntryKind::Socket => Ok(()),
}
}
fn maybe_match_file(
&self,
host_path: &Path,
walk_rel: &[String],
out: &mut Vec<A::Output>,
) -> Result<(), A::Error> {
let candidate = format!("{}{}", self.candidate_prefix, walk_rel.join("/"));
if !self.matcher.is_match(&candidate) {
return Ok(());
}
let workspace_absolute_path = format!("{}/{}", self.workspace_prefix, walk_rel.join("/"));
self.action
.on_match(self.fs, host_path, workspace_absolute_path, out)
}
}
pub(crate) fn literal_workspace_segments<'a>(
haz_path: &'a HazPath,
project_root: &'a ProjectRoot,
) -> Vec<&'a PathSegment> {
match (haz_path, project_root) {
(HazPath::WorkspaceAbsolute(segs), _)
| (HazPath::ProjectRelative(segs), ProjectRoot::WorkspaceRoot) => segs.iter().collect(),
(HazPath::ProjectRelative(rel), ProjectRoot::Nested(cp)) => {
let mut v: Vec<&PathSegment> = cp.segments().iter().collect();
v.extend(rel.iter());
v
}
}
}
pub(crate) fn glob_walk_origin(
workspace_host: &Path,
project_root: &ProjectRoot,
anchor: PathAnchor,
) -> (PathBuf, String, &'static str) {
match (anchor, project_root) {
(PathAnchor::WorkspaceAbsolute, _) => (workspace_host.to_path_buf(), String::new(), "/"),
(PathAnchor::ProjectRelative, ProjectRoot::WorkspaceRoot) => {
(workspace_host.to_path_buf(), String::new(), "")
}
(PathAnchor::ProjectRelative, ProjectRoot::Nested(cp)) => {
let segs: Vec<&PathSegment> = cp.segments().iter().collect();
(
host_path_from_segments(workspace_host, &segs),
workspace_absolute_string_from_segments(&segs),
"",
)
}
}
}
pub(crate) fn host_path_from_segments(workspace_root: &Path, segments: &[&PathSegment]) -> PathBuf {
let mut p = workspace_root.to_path_buf();
for s in segments {
p.push(s.as_str());
}
p
}
pub(crate) fn workspace_absolute_string_from_segments(segments: &[&PathSegment]) -> String {
let mut s = String::new();
for seg in segments {
s.push('/');
s.push_str(seg.as_str());
}
s
}