use super::helpers::*;
use std::path::Path;
pub(crate) fn cmd_graph_topological_levels(file: &Path, json: bool) -> Result<(), String> {
let config = parse_and_validate(file)?;
let mut depths: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
fn calc_depth(
name: &str,
resources: &indexmap::IndexMap<String, crate::core::types::Resource>,
depths: &mut std::collections::HashMap<String, usize>,
visited: &mut std::collections::HashSet<String>,
) -> usize {
if let Some(&d) = depths.get(name) {
return d;
}
if visited.contains(name) {
return 0; }
visited.insert(name.to_string());
let res = match resources.get(name) {
Some(r) => r,
None => return 0,
};
let max_dep = res
.depends_on
.iter()
.map(|dep| calc_depth(dep, resources, depths, visited))
.max()
.unwrap_or(0);
let depth = if res.depends_on.is_empty() {
0
} else {
max_dep + 1
};
depths.insert(name.to_string(), depth);
depth
}
let resource_names: Vec<String> = config.resources.keys().cloned().collect();
for name in &resource_names {
let mut visited = std::collections::HashSet::new();
calc_depth(name, &config.resources, &mut depths, &mut visited);
}
let max_level = depths.values().max().copied().unwrap_or(0);
let mut levels: Vec<Vec<String>> = vec![Vec::new(); max_level + 1];
for (name, depth) in &depths {
levels[*depth].push(name.clone());
}
for level in &mut levels {
level.sort();
}
if json {
let items: Vec<String> = levels
.iter()
.enumerate()
.map(|(i, rs)| {
let r_items: Vec<String> = rs.iter().map(|r| format!(r#""{r}""#)).collect();
format!(
r#"{{"level":{},"resources":[{}],"count":{}}}"#,
i,
r_items.join(","),
rs.len()
)
})
.collect();
println!(r#"{{"topological_levels":[{}]}}"#, items.join(","));
} else {
println!("Topological levels ({} levels):", max_level + 1);
for (i, resources) in levels.iter().enumerate() {
if !resources.is_empty() {
println!(
" Level {} ({}): {}",
i,
resources.len(),
resources.join(", ")
);
}
}
}
Ok(())
}
pub(crate) fn cmd_graph_critical_chain(file: &Path, json: bool) -> Result<(), String> {
let config = parse_and_validate(file)?;
fn longest_path(
name: &str,
resources: &indexmap::IndexMap<String, crate::core::types::Resource>,
memo: &mut std::collections::HashMap<String, Vec<String>>,
visited: &mut std::collections::HashSet<String>,
) -> Vec<String> {
if let Some(path) = memo.get(name) {
return path.clone();
}
if visited.contains(name) {
return vec![name.to_string()];
}
visited.insert(name.to_string());
let res = match resources.get(name) {
Some(r) => r,
None => return vec![name.to_string()],
};
let mut best: Vec<String> = Vec::new();
for dep in &res.depends_on {
let path = longest_path(dep, resources, memo, visited);
if path.len() > best.len() {
best = path;
}
}
best.push(name.to_string());
memo.insert(name.to_string(), best.clone());
best
}
let mut memo = std::collections::HashMap::new();
let mut longest = Vec::new();
for name in config.resources.keys() {
let mut visited = std::collections::HashSet::new();
let path = longest_path(name, &config.resources, &mut memo, &mut visited);
if path.len() > longest.len() {
longest = path;
}
}
if json {
let items: Vec<String> = longest.iter().map(|n| format!(r#""{n}""#)).collect();
println!(
r#"{{"critical_chain":[{}],"length":{}}}"#,
items.join(","),
longest.len()
);
} else if longest.is_empty() {
println!("No dependency chains found");
} else {
println!("Critical chain ({} steps):", longest.len());
println!(" {}", longest.join(" -> "));
}
Ok(())
}
pub(crate) fn cmd_graph_parallel_groups(file: &Path, json: bool) -> Result<(), String> {
let config = parse_and_validate(file)?;
let mut depths: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
fn calc_depth_p(
name: &str,
resources: &indexmap::IndexMap<String, crate::core::types::Resource>,
depths: &mut std::collections::HashMap<String, usize>,
visited: &mut std::collections::HashSet<String>,
) -> usize {
if let Some(&d) = depths.get(name) {
return d;
}
if visited.contains(name) {
return 0;
}
visited.insert(name.to_string());
let res = match resources.get(name) {
Some(r) => r,
None => return 0,
};
let max_dep = res
.depends_on
.iter()
.map(|dep| calc_depth_p(dep, resources, depths, visited))
.max()
.unwrap_or(0);
let depth = if res.depends_on.is_empty() {
0
} else {
max_dep + 1
};
depths.insert(name.to_string(), depth);
depth
}
let names: Vec<String> = config.resources.keys().cloned().collect();
for name in &names {
let mut visited = std::collections::HashSet::new();
calc_depth_p(name, &config.resources, &mut depths, &mut visited);
}
let mut levels: std::collections::BTreeMap<usize, Vec<String>> =
std::collections::BTreeMap::new();
for (name, &depth) in &depths {
levels.entry(depth).or_default().push(name.clone());
}
for group in levels.values_mut() {
group.sort();
}
if json {
let items: Vec<String> = levels
.iter()
.map(|(level, names)| {
let name_list: Vec<String> = names.iter().map(|n| format!(r#""{n}""#)).collect();
format!(
r#"{{"level":{},"parallel":[{}]}}"#,
level,
name_list.join(",")
)
})
.collect();
println!(
r#"{{"parallel_groups":[{}],"total_levels":{}}}"#,
items.join(","),
levels.len()
);
} else if levels.is_empty() {
println!("No resources to parallelize");
} else {
println!("Parallel execution groups ({} levels):", levels.len());
for (level, names) in &levels {
println!(
" Level {} ({} parallel): {}",
level,
names.len(),
names.join(", ")
);
}
}
Ok(())
}
pub(super) use super::graph_topology_b::*;