stern4rust/
package_tree.rs1use std::collections::BTreeMap;
6use std::path::Path;
7use std::path::PathBuf;
8
9use crate::source_file::SourceFile;
10
11pub struct PackageTree {
24 directories: BTreeMap<PathBuf, Vec<PathBuf>>,
25}
26
27impl PackageTree {
28 pub const REGISTRY_NAMES: [&'static str; 4] = ["all_tests.rs", "lib.rs", "main.rs", "mod.rs"];
29
30 pub fn of(files: &[SourceFile]) -> Self {
31 let mut directories: BTreeMap<PathBuf, Vec<PathBuf>> = BTreeMap::new();
32 for file in files {
33 let path = PathBuf::from(file.relative_path().replace('\\', "/"));
34 let parent = path.parent().unwrap_or(Path::new("")).to_path_buf();
35 directories.entry(parent).or_default().push(path);
36 }
37 Self { directories }
38 }
39
40 pub fn directories(&self) -> Vec<&Path> {
41 self.directories.keys().map(PathBuf::as_path).collect()
42 }
43
44 pub fn registries_in(&self, directory: &Path) -> Vec<&Path> {
47 let mut found: Vec<&Path> = self
48 .files_in(directory)
49 .into_iter()
50 .filter(|path| Self::is_registry(path))
51 .collect();
52 found.sort_by_key(|path| Self::registry_rank(path));
53 found
54 }
55
56 pub fn expected_modules_in(&self, directory: &Path) -> Vec<String> {
60 let mut expected: Vec<String> = self
61 .files_in(directory)
62 .into_iter()
63 .filter(|path| !Self::is_registry(path))
64 .filter_map(Self::module_name)
65 .collect();
66 expected.extend(self.submodules_of(directory));
67 expected.sort();
68 expected
69 }
70
71 fn files_in(&self, directory: &Path) -> Vec<&Path> {
72 self.directories
73 .get(directory)
74 .map(|paths| paths.iter().map(PathBuf::as_path).collect())
75 .unwrap_or_default()
76 }
77
78 fn submodules_of(&self, directory: &Path) -> Vec<String> {
82 self.directories
83 .keys()
84 .filter(|candidate| candidate.parent() == Some(directory))
85 .filter(|candidate| !self.registries_in(candidate).is_empty())
86 .filter_map(|candidate| Self::directory_name(candidate))
87 .collect()
88 }
89
90 fn directory_name(path: &Path) -> Option<String> {
91 path.file_name()
92 .and_then(|name| name.to_str())
93 .map(str::to_string)
94 }
95
96 fn is_registry(path: &Path) -> bool {
97 path.file_name()
98 .and_then(|name| name.to_str())
99 .is_some_and(|name| Self::REGISTRY_NAMES.contains(&name))
100 }
101
102 fn module_name(path: &Path) -> Option<String> {
103 path.file_stem()
104 .and_then(|stem| stem.to_str())
105 .map(str::to_string)
106 }
107
108 fn registry_rank(path: &Path) -> usize {
109 path.file_name()
110 .and_then(|name| name.to_str())
111 .and_then(|name| Self::REGISTRY_NAMES.iter().position(|known| *known == name))
112 .unwrap_or(usize::MAX)
113 }
114}