use std::path::{Path, PathBuf};
use crate::config::DiscoveryConfig;
mod stop;
pub(crate) use stop::dirs;
pub use stop::StopReason;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Walk {
pub dirs: Vec<PathBuf>,
pub stopped: StopReason,
}
pub fn walk(start: &Path, cfg: &DiscoveryConfig) -> Walk {
let start = normalize(start);
let mut dirs = vec![start.clone()];
if !cfg.walk_up {
return Walk {
dirs,
stopped: StopReason::WalkUpDisabled,
};
}
let home = directories::UserDirs::new().map(|d| normalize(d.home_dir()));
let mut current = start;
let stopped = loop {
if dirs.len() >= cfg.max_depth {
break StopReason::MaxDepth;
}
if cfg.stop_at_git && current.join(".git").exists() {
break StopReason::GitRoot;
}
if Some(¤t) == home.as_ref() {
break StopReason::Home;
}
match current.parent() {
Some(parent) if parent != current => {
current = parent.to_path_buf();
dirs.push(current.clone());
}
_ => break StopReason::FilesystemRoot,
}
};
Walk { dirs, stopped }
}
impl Walk {
pub fn project_root(&self, is_root: impl Fn(&Path) -> bool) -> Option<PathBuf> {
self.dirs.iter().find(|d| is_root(d)).cloned()
}
pub fn config_paths(&self) -> Vec<PathBuf> {
self.dirs
.iter()
.map(|d| d.join(".pmpx.toml"))
.filter(|p| p.is_file())
.collect()
}
}
fn normalize(p: &Path) -> PathBuf {
let absolute = if p.is_absolute() {
p.to_path_buf()
} else {
std::env::current_dir()
.map(|cwd| cwd.join(p))
.unwrap_or_else(|_| p.to_path_buf())
};
lexical_normalize(&absolute)
}
fn lexical_normalize(p: &Path) -> PathBuf {
use std::path::Component;
let mut out = PathBuf::new();
for comp in p.components() {
match comp {
Component::CurDir => {}
Component::ParentDir => {
if !out.pop() {
out.push("..");
}
}
other => out.push(other.as_os_str()),
}
}
out
}
#[cfg(test)]
mod tests;