rs_hack/commands/
neighbors.rs1use std::path::{Path, PathBuf};
5
6use anyhow::Result;
7
8#[derive(Debug)]
9pub struct NeighborsReport {
10 pub target: PathBuf,
11 pub siblings: Vec<PathBuf>,
12 pub twin_files: Vec<PathBuf>,
13 pub test_files: Vec<PathBuf>,
14}
15
16pub fn run(path: &Path) -> Result<NeighborsReport> {
17 let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
18
19 let parent = path.parent().unwrap_or_else(|| Path::new("."));
20 let stem = path
21 .file_stem()
22 .and_then(|s| s.to_str())
23 .unwrap_or("")
24 .to_string();
25
26 let mut siblings = Vec::new();
28 if let Ok(entries) = std::fs::read_dir(parent) {
29 for entry in entries.flatten() {
30 let ep = entry.path();
31 if ep == path {
32 continue;
33 }
34 if ep.extension().and_then(|s| s.to_str()) == Some("rs") {
35 siblings.push(ep);
36 }
37 }
38 }
39 siblings.sort();
40
41 let mut twin_files = Vec::new();
44
45 let parent_name = parent
46 .file_name()
47 .and_then(|s| s.to_str())
48 .unwrap_or("")
49 .to_string();
50
51 let mut search_roots: Vec<PathBuf> = Vec::new();
53 if let Some(grandparent) = parent.parent() {
54 search_roots.push(grandparent.to_path_buf());
55 if let Some(great) = grandparent.parent() {
56 search_roots.push(great.to_path_buf());
57 }
58 }
59
60 for root in &search_roots {
61 if let Ok(entries) = std::fs::read_dir(root) {
62 for entry in entries.flatten() {
63 let ep = entry.path();
64 if !ep.is_dir() {
65 continue;
66 }
67 let dir_name = ep
68 .file_name()
69 .and_then(|s| s.to_str())
70 .unwrap_or("")
71 .to_string();
72 if dir_name.starts_with(&parent_name)
74 && dir_name.len() > parent_name.len()
75 && dir_name[parent_name.len()..]
76 .chars()
77 .all(|c| c.is_ascii_digit())
78 {
79 collect_matching_files(&ep, &stem, &mut twin_files);
81 }
82 }
83 }
84 }
85 twin_files.sort();
86
87 let mut test_files = Vec::new();
89
90 let mut search = Some(parent);
91 while let Some(dir) = search {
92 let tests_dir = dir.join("tests");
93 if tests_dir.is_dir() {
94 collect_matching_files(&tests_dir, &stem, &mut test_files);
95 break;
96 }
97 search = dir.parent();
98 }
99 test_files.sort();
100
101 Ok(NeighborsReport {
102 target: path,
103 siblings,
104 twin_files,
105 test_files,
106 })
107}
108
109fn collect_matching_files(dir: &Path, stem: &str, out: &mut Vec<PathBuf>) {
111 if let Ok(entries) = std::fs::read_dir(dir) {
112 for entry in entries.flatten() {
113 let ep = entry.path();
114 if ep.is_dir() {
115 collect_matching_files(&ep, stem, out);
116 } else if ep.extension().and_then(|s| s.to_str()) == Some("rs") {
117 let file_stem = ep.file_stem().and_then(|s| s.to_str()).unwrap_or("");
118 if file_stem.contains(stem) {
119 out.push(ep);
120 }
121 }
122 }
123 }
124}