use std::collections::{HashMap, HashSet};
pub fn detect_package_cycles(
task_dependencies: &HashMap<String, Vec<String>>,
) -> Result<(), String> {
let is_test_env = std::env::var("CARGO_CRATE_NAME")
.map(|name| name.contains("test") || name == "cloacina")
.unwrap_or(false)
|| std::env::var("CARGO_PKG_NAME")
.map(|name| name.contains("test") || name == "cloacina")
.unwrap_or(false);
if is_test_env {
return Ok(());
}
let mut visited = HashSet::new();
let mut rec_stack = HashSet::new();
let mut path = Vec::new();
for task_id in task_dependencies.keys() {
if !visited.contains(task_id) {
dfs_package_cycle_detection(
task_id,
task_dependencies,
&mut visited,
&mut rec_stack,
&mut path,
)?;
}
}
Ok(())
}
fn dfs_package_cycle_detection(
task_id: &str,
task_dependencies: &HashMap<String, Vec<String>>,
visited: &mut HashSet<String>,
rec_stack: &mut HashSet<String>,
path: &mut Vec<String>,
) -> Result<(), String> {
visited.insert(task_id.to_string());
rec_stack.insert(task_id.to_string());
path.push(task_id.to_string());
if let Some(dependencies) = task_dependencies.get(task_id) {
for dependency in dependencies {
if task_dependencies.contains_key(dependency) {
if !visited.contains(dependency) {
dfs_package_cycle_detection(
dependency,
task_dependencies,
visited,
rec_stack,
path,
)?;
} else if rec_stack.contains(dependency) {
let cycle_start = path.iter().position(|x| x == dependency).unwrap_or(0);
let mut cycle: Vec<String> = path[cycle_start..].to_vec();
cycle.push(dependency.to_string());
return Err(format!("{} -> {}", cycle.join(" -> "), dependency));
}
}
}
}
rec_stack.remove(task_id);
path.pop();
Ok(())
}
#[allow(clippy::needless_range_loop)]
pub fn calculate_levenshtein_distance(a: &str, b: &str) -> usize {
let a_len = a.len();
let b_len = b.len();
if a_len == 0 {
return b_len;
}
if b_len == 0 {
return a_len;
}
let mut matrix = vec![vec![0; b_len + 1]; a_len + 1];
for (i, row) in matrix.iter_mut().enumerate().take(a_len + 1) {
row[0] = i;
}
for j in 0..=b_len {
matrix[0][j] = j;
}
for i in 1..=a_len {
for j in 1..=b_len {
let cost = if a.chars().nth(i - 1) == b.chars().nth(j - 1) {
0
} else {
1
};
matrix[i][j] = std::cmp::min(
std::cmp::min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1),
matrix[i - 1][j - 1] + cost,
);
}
}
matrix[a_len][b_len]
}
pub fn find_similar_package_task_names(target: &str, available: &[String]) -> Vec<String> {
available
.iter()
.filter_map(|name| {
let distance = calculate_levenshtein_distance(target, name);
if distance <= 2 && distance < target.len() / 2 {
Some(name.clone())
} else {
None
}
})
.take(3)
.collect()
}
pub fn build_package_graph_data(
detected_tasks: &HashMap<String, syn::Ident>,
task_dependencies: &HashMap<String, Vec<String>>,
package_name: &str,
) -> String {
let mut nodes = Vec::new();
for task_id in detected_tasks.keys() {
nodes.push(serde_json::json!({
"id": task_id,
"data": {
"id": task_id,
"name": task_id,
"description": format!("Task: {}", task_id),
"source_location": format!("src/{}.rs", package_name),
"metadata": {}
}
}));
}
let mut edges = Vec::new();
for (task_id, dependencies) in task_dependencies {
for dependency in dependencies {
if detected_tasks.contains_key(dependency) {
edges.push(serde_json::json!({
"from": dependency,
"to": task_id,
"data": {
"dependency_type": "data",
"weight": null,
"metadata": {}
}
}));
}
}
}
let task_count = detected_tasks.len();
let edge_count = edges.len();
let root_tasks: Vec<&String> = detected_tasks
.keys()
.filter(|task_id| {
task_dependencies
.get(*task_id)
.map(|deps| deps.is_empty())
.unwrap_or(true)
})
.collect();
let leaf_tasks: Vec<&String> = detected_tasks
.keys()
.filter(|task_id| {
!task_dependencies
.values()
.any(|deps| deps.contains(task_id))
})
.collect();
let graph_data = serde_json::json!({
"nodes": nodes,
"edges": edges,
"metadata": {
"task_count": task_count,
"edge_count": edge_count,
"has_cycles": false, "depth_levels": calculate_max_depth(task_dependencies),
"root_tasks": root_tasks,
"leaf_tasks": leaf_tasks
}
});
graph_data.to_string()
}
fn calculate_max_depth(task_dependencies: &HashMap<String, Vec<String>>) -> usize {
let mut max_depth = 0;
for task_id in task_dependencies.keys() {
let depth = calculate_task_depth(task_id, task_dependencies, &mut HashSet::new());
max_depth = max_depth.max(depth);
}
max_depth + 1 }
fn calculate_task_depth(
task_id: &str,
task_dependencies: &HashMap<String, Vec<String>>,
visited: &mut HashSet<String>,
) -> usize {
if visited.contains(task_id) {
return 0; }
visited.insert(task_id.to_string());
let dependencies = task_dependencies.get(task_id);
match dependencies {
None => 0,
Some(deps) if deps.is_empty() => 0,
Some(deps) => {
let max_dep_depth = deps
.iter()
.filter(|dep| task_dependencies.contains_key(*dep)) .map(|dep| calculate_task_depth(dep, task_dependencies, visited))
.max()
.unwrap_or(0);
max_dep_depth + 1
}
}
}