use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Default)]
pub struct PathRegistry {
paths: HashSet<Arc<PathBuf>>,
watch_dirs: HashSet<PathBuf>,
}
impl PathRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn add_paths(&mut self, paths: impl IntoIterator<Item = PathBuf>) -> Vec<PathBuf> {
let mut new_dirs = Vec::new();
for path in paths {
let arc_path = Arc::new(path);
if self.paths.insert(arc_path.clone()) {
if let Some(parent) = arc_path.parent() {
let parent_path = if parent.as_os_str().is_empty() {
PathBuf::from(".")
} else {
parent.to_path_buf()
};
if self.watch_dirs.insert(parent_path.clone()) {
new_dirs.push(parent_path);
}
}
}
}
new_dirs
}
pub fn remove_path(&mut self, path: &Path) {
self.paths.retain(|p| p.as_ref() != path);
}
pub fn contains(&self, path: &Path) -> bool {
self.paths.iter().any(|p| p.as_ref() == path)
}
pub fn paths(&self) -> impl Iterator<Item = &Path> {
self.paths.iter().map(|p| p.as_ref().as_path())
}
pub fn watch_dirs(&self) -> &HashSet<PathBuf> {
&self.watch_dirs
}
pub fn path_count(&self) -> usize {
self.paths.len()
}
pub fn dir_count(&self) -> usize {
self.watch_dirs.len()
}
pub fn rebuild(&mut self, paths: impl IntoIterator<Item = PathBuf>) {
self.paths.clear();
self.watch_dirs.clear();
self.add_paths(paths);
}
pub fn compute_watch_dirs(paths: &[PathBuf]) -> HashSet<PathBuf> {
let mut dirs = HashSet::new();
for path in paths {
if let Some(parent) = path.parent() {
if parent.as_os_str().is_empty() {
dirs.insert(PathBuf::from("."));
} else {
dirs.insert(parent.to_path_buf());
}
}
}
dirs
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_registry_basic() {
let mut registry = PathRegistry::new();
let paths = vec![
PathBuf::from("/project/src/main.rs"),
PathBuf::from("/project/src/lib.rs"),
PathBuf::from("/project/tests/test.rs"),
];
let new_dirs = registry.add_paths(paths.clone());
assert_eq!(new_dirs.len(), 2);
assert!(new_dirs.contains(&PathBuf::from("/project/src")));
assert!(new_dirs.contains(&PathBuf::from("/project/tests")));
assert_eq!(registry.path_count(), 3);
assert!(registry.contains(Path::new("/project/src/main.rs")));
}
#[test]
fn test_path_registry_interning() {
let mut registry = PathRegistry::new();
let path = PathBuf::from("/project/src/main.rs");
let dirs1 = registry.add_paths(vec![path.clone()]);
let dirs2 = registry.add_paths(vec![path.clone()]);
assert_eq!(dirs1.len(), 1);
assert!(dirs2.is_empty());
assert_eq!(registry.path_count(), 1);
}
#[test]
fn test_path_registry_remove() {
let mut registry = PathRegistry::new();
let path = PathBuf::from("/project/src/main.rs");
registry.add_paths(vec![path.clone()]);
assert!(registry.contains(&path));
registry.remove_path(&path);
assert!(!registry.contains(&path));
assert_eq!(registry.path_count(), 0);
}
#[test]
fn test_path_registry_root_files() {
let mut registry = PathRegistry::new();
let path = PathBuf::from("Cargo.toml");
let dirs = registry.add_paths(vec![path]);
assert_eq!(dirs.len(), 1);
assert!(dirs.contains(&PathBuf::from(".")));
}
#[test]
fn test_compute_watch_dirs() {
let paths = vec![
PathBuf::from("/a/b/file1.rs"),
PathBuf::from("/a/b/file2.rs"),
PathBuf::from("/a/c/file3.rs"),
];
let dirs = PathRegistry::compute_watch_dirs(&paths);
assert_eq!(dirs.len(), 2);
assert!(dirs.contains(&PathBuf::from("/a/b")));
assert!(dirs.contains(&PathBuf::from("/a/c")));
}
}