1use std::path::{Path, PathBuf};
2
3use std::ffi::OsStr;
4
5fn is_rust_file(file: &Path) -> bool {
7 file.extension().is_some_and(|ext| ext == "rs")
8}
9
10fn get_stem_rs(file: &Path) -> Option<&OsStr> {
12 if is_rust_file(file) {
13 file.file_stem()
14 .filter(|name| name.to_string_lossy() != "mod")
15 } else {
16 None
17 }
18}
19
20#[derive(Debug, Default)]
21pub struct CratesScanner(Vec<PathBuf>);
22
23impl CratesScanner {
24 pub fn scan(&mut self, base: PathBuf) {
26 if base.join("Cargo.toml").is_file() && base.join("src").is_dir() {
27 self.0.push(base.clone());
28 }
29
30 std::fs::read_dir(&base)
31 .into_iter()
32 .flatten()
33 .filter_map(|entry| entry.ok())
34 .map(|entry| entry.path())
35 .filter(|entry| {
36 entry.is_dir()
37 && entry
38 .file_name()
39 .is_none_or(|name| name.to_string_lossy() != "src")
40 })
41 .for_each(|entry| self.scan(entry));
42 }
43
44 pub fn into_inner(self) -> Vec<PathBuf> {
45 self.0
46 }
47}
48
49#[derive(Debug, Default)]
50pub struct FlatModuleScanner(Vec<(PathBuf, PathBuf)>);
51
52impl FlatModuleScanner {
53 fn scan(&mut self, base: &Path) {
55 std::fs::read_dir(base)
56 .into_iter()
57 .flatten()
58 .filter_map(|entry| entry.ok())
59 .map(|entry| entry.path())
60 .for_each(|path| {
61 if path.is_dir() {
62 self.scan(&path);
63 } else if let Some(name) = get_stem_rs(&path) {
64 let sibling_dir = path.with_file_name(name);
65
66 if sibling_dir.is_dir() {
67 let mod_rs_path = sibling_dir.join("mod.rs");
68 if !mod_rs_path.exists() {
69 self.0.push((path, mod_rs_path));
70 }
71 }
72 }
73 });
74 }
75
76 pub fn scan_crates(&mut self, crates: &[PathBuf]) {
77 for path in crates {
78 let src_path = path.join("src");
79 self.scan(&src_path);
80 }
81 }
82
83 pub fn into_inner(self) -> Vec<(PathBuf, PathBuf)> {
84 self.0
85 }
86}
87
88pub fn scan(base: PathBuf) -> std::io::Result<Vec<(PathBuf, PathBuf)>> {
89 let mut crates = CratesScanner::default();
90 crates.scan(base);
91 let crates = crates.into_inner();
92
93 if crates.is_empty() {
94 return Err(std::io::Error::new(
95 std::io::ErrorKind::NotFound,
96 "no crates found in directory",
97 ));
98 }
99
100 let mut modules = FlatModuleScanner::default();
101 modules.scan_crates(&crates);
102
103 Ok(modules.into_inner())
104}
105
106pub struct ModuleMigrator;
107
108impl ModuleMigrator {
109 pub fn migrate(&self, src: &Path, dst: &Path) -> std::io::Result<()> {
110 if let Some(parent) = dst.parent() {
111 std::fs::create_dir_all(parent)?;
112 }
113 std::fs::rename(src, dst)?;
114 Ok(())
115 }
116}