use std::collections::BTreeMap;
use std::path::Path;
use std::path::PathBuf;
use crate::source_file::SourceFile;
pub struct PackageTree {
directories: BTreeMap<PathBuf, Vec<PathBuf>>,
}
impl PackageTree {
pub const REGISTRY_NAMES: [&'static str; 4] = ["all_tests.rs", "lib.rs", "main.rs", "mod.rs"];
pub fn of(files: &[SourceFile]) -> Self {
let mut directories: BTreeMap<PathBuf, Vec<PathBuf>> = BTreeMap::new();
for file in files {
let path = PathBuf::from(file.relative_path().replace('\\', "/"));
let parent = path.parent().unwrap_or(Path::new("")).to_path_buf();
directories.entry(parent.clone()).or_default().push(path);
let mut ancestor = parent;
while let Some(above) = ancestor.parent() {
let above = above.to_path_buf();
directories.entry(above.clone()).or_default();
ancestor = above;
}
}
Self { directories }
}
pub fn directories(&self) -> Vec<&Path> {
self.directories.keys().map(PathBuf::as_path).collect()
}
pub fn registries_in(&self, directory: &Path) -> Vec<&Path> {
let mut found: Vec<&Path> = self
.files_in(directory)
.into_iter()
.filter(|path| Self::is_registry(path))
.collect();
found.sort_by_key(|path| Self::registry_rank(path));
found
}
pub fn expected_modules_in(&self, directory: &Path) -> Vec<String> {
let mut expected: Vec<String> = self
.files_in(directory)
.into_iter()
.filter(|path| !Self::is_registry(path))
.filter_map(Self::module_name)
.collect();
expected.extend(self.submodules_of(directory));
expected.sort();
expected
}
pub fn subdirectories_of(&self, directory: &Path) -> Vec<&Path> {
self.directories
.keys()
.filter(|candidate| candidate.parent() == Some(directory))
.map(PathBuf::as_path)
.collect()
}
pub fn files_in(&self, directory: &Path) -> Vec<&Path> {
self.directories
.get(directory)
.map(|paths| paths.iter().map(PathBuf::as_path).collect())
.unwrap_or_default()
}
fn submodules_of(&self, directory: &Path) -> Vec<String> {
self.directories
.keys()
.filter(|candidate| candidate.parent() == Some(directory))
.filter(|candidate| !self.registries_in(candidate).is_empty())
.filter_map(|candidate| Self::directory_name(candidate))
.collect()
}
fn directory_name(path: &Path) -> Option<String> {
path.file_name()
.and_then(|name| name.to_str())
.map(str::to_string)
}
fn is_registry(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| Self::REGISTRY_NAMES.contains(&name))
}
fn module_name(path: &Path) -> Option<String> {
path.file_stem()
.and_then(|stem| stem.to_str())
.map(str::to_string)
}
fn registry_rank(path: &Path) -> usize {
path.file_name()
.and_then(|name| name.to_str())
.and_then(|name| Self::REGISTRY_NAMES.iter().position(|known| *known == name))
.unwrap_or(usize::MAX)
}
}