use std::path::{Path, PathBuf};
pub fn find_first_match(project_dir: &Path, pattern: &str) -> Option<PathBuf> {
if let Some(ext) = pattern.strip_prefix("*.") {
let mut matches: Vec<std::path::PathBuf> = std::fs::read_dir(project_dir)
.ok()?
.flatten()
.map(|e| e.path())
.filter(|p| p.is_file())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some(ext))
.collect();
matches.sort();
return matches.into_iter().next();
}
let candidate = project_dir.join(pattern);
if candidate.is_file() {
Some(candidate)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn exact_filename_match() {
let tmp = tempdir().unwrap();
fs::write(tmp.path().join("Cargo.toml"), "").unwrap();
assert!(find_first_match(tmp.path(), "Cargo.toml").is_some());
assert!(find_first_match(tmp.path(), "nope.txt").is_none());
}
#[test]
fn extension_glob_match() {
let tmp = tempdir().unwrap();
fs::write(tmp.path().join("main.tf"), "").unwrap();
let found = find_first_match(tmp.path(), "*.tf").unwrap();
assert_eq!(found.file_name().unwrap(), "main.tf");
}
#[test]
fn no_match_when_missing() {
let tmp = tempdir().unwrap();
assert!(find_first_match(tmp.path(), "Cargo.toml").is_none());
assert!(find_first_match(tmp.path(), "*.tf").is_none());
}
#[test]
fn extension_glob_returns_deterministic_match() {
let tmp = tempdir().unwrap();
for name in ["b.tf", "a.tf", "c.tf"] {
fs::write(tmp.path().join(name), "").unwrap();
}
let first = find_first_match(tmp.path(), "*.tf").unwrap();
assert_eq!(first.file_name().unwrap(), "a.tf");
for _ in 0..3 {
assert_eq!(
find_first_match(tmp.path(), "*.tf").unwrap(),
first,
"extension-glob matcher must be deterministic"
);
}
}
#[test]
fn does_not_walk_subdirs() {
let tmp = tempdir().unwrap();
fs::create_dir(tmp.path().join("sub")).unwrap();
fs::write(tmp.path().join("sub").join("Cargo.toml"), "").unwrap();
assert!(find_first_match(tmp.path(), "Cargo.toml").is_none());
}
}