|
1 | 1 | use anyhow::{anyhow, Context, Result}; |
2 | 2 | use camino::Utf8PathBuf; |
3 | | -use petgraph::graphmap::DiGraphMap; |
| 3 | +use petgraph::graph::DiGraph; |
4 | 4 | use serde_json::json; |
| 5 | +use std::collections::HashMap; |
5 | 6 | use walkdir::WalkDir; |
6 | 7 |
|
7 | 8 | use crate::model::SeamRecord; |
@@ -39,46 +40,41 @@ pub fn run(root: Option<&str>, format: &str, out: Option<&str>) -> Result<()> { |
39 | 40 | return Err(anyhow!("no seam records found")); |
40 | 41 | } |
41 | 42 |
|
42 | | - let mut g: DiGraphMap<String, String> = DiGraphMap::new(); // edge weight = seam id |
| 43 | + let mut g = DiGraph::<String, String>::new(); |
| 44 | + let mut node_map: HashMap<String, petgraph::graph::NodeIndex> = HashMap::new(); |
43 | 45 | let mut seams = vec![]; |
44 | 46 |
|
45 | 47 | for f in files { |
46 | 48 | let raw = std::fs::read_to_string(&f).with_context(|| format!("read {f}"))?; |
47 | 49 | let rec: SeamRecord = serde_json::from_str(&raw).with_context(|| format!("decode {f}"))?; |
48 | | - g.add_node(rec.side_a.clone()); |
49 | | - g.add_node(rec.side_b.clone()); |
50 | | - g.add_edge(rec.side_a.clone(), rec.side_b.clone(), rec.id.clone()); |
51 | | - seams.push(rec.id); |
52 | | - } |
53 | 50 |
|
54 | | - // Cycle detection (simple): if any node reachable to itself via DFS in a small graph, flag. |
55 | | - // In v0: warnings only. |
56 | | - let mut warnings = vec![]; |
57 | | - for n in g.nodes() { |
58 | | - // naive: if there's a path back, petgraph doesn't give easy on graphmap; skip deep analysis v0. |
59 | | - let _ = n; |
| 51 | + let a_idx = *node_map.entry(rec.side_a.clone()).or_insert_with(|| g.add_node(rec.side_a.clone())); |
| 52 | + let b_idx = *node_map.entry(rec.side_b.clone()).or_insert_with(|| g.add_node(rec.side_b.clone())); |
| 53 | + g.add_edge(a_idx, b_idx, rec.id.clone()); |
| 54 | + seams.push(rec.id); |
60 | 55 | } |
61 | 56 |
|
62 | | - if !warnings.is_empty() { |
63 | | - eprintln!("Warnings:"); |
64 | | - for w in warnings { |
65 | | - eprintln!("- {w}"); |
66 | | - } |
67 | | - } |
| 57 | + let components: Vec<&String> = g.node_weights().collect(); |
| 58 | + let edges: Vec<serde_json::Value> = g.edge_indices().map(|ei| { |
| 59 | + let (a, b) = g.edge_endpoints(ei).unwrap(); |
| 60 | + let id = &g[ei]; |
| 61 | + json!({"from": g[a], "to": g[b], "seam": id}) |
| 62 | + }).collect(); |
68 | 63 |
|
69 | 64 | let payload = json!({ |
70 | | - "components": g.nodes().collect::<Vec<_>>(), |
71 | | - "edges": g.all_edges().map(|(a,b,id)| json!({"from":a,"to":b,"seam":id})).collect::<Vec<_>>(), |
| 65 | + "components": components, |
| 66 | + "edges": edges, |
72 | 67 | "seams": seams, |
73 | 68 | }); |
74 | 69 |
|
75 | 70 | let rendered = if format == "json" { |
76 | 71 | serde_json::to_string_pretty(&payload)? |
77 | 72 | } else if format == "dot" { |
78 | | - // Minimal DOT emission |
79 | 73 | let mut s = String::from("digraph seams {\n"); |
80 | | - for (a, b, id) in g.all_edges() { |
81 | | - s.push_str(&format!(" \"{}\" -> \"{}\" [label=\"{}\"];\n", a, b, id)); |
| 74 | + for ei in g.edge_indices() { |
| 75 | + let (a, b) = g.edge_endpoints(ei).unwrap(); |
| 76 | + let id = &g[ei]; |
| 77 | + s.push_str(&format!(" \"{}\" -> \"{}\" [label=\"{}\"];\n", g[a], g[b], id)); |
82 | 78 | } |
83 | 79 | s.push_str("}\n"); |
84 | 80 | s |
|
0 commit comments