Skip to main content

cargo_hilly/
lib.rs

1use std::path::{Path, PathBuf};
2
3use std::ffi::OsStr;
4
5/// Checks if the file is a rust file
6fn is_rust_file(file: &Path) -> bool {
7    file.extension().is_some_and(|ext| ext == "rs")
8}
9
10/// Returns the filename without the extension
11fn 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    /// Finds all the crates in the given directory
25    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    /// Scan for all the flat modules that can be moved
54    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}