1#![allow(dead_code)]
7
8#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct DotNode {
12 pub id: String,
13 pub label: String,
14 pub shape: String,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct DotEdge {
21 pub from: String,
22 pub to: String,
23 pub label: String,
24}
25
26#[allow(dead_code)]
28#[derive(Debug, Clone, Default)]
29pub struct DotExport {
30 pub name: String,
31 pub directed: bool,
32 pub nodes: Vec<DotNode>,
33 pub edges: Vec<DotEdge>,
34}
35
36#[allow(dead_code)]
38pub fn new_dot_export(name: &str, directed: bool) -> DotExport {
39 DotExport {
40 name: name.to_string(),
41 directed,
42 nodes: Vec::new(),
43 edges: Vec::new(),
44 }
45}
46
47#[allow(dead_code)]
49pub fn add_dot_node(graph: &mut DotExport, id: &str, label: &str, shape: &str) {
50 graph.nodes.push(DotNode {
51 id: id.to_string(),
52 label: label.to_string(),
53 shape: shape.to_string(),
54 });
55}
56
57#[allow(dead_code)]
59pub fn add_dot_edge(graph: &mut DotExport, from: &str, to: &str, label: &str) {
60 graph.edges.push(DotEdge {
61 from: from.to_string(),
62 to: to.to_string(),
63 label: label.to_string(),
64 });
65}
66
67#[allow(dead_code)]
69pub fn dot_node_count(graph: &DotExport) -> usize {
70 graph.nodes.len()
71}
72
73#[allow(dead_code)]
75pub fn dot_edge_count(graph: &DotExport) -> usize {
76 graph.edges.len()
77}
78
79#[allow(dead_code)]
81pub fn to_dot_string(graph: &DotExport) -> String {
82 let kw = if graph.directed { "digraph" } else { "graph" };
83 let arrow = if graph.directed { " -> " } else { " -- " };
84 let mut out = format!("{} {} {{\n", kw, graph.name);
85 for node in &graph.nodes {
86 out.push_str(&format!(
87 " {} [label=\"{}\", shape={}];\n",
88 node.id, node.label, node.shape
89 ));
90 }
91 for edge in &graph.edges {
92 let lbl = if edge.label.is_empty() {
93 String::new()
94 } else {
95 format!(" [label=\"{}\"]", edge.label)
96 };
97 out.push_str(&format!(" {}{}{}{};\n", edge.from, arrow, edge.to, lbl));
98 }
99 out.push('}');
100 out
101}
102
103#[allow(dead_code)]
105pub fn find_dot_node<'a>(graph: &'a DotExport, id: &str) -> Option<&'a DotNode> {
106 graph.nodes.iter().find(|n| n.id == id)
107}
108
109#[allow(dead_code)]
111pub fn export_skeleton_dot(bones: &[(&str, Option<&str>)]) -> String {
112 let mut graph = new_dot_export("skeleton", true);
113 for &(bone, parent) in bones {
114 add_dot_node(&mut graph, bone, bone, "ellipse");
115 if let Some(p) = parent {
116 add_dot_edge(&mut graph, p, bone, "");
117 }
118 }
119 to_dot_string(&graph)
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_new_dot_export_empty() {
128 let g = new_dot_export("g", true);
129 assert_eq!(dot_node_count(&g), 0);
130 }
131
132 #[test]
133 fn test_add_node() {
134 let mut g = new_dot_export("g", true);
135 add_dot_node(&mut g, "n1", "Node 1", "box");
136 assert_eq!(dot_node_count(&g), 1);
137 }
138
139 #[test]
140 fn test_add_edge() {
141 let mut g = new_dot_export("g", true);
142 add_dot_edge(&mut g, "a", "b", "dep");
143 assert_eq!(dot_edge_count(&g), 1);
144 }
145
146 #[test]
147 fn test_to_dot_directed() {
148 let g = new_dot_export("mygraph", true);
149 let s = to_dot_string(&g);
150 assert!(s.contains("digraph"));
151 }
152
153 #[test]
154 fn test_to_dot_undirected() {
155 let g = new_dot_export("g", false);
156 let s = to_dot_string(&g);
157 assert!(s.contains("graph"));
158 }
159
160 #[test]
161 fn test_to_dot_contains_node() {
162 let mut g = new_dot_export("g", true);
163 add_dot_node(&mut g, "n1", "MyNode", "box");
164 let s = to_dot_string(&g);
165 assert!(s.contains("n1"));
166 }
167
168 #[test]
169 fn test_to_dot_contains_edge() {
170 let mut g = new_dot_export("g", true);
171 add_dot_edge(&mut g, "a", "b", "");
172 let s = to_dot_string(&g);
173 assert!(s.contains("->"));
174 }
175
176 #[test]
177 fn test_find_dot_node() {
178 let mut g = new_dot_export("g", true);
179 add_dot_node(&mut g, "hip", "Hip", "ellipse");
180 let n = find_dot_node(&g, "hip");
181 assert!(n.is_some());
182 }
183
184 #[test]
185 fn test_export_skeleton_dot() {
186 let bones = vec![("root", None), ("hip", Some("root")), ("knee", Some("hip"))];
187 let s = export_skeleton_dot(&bones);
188 assert!(s.contains("digraph"));
189 assert!(s.contains("root"));
190 }
191
192 #[test]
193 fn test_find_missing_node() {
194 let g = new_dot_export("g", true);
195 assert!(find_dot_node(&g, "missing").is_none());
196 }
197}