1use wolf_graph::prelude::*;
2
3use crate::{EdgeAttributes, GraphAttributes, GroupAttributes, NodeAttributes};
4
5pub trait DotEncodable<GData, NData, EData>: VisitableGraph + Sized {
9 fn dot_graph_attributes(&self) -> Option<GraphAttributes> {
10 None
11 }
12
13 fn dot_node_attributes(&self, _node: &NodeID) -> Option<NodeAttributes> {
14 None
15 }
16
17 fn dot_edge_attributes(&self, _edge: &EdgeID) -> Option<EdgeAttributes> {
18 None
19 }
20
21 fn dot_is_compound(&self) -> bool {
23 false
24 }
25
26 fn dot_children(&self, _node: Option<&NodeID>) -> Nodes {
28 Nodes::new()
29 }
30
31 fn dot_is_group(&self, node: &NodeID) -> bool {
34 !self.dot_children(Some(node)).is_empty()
35 }
36
37 fn dot_group_attributes(&self, _group: &NodeID) -> Option<GroupAttributes> {
38 None
39 }
40}
41
42pub trait DotFormat<GData, NData, EData>: DotEncodable<GData, NData, EData> {
43 fn dot_format(&self) -> String {
44 let mut lines = Vec::new();
45 format_graph(self, &mut lines);
46 format_lines(&lines)
47 }
48}
49
50impl<GData, NData, EData, T> DotFormat<GData, NData, EData> for T where T: DotEncodable<GData, NData, EData> {}
52
53fn format_graph<GData, NData, EData>(
54 graph: &impl DotEncodable<GData, NData, EData>,
55 lines: &mut Vec<(usize, String)>
56) {
57 let indent = 0;
58
59 let graph_label = graph
60 .dot_graph_attributes()
61 .and_then(|attrs| attrs.label)
62 .unwrap_or_else(|| "G".to_string());
63 lines.push((indent, format!("digraph {} {{", graph_label)));
64 if graph.dot_is_compound() {
65 format_group(graph, lines, None, indent + 1);
66 } else {
67 format_nodes(graph, lines, &graph.all_nodes(), indent + 1);
68 }
69 format_edges(graph, lines, &graph.all_edges(), indent + 1);
70 lines.push((indent, "}".to_string()));
71}
72
73fn format_group<GData, NData, EData>(
74 graph: &impl DotEncodable<GData, NData, EData>,
75 lines: &mut Vec<(usize, String)>,
76 parent: Option<&NodeID>,
77 indent: usize
78) {
79 if let Some(attrs) = parent
80 .and_then(|parent| graph.dot_group_attributes(parent))
81 .and_then(|attributes| attributes.attributes(false))
82 {
83 lines.push((indent, attrs));
84 }
85 format_nodes(graph, lines, &graph.dot_children(parent), indent);
86}
87
88fn format_subgraph<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, parent: &NodeID, indent: usize) {
89 lines.push((indent, format!("subgraph cluster_{} {{", parent)));
90 format_group(graph, lines, Some(parent), indent + 1);
91 lines.push((indent, "}".to_string()));
92}
93
94fn format_nodes<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, nodes: &Nodes, indent: usize) {
95 for node in nodes {
96 if graph.dot_is_group(node) {
97 format_subgraph(graph, lines, node, indent);
98 } else {
99 format_leaf_node(graph, lines, node, indent);
100 }
101 }
102}
103
104fn format_leaf_node<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, node: &NodeID, indent: usize) {
105 let mut line_components = vec![node.to_string()];
106 line_components.push(" ".to_string());
107 if let Some(attributes) = graph.dot_node_attributes(node) {
108 if let Some(attrs) = attributes.attributes() {
109 line_components.push(attrs);
110 }
111 }
112 lines.push((indent, line_components.join("")));
113}
114
115fn format_edges<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, edges: &Edges, indent: usize) {
116 for edge in edges {
117 format_edge(graph, lines, edge, indent);
118 }
119}
120
121fn format_edge<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, edge: &EdgeID, indent: usize) {
122 let mut line_components = vec![graph.source(edge).unwrap().to_string()];
123 line_components.push(" -> ".to_string());
124 line_components.push(graph.target(edge).unwrap().to_string());
125 line_components.push(" ".to_string());
126 if let Some(attributes) = graph.dot_edge_attributes(edge) {
127 if let Some(attrs) = attributes.attributes() {
128 line_components.push(attrs);
129 }
130 }
131 lines.push((indent, line_components.join("")));
132}
133
134fn format_lines(lines: &[(usize, String)]) -> String {
135 lines
136 .iter()
137 .map(|(indent, s)| format!("{}{}", " ".repeat(indent * 4), s))
138 .collect::<Vec<_>>()
139 .join("\n")
140}