1use crate::ir::{Module, Node, NodeId};
9use petgraph::Direction;
10use std::collections::{HashMap, HashSet};
11
12pub fn resolve_key(module: &Module, key: &str) -> Option<NodeId> {
15 module.resolve_value_path(key)
16}
17
18pub fn node_label(module: &Module, id: NodeId) -> String {
21 let mut paths: HashMap<NodeId, String> = HashMap::new();
22 for (name, binding) in &module.bindings {
23 paths.insert(*binding, name.clone());
24 }
25 for (node, path) in module.value_paths() {
26 paths.insert(node, path);
27 }
28 paths
29 .get(&id)
30 .cloned()
31 .unwrap_or_else(|| format!("node{}", id.0))
32}
33
34pub fn transitive_dependents(module: &Module, id: NodeId) -> Vec<NodeId> {
38 walk(module, id, Direction::Outgoing)
39}
40
41pub fn transitive_dependencies(module: &Module, id: NodeId) -> Vec<NodeId> {
44 walk(module, id, Direction::Incoming)
45}
46
47fn walk(module: &Module, start: NodeId, direction: Direction) -> Vec<NodeId> {
48 let Some(&start_idx) = module.node_to_graph.get(&start) else {
49 return Vec::new();
50 };
51 let mut seen = HashSet::from([start]);
52 let mut out = Vec::new();
53 let mut stack = vec![start_idx];
54 while let Some(idx) = stack.pop() {
55 let mut neighbors: Vec<_> = module.graph.neighbors_directed(idx, direction).collect();
59 neighbors.reverse();
60 for neighbor in neighbors {
61 let id = module.graph[neighbor];
62 if seen.insert(id) {
63 out.push(id);
64 stack.push(neighbor);
65 }
66 }
67 }
68 out
69}
70
71pub fn impact_paths(module: &Module, id: NodeId) -> Vec<String> {
77 let mut paths: Vec<String> = transitive_dependents(module, id)
78 .into_iter()
79 .filter(|node| !matches!(module.get(*node), Some(Node::Table { .. })))
80 .map(|node| node_label(module, node))
81 .collect();
82 paths.sort();
83 paths.dedup();
84 paths
85}
86
87pub fn why_paths(module: &Module, id: NodeId) -> Vec<String> {
89 let mut paths: Vec<String> = transitive_dependencies(module, id)
90 .into_iter()
91 .map(|node| node_label(module, node))
92 .collect();
93 paths.sort();
94 paths
95}
96
97fn render_id(id: NodeId) -> String {
99 format!("n{}", id.0)
100}
101
102fn sorted_nodes(module: &Module) -> Vec<(String, String)> {
103 let mut nodes: Vec<(String, String)> = module
104 .node_to_graph
105 .keys()
106 .map(|id| (render_id(*id), node_label(module, *id)))
107 .collect();
108 nodes.sort();
109 nodes
110}
111
112fn sorted_edges(module: &Module) -> Vec<(String, String)> {
113 let mut edges = Vec::new();
114 for edge in module.graph.raw_edges() {
115 let from = render_id(module.graph[edge.source()]);
116 let to = render_id(module.graph[edge.target()]);
117 edges.push((from, to));
118 }
119 edges.sort();
120 edges
121}
122
123pub fn to_mermaid(module: &Module) -> String {
125 let mut out = String::from("flowchart TD\n");
126 for (id, label) in sorted_nodes(module) {
127 out.push_str(&format!(" {id}[\"{label}\"]\n"));
128 }
129 for (from, to) in sorted_edges(module) {
130 out.push_str(&format!(" {from} --> {to}\n"));
131 }
132 out
133}
134
135pub fn to_dot(module: &Module) -> String {
137 fn escape(s: &str) -> String {
138 s.replace('\\', "\\\\").replace('"', "\\\"")
139 }
140 let mut out = String::from("digraph active {\n");
141 for (id, label) in sorted_nodes(module) {
142 out.push_str(&format!(" \"{id}\" [label=\"{}\"];\n", escape(&label)));
143 }
144 for (from, to) in sorted_edges(module) {
145 out.push_str(&format!(" \"{from}\" -> \"{to}\";\n"));
146 }
147 out.push_str("}\n");
148 out
149}
150
151pub fn to_graph_json(module: &Module) -> serde_json::Value {
154 let nodes: Vec<serde_json::Value> = sorted_nodes(module)
155 .into_iter()
156 .map(|(id, label)| serde_json::json!({"id": id, "label": label}))
157 .collect();
158 let edges: Vec<serde_json::Value> = sorted_edges(module)
159 .into_iter()
160 .map(|(from, to)| serde_json::json!({"from": from, "to": to}))
161 .collect();
162 serde_json::json!({"nodes": nodes, "edges": edges})
163}