use crate::application::suppress::path_matches_glob;
use crate::domain::types::{LanguageId, SourceFile};
use crate::ports::outbound::{SourceLoader, WorkspaceDiscovery};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Default)]
pub struct IgnoreWalkDiscovery;
impl WorkspaceDiscovery for IgnoreWalkDiscovery {
fn discover(
&self,
root: &Path,
languages: &[LanguageId],
exclusions: &[String],
) -> Result<Vec<PathBuf>, String> {
let mut out = Vec::new();
let walker = ignore::WalkBuilder::new(root)
.hidden(false)
.git_ignore(true)
.git_global(true)
.git_exclude(true)
.build();
for entry in walker {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
if !path.is_file() {
continue;
}
let rel = path
.strip_prefix(root)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
if exclusions.iter().any(|ex| path_matches_glob(&rel, ex)) {
continue;
}
let abs = path.to_string_lossy().replace('\\', "/");
if exclusions.iter().any(|ex| path_matches_glob(&abs, ex)) {
continue;
}
if languages.iter().any(|l| language_matches(l, path)) {
out.push(path.to_path_buf());
}
}
out.sort();
Ok(out)
}
}
fn language_matches(lang: &LanguageId, path: &Path) -> bool {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_ascii_lowercase();
match lang.as_str() {
"rust" => ext == "rs",
"typescript" | "ts" => ext == "ts" || ext == "tsx",
"javascript" | "js" => ext == "js" || ext == "jsx" || ext == "mjs" || ext == "cjs",
"python" | "py" => ext == "py",
"go" => ext == "go",
"java" => ext == "java",
_ => false,
}
}
pub struct FsSourceLoader;
impl SourceLoader for FsSourceLoader {
fn load(&self, path: &Path, language: LanguageId) -> Result<SourceFile, String> {
let content = fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?;
Ok(SourceFile {
path: path.to_path_buf(),
content,
language,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn discovers_rust_files() {
let dir = tempdir().unwrap();
let src = dir.path().join("src");
fs::create_dir_all(&src).unwrap();
let mut f = fs::File::create(src.join("lib.rs")).unwrap();
writeln!(f, "fn main() {{}}").unwrap();
fs::create_dir_all(dir.path().join("target/debug")).unwrap();
fs::write(dir.path().join("target/debug/x.rs"), "x").unwrap();
let d = IgnoreWalkDiscovery;
let paths = d
.discover(
dir.path(),
&[LanguageId::rust()],
&["**/target/**".into()],
)
.unwrap();
assert_eq!(paths.len(), 1);
assert!(paths[0].ends_with("lib.rs"));
}
}