use std::path::{Path, PathBuf};
fn relative_name(root: &Path, path: &Path) -> Result<String, String> {
let below: &Path = path
.strip_prefix(root)
.map_err(|error| return format!("{} is not under {}: {error}", path.display(), root.display()))?;
let mut parts: Vec<String> = Vec::new();
for component in below.components() {
match component.as_os_str().to_str() {
Some(text) => parts.push(text.to_owned()),
None => return Err(format!("{} has a name that is not UTF-8", path.display())),
}
}
return Ok(parts.join("/"));
}
pub(crate) fn discover(root: &Path) -> Result<Vec<String>, String> {
let mut found: Vec<String> = Vec::new();
let mut pending: Vec<PathBuf> = vec![root.to_path_buf()];
while let Some(directory) = pending.pop() {
let entries: std::fs::ReadDir = std::fs::read_dir(&directory)
.map_err(|error| return format!("cannot list {}: {error}", directory.display()))?;
for item in entries {
let entry: std::fs::DirEntry =
item.map_err(|error| return format!("cannot read an entry of {}: {error}", directory.display()))?;
let path: PathBuf = entry.path();
let kind: std::fs::FileType =
entry.file_type().map_err(|error| return format!("cannot inspect {}: {error}", path.display()))?;
if kind.is_symlink() {
return Err(format!("{} is a symbolic link; the scan cannot prove what it reads", path.display()));
}
if kind.is_dir() {
pending.push(path);
continue;
}
if path.extension().is_some_and(|extension| return extension == "rs") {
found.push(relative_name(root, &path)?);
}
}
}
if found.is_empty() {
return Err(format!("no .rs file under {}; an empty listing proves nothing", root.display()));
}
found.sort();
return Ok(found);
}