Files
archlens/crates/adapters/walkdir/src/walkdir_discovery.rs
Gabriel Kaszewski e26151b4a1
Some checks failed
CI / Check / Test (push) Failing after 44s
Architecture Docs / Generate diagrams (push) Successful in 3m21s
refactor: move scattered business logic into domain
- CodeGraph::merge_project_edges() replaces presentation-layer function
- Language::is_test_file() centralises test file detection (was in walkdir)
- AnalysisConfig::is_standard_excluded() centralises default dir exclusions (was in walkdir)
- normalize_cargo_package() / normalize_python_package() in domain replace duplicated normalisers in each adapter
- walkdir, cargo-workspace, python-project updated to call domain methods
2026-06-17 10:58:46 +02:00

99 lines
2.9 KiB
Rust

use std::path::Path;
use ignore::WalkBuilder;
use archlens_domain::{
AnalysisConfig, DomainError, FilePath, Language, SourceFile, ports::FileDiscovery,
};
pub struct WalkdirDiscovery;
impl Default for WalkdirDiscovery {
fn default() -> Self {
Self::new()
}
}
impl WalkdirDiscovery {
pub fn new() -> Self {
Self
}
fn detect_language(path: &Path) -> Option<Language> {
match path.extension()?.to_str()? {
"rs" => Some(Language::Rust),
"py" => Some(Language::Python),
"cs" => Some(Language::CSharp),
_ => None,
}
}
fn is_excluded(path: &Path, root: &Path, excludes: &[String]) -> bool {
let relative = path.strip_prefix(root).unwrap_or(path);
let relative_str = relative.to_string_lossy();
for component in relative.components() {
let name = component.as_os_str().to_string_lossy();
if AnalysisConfig::is_standard_excluded(&name) {
return true;
}
}
excludes
.iter()
.any(|exclude| relative_str.contains(exclude.trim_end_matches('/')))
}
}
impl FileDiscovery for WalkdirDiscovery {
fn discover(
&self,
root: &Path,
config: &AnalysisConfig,
) -> Result<Vec<SourceFile>, DomainError> {
let mut files = Vec::new();
let walker = WalkBuilder::new(root).hidden(true).git_ignore(true).build();
for entry in walker.filter_map(|e| e.ok()) {
let path = entry.path();
if !path.is_file() {
continue;
}
if Self::is_excluded(path, root, config.excludes()) {
continue;
}
if let Some(scope) = config.scope() {
let relative = path.strip_prefix(root).unwrap_or(path);
if !relative.starts_with(scope) {
continue;
}
}
if let Some(language) = Self::detect_language(path) {
if !config.include_tests() && language.is_test_file(path) {
continue;
}
if let Some(changed) = config.changed_files() {
let relative = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
if !changed
.iter()
.any(|c| relative.ends_with(c.as_str()) || c.ends_with(relative.as_ref()))
{
continue;
}
}
let absolute = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
let file_path = FilePath::new(&absolute.to_string_lossy())
.map_err(|e| DomainError::IoError(e.to_string()))?;
files.push(SourceFile::new(file_path, language));
}
}
Ok(files)
}
}