oxihuman_export/
dot_graph_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
8pub struct DotNode {
9 pub id: String,
10 pub label: String,
11 pub shape: String,
12}
13
14#[derive(Debug, Clone)]
15pub struct DotEdge {
16 pub from: String,
17 pub to: String,
18 pub label: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct DotGraph {
23 pub name: String,
24 pub directed: bool,
25 pub nodes: Vec<DotNode>,
26 pub edges: Vec<DotEdge>,
27}
28
29pub fn new_dot_graph(name: &str, directed: bool) -> DotGraph {
30 DotGraph {
31 name: name.to_string(),
32 directed,
33 nodes: Vec::new(),
34 edges: Vec::new(),
35 }
36}
37
38pub fn add_dot_node(graph: &mut DotGraph, id: &str, label: &str, shape: &str) {
39 graph.nodes.push(DotNode {
40 id: id.to_string(),
41 label: label.to_string(),
42 shape: shape.to_string(),
43 });
44}
45
46pub fn add_dot_edge(graph: &mut DotGraph, from: &str, to: &str, label: &str) {
47 graph.edges.push(DotEdge {
48 from: from.to_string(),
49 to: to.to_string(),
50 label: label.to_string(),
51 });
52}
53
54pub fn render_dot(graph: &DotGraph) -> String {
55 let kind = if graph.directed { "digraph" } else { "graph" };
56 let arrow = if graph.directed { "->" } else { "--" };
57 let mut s = format!("{} {} {{\n rankdir=TB;\n", kind, graph.name);
58 for n in &graph.nodes {
59 s.push_str(&format!(
60 " {} [label=\"{}\", shape={}];\n",
61 n.id, n.label, n.shape
62 ));
63 }
64 for e in &graph.edges {
65 let lbl = if e.label.is_empty() {
66 String::new()
67 } else {
68 format!(" [label=\"{}\"]", e.label)
69 };
70 s.push_str(&format!(" {} {} {}{};\n", e.from, arrow, e.to, lbl));
71 }
72 s.push_str("}\n");
73 s
74}
75
76pub fn export_dot(graph: &DotGraph) -> Vec<u8> {
77 render_dot(graph).into_bytes()
78}
79pub fn dot_node_count(graph: &DotGraph) -> usize {
80 graph.nodes.len()
81}
82pub fn dot_edge_count(graph: &DotGraph) -> usize {
83 graph.edges.len()
84}
85pub fn validate_dot_graph(graph: &DotGraph) -> bool {
86 !graph.name.is_empty()
87}
88pub fn dot_size_bytes(graph: &DotGraph) -> usize {
89 render_dot(graph).len()
90}
91
92pub fn scene_to_dot(nodes: &[(&str, Option<&str>)]) -> DotGraph {
93 let mut g = new_dot_graph("scene", true);
94 for (name, parent) in nodes {
95 add_dot_node(&mut g, name, name, "box");
96 if let Some(p) = parent {
97 add_dot_edge(&mut g, p, name, "");
98 }
99 }
100 g
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_new_dot_graph() {
109 let g = new_dot_graph("test", true);
110 assert_eq!(g.name, "test");
111 assert!(g.directed);
112 }
113
114 #[test]
115 fn test_add_node_edge() {
116 let mut g = new_dot_graph("g", true);
117 add_dot_node(&mut g, "a", "A", "box");
118 add_dot_edge(&mut g, "a", "b", "");
119 assert_eq!(dot_node_count(&g), 1);
120 assert_eq!(dot_edge_count(&g), 1);
121 }
122
123 #[test]
124 fn test_render_dot_contains_digraph() {
125 let g = new_dot_graph("mygraph", true);
126 let s = render_dot(&g);
127 assert!(s.contains("digraph"));
128 }
129
130 #[test]
131 fn test_render_dot_undirected() {
132 let g = new_dot_graph("g", false);
133 let s = render_dot(&g);
134 assert!(s.contains("graph"));
135 assert!(!s.contains("digraph"));
136 }
137
138 #[test]
139 fn test_export_dot_nonempty() {
140 let g = new_dot_graph("g", true);
141 assert!(!export_dot(&g).is_empty());
142 }
143
144 #[test]
145 fn test_validate_dot_graph() {
146 let g = new_dot_graph("v", true);
147 assert!(validate_dot_graph(&g));
148 }
149
150 #[test]
151 fn test_scene_to_dot() {
152 let nodes = vec![("root", None), ("child", Some("root"))];
153 let g = scene_to_dot(&nodes);
154 assert_eq!(dot_node_count(&g), 2);
155 assert_eq!(dot_edge_count(&g), 1);
156 }
157
158 #[test]
159 fn test_dot_size_bytes() {
160 let g = new_dot_graph("x", true);
161 assert!(dot_size_bytes(&g) > 0);
162 }
163}