#![allow(dead_code)]
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
#[derive(Debug, Default)]
pub struct FileGraph {
imports: HashMap<PathBuf, HashSet<PathBuf>>,
importers: HashMap<PathBuf, HashSet<PathBuf>>,
}
impl FileGraph {
pub fn new() -> Self {
Self::default()
}
pub fn add_file(&mut self, path: PathBuf) {
self.imports.entry(path.clone()).or_default();
self.importers.entry(path).or_default();
}
pub fn remove_file(&mut self, path: &Path) {
if let Some(imported) = self.imports.remove(path) {
for imported_path in imported {
if let Some(importers) = self.importers.get_mut(&imported_path) {
importers.remove(path);
}
}
}
if let Some(importers) = self.importers.remove(path) {
for importer_path in importers {
if let Some(imports) = self.imports.get_mut(&importer_path) {
imports.remove(path);
}
}
}
}
pub fn add_import(&mut self, from: &Path, to: &Path) {
self.imports
.entry(from.to_path_buf())
.or_default()
.insert(to.to_path_buf());
self.importers
.entry(to.to_path_buf())
.or_default()
.insert(from.to_path_buf());
}
pub fn remove_import(&mut self, from: &Path, to: &Path) {
if let Some(imports) = self.imports.get_mut(from) {
imports.remove(to);
}
if let Some(importers) = self.importers.get_mut(to) {
importers.remove(from);
}
}
pub fn get_imports(&self, path: &Path) -> Option<&HashSet<PathBuf>> {
self.imports.get(path)
}
pub fn get_importers(&self, path: &Path) -> Option<&HashSet<PathBuf>> {
self.importers.get(path)
}
pub fn get_affected_files(&self, changed_path: &Path) -> HashSet<PathBuf> {
let mut affected = HashSet::new();
let mut to_visit = vec![changed_path.to_path_buf()];
while let Some(current) = to_visit.pop() {
if affected.contains(¤t) {
continue;
}
affected.insert(current.clone());
if let Some(importers) = self.importers.get(¤t) {
for importer in importers {
if !affected.contains(importer) {
to_visit.push(importer.clone());
}
}
}
}
affected
}
pub fn get_dependencies(&self, path: &Path) -> HashSet<PathBuf> {
let mut deps = HashSet::new();
let mut to_visit = vec![path.to_path_buf()];
while let Some(current) = to_visit.pop() {
if deps.contains(¤t) {
continue;
}
deps.insert(current.clone());
if let Some(imports) = self.imports.get(¤t) {
for import in imports {
if !deps.contains(import) {
to_visit.push(import.clone());
}
}
}
}
deps
}
pub fn has_cycle(&self, start: &Path) -> bool {
let mut visited = HashSet::new();
let mut rec_stack = HashSet::new();
self.has_cycle_util(start, &mut visited, &mut rec_stack)
}
fn has_cycle_util(
&self,
path: &Path,
visited: &mut HashSet<PathBuf>,
rec_stack: &mut HashSet<PathBuf>,
) -> bool {
let path_buf = path.to_path_buf();
if rec_stack.contains(&path_buf) {
return true;
}
if visited.contains(&path_buf) {
return false;
}
visited.insert(path_buf.clone());
rec_stack.insert(path_buf.clone());
if let Some(imports) = self.imports.get(path) {
for import in imports {
if self.has_cycle_util(import, visited, rec_stack) {
return true;
}
}
}
rec_stack.remove(&path_buf);
false
}
pub fn clear_imports(&mut self, path: &Path) {
if let Some(imports) = self.imports.remove(path) {
for imported in imports {
if let Some(importers) = self.importers.get_mut(&imported) {
importers.remove(path);
}
}
}
self.imports.insert(path.to_path_buf(), HashSet::new());
}
}