stern4rust/finding/model/
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 {
35 let mut directories: BTreeMap<PathBuf, Vec<PathBuf>> = BTreeMap::new();
36 for file in files {
37 let path = PathBuf::from(file.relative_path().replace('\\', "/"));
38 let parent = path.parent().unwrap_or(Path::new("")).to_path_buf();
39 directories.entry(parent.clone()).or_default().push(path);
40 let mut ancestor = parent;
41 while let Some(above) = ancestor.parent() {
42 let above = above.to_path_buf();
43 directories.entry(above.clone()).or_default();
44 ancestor = above;
45 }
46 }
47 Self { directories }
48 }
49
50 pub fn directories(&self) -> Vec<&Path> {
51 self.directories.keys().map(PathBuf::as_path).collect()
52 }
53
54 pub fn registries_in(&self, directory: &Path) -> Vec<&Path> {
57 let mut found: Vec<&Path> = self
58 .files_in(directory)
59 .into_iter()
60 .filter(|path| Self::is_registry(path))
61 .collect();
62 found.sort_by_key(|path| Self::registry_rank(path));
63 found
64 }
65
66 pub fn expected_modules_in(&self, directory: &Path) -> Vec<String> {
70 let mut expected: Vec<String> = self
71 .files_in(directory)
72 .into_iter()
73 .filter(|path| !Self::is_registry(path))
74 .filter_map(Self::module_name)
75 .collect();
76 expected.extend(self.submodules_of(directory));
77 expected.sort();
78 expected
79 }
80
81 pub fn subdirectories_of(&self, directory: &Path) -> Vec<&Path> {
85 self.directories
86 .keys()
87 .filter(|candidate| candidate.parent() == Some(directory))
88 .map(PathBuf::as_path)
89 .collect()
90 }
91
92 pub fn files_in(&self, directory: &Path) -> Vec<&Path> {
93 self.directories
94 .get(directory)
95 .map(|paths| paths.iter().map(PathBuf::as_path).collect())
96 .unwrap_or_default()
97 }
98
99 fn submodules_of(&self, directory: &Path) -> Vec<String> {
103 self.directories
104 .keys()
105 .filter(|candidate| candidate.parent() == Some(directory))
106 .filter(|candidate| !self.registries_in(candidate).is_empty())
107 .filter_map(|candidate| Self::directory_name(candidate))
108 .collect()
109 }
110
111 fn directory_name(path: &Path) -> Option<String> {
112 path.file_name()
113 .and_then(|name| name.to_str())
114 .map(str::to_string)
115 }
116
117 fn is_registry(path: &Path) -> bool {
118 path.file_name()
119 .and_then(|name| name.to_str())
120 .is_some_and(|name| Self::REGISTRY_NAMES.contains(&name))
121 }
122
123 fn module_name(path: &Path) -> Option<String> {
124 path.file_stem()
125 .and_then(|stem| stem.to_str())
126 .map(str::to_string)
127 }
128
129 fn registry_rank(path: &Path) -> usize {
130 path.file_name()
131 .and_then(|name| name.to_str())
132 .and_then(|name| Self::REGISTRY_NAMES.iter().position(|known| *known == name))
133 .unwrap_or(usize::MAX)
134 }
135}