use std::fs;
use std::path::{Path, PathBuf};
const UNLADDERED: [&str; 4] = ["lib", "main", "tests", "test_support"];
pub fn assert_ladder(src_root: &Path, ladder: &[&[&str]]) {
for p in entries(src_root) {
let name = p.file_stem().unwrap().to_str().unwrap();
assert!(
ladder.concat().contains(&name) || UNLADDERED.contains(&name),
"{name} is a module root the ladder does not place",
);
}
for (tier, rungs) in ladder.iter().enumerate() {
let beneath = ladder[..tier].concat();
for rung in *rungs {
for f in rung_files(src_root, rung) {
let src = fs::read_to_string(&f).unwrap();
for path in src.split("crate::").skip(1) {
let named = path.split(|c: char| c != '_' && !c.is_alphanumeric()).next().unwrap();
assert!(
named == *rung || beneath.contains(&named) || UNLADDERED.contains(&named),
"{}: {rung}/ names crate::{named}, which is not beneath it",
f.display(),
);
}
}
}
}
}
pub fn rung_files(src_root: &Path, rung: &str) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut dirs = vec![src_root.join(rung)];
while let Some(dir) = dirs.pop() {
for p in entries(&dir) {
if p.is_dir() {
if !p.ends_with("tests") && !p.ends_with("benches") {
dirs.push(p);
}
} else if p.extension().is_some_and(|x| x == "rs") {
out.push(p);
}
}
}
out
}
fn entries(dir: &Path) -> Vec<PathBuf> {
let read = fs::read_dir(dir).unwrap_or_else(|e| panic!("a source walk must read {}: {e}", dir.display()));
read.map(|e| e.unwrap().path()).collect()
}