use std::collections::{BTreeSet, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use tracing::info;
const ALEF_HEADER_MARKERS: &[&str] = &["auto-generated by alef", "AUTO-GENERATED by alef", "DO NOT EDIT"];
pub fn cleanup_orphaned_files(current_gen_paths: &HashSet<PathBuf>) -> anyhow::Result<usize> {
if current_gen_paths.is_empty() {
return Ok(0);
}
let normalized: HashSet<PathBuf> = current_gen_paths
.iter()
.map(|p| p.canonicalize().unwrap_or_else(|_| p.clone()))
.collect();
let touched_dirs: BTreeSet<PathBuf> = current_gen_paths
.iter()
.filter_map(|p| p.parent().map(|d| d.to_path_buf()))
.collect();
let mut removed_count = 0;
let mut visited_dirs: HashSet<PathBuf> = HashSet::new();
for dir in &touched_dirs {
if !dir.exists() {
continue;
}
let canonical_dir = dir.canonicalize().unwrap_or_else(|_| dir.clone());
if !visited_dirs.insert(canonical_dir.clone()) {
continue;
}
removed_count += cleanup_dir_recursive(&canonical_dir, &normalized, &touched_dirs)?;
}
Ok(removed_count)
}
fn cleanup_dir_recursive(
dir: &Path,
normalized_gen_paths: &HashSet<PathBuf>,
touched_dirs: &BTreeSet<PathBuf>,
) -> anyhow::Result<usize> {
let mut removed_count = 0;
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
let canonical_sub = path.canonicalize().unwrap_or_else(|_| path.clone());
let descend = touched_dirs
.iter()
.any(|td| td == &canonical_sub || td.starts_with(&canonical_sub));
if descend {
removed_count += cleanup_dir_recursive(&path, normalized_gen_paths, touched_dirs)?;
}
continue;
}
if !has_alef_header(&path)? {
continue;
}
let canonical_path = path.canonicalize().unwrap_or_else(|_| path.clone());
if !normalized_gen_paths.contains(&canonical_path) {
info!("Removing stale alef-generated file: {}", path.display());
fs::remove_file(&path)?;
removed_count += 1;
}
}
Ok(removed_count)
}
fn has_alef_header(path: &Path) -> anyhow::Result<bool> {
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => {
return Ok(false);
}
};
let first_lines = content.lines().take(5).collect::<Vec<_>>().join("\n");
for marker in ALEF_HEADER_MARKERS {
if first_lines.contains(marker) {
return Ok(true);
}
}
Ok(false)
}