use std::path::{Path, PathBuf};
use walkdir::WalkDir;
pub fn discover_files(paths: &[PathBuf]) -> Vec<PathBuf> {
let mut files = Vec::new();
for path in paths {
if path.is_file() {
if is_rust_file(path) {
files.push(path.clone());
}
} else if path.is_dir() {
let workspace_member_srcs = workspace_member_src_dirs(path);
if !workspace_member_srcs.is_empty() {
for member_src in workspace_member_srcs {
collect_rust_files(&member_src, &mut files);
}
continue;
}
let scan_path = if should_restrict_to_src(path) {
let src_path = path.join("src");
if src_path.is_dir() {
src_path
} else {
continue;
}
} else {
path.clone()
};
collect_rust_files(&scan_path, &mut files);
}
}
files.sort();
files.dedup();
files
}
fn collect_rust_files(scan_path: &Path, files: &mut Vec<PathBuf>) {
for entry in WalkDir::new(scan_path).into_iter().filter_map(|e| e.ok()) {
let p = entry.path();
if p.is_file() && is_rust_file(p) {
files.push(p.to_path_buf());
}
}
}
fn workspace_member_src_dirs(path: &Path) -> Vec<PathBuf> {
let cargo_toml = path.join("Cargo.toml");
if !cargo_toml.is_file() {
return vec![];
}
let content = match std::fs::read_to_string(&cargo_toml) {
Ok(content) => content,
Err(_) => return vec![],
};
let parsed: toml::Value = match toml::from_str(&content) {
Ok(value) => value,
Err(_) => return vec![],
};
let members = match parsed
.get("workspace")
.and_then(|w| w.get("members"))
.and_then(toml::Value::as_array)
{
Some(members) => members,
None => return vec![],
};
let mut src_dirs = Vec::new();
for member in members {
let Some(member_path) = member.as_str() else {
continue;
};
if member_path.contains('*') || member_path.contains('?') {
continue;
}
let member_src = path.join(member_path).join("src");
if member_src.is_dir() {
src_dirs.push(member_src);
}
}
src_dirs
}
fn should_restrict_to_src(path: &Path) -> bool {
let last_component = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
if last_component.is_empty() || last_component == "." || last_component == ".." {
return true;
}
if last_component == "src" {
return false;
}
for component in path.components() {
if let Some(s) = component.as_os_str().to_str() {
if s == "src" {
return false;
}
}
}
true
}
fn is_rust_file(path: &Path) -> bool {
path.extension().is_some_and(|e| e == "rs")
}