use wolf_graph::prelude::*;
use crate::{EdgeAttributes, GraphAttributes, GroupAttributes, NodeAttributes};
pub trait DotEncodable<GData, NData, EData>: VisitableGraph + Sized {
fn dot_graph_attributes(&self) -> Option<GraphAttributes> {
None
}
fn dot_node_attributes(&self, _node: &NodeID) -> Option<NodeAttributes> {
None
}
fn dot_edge_attributes(&self, _edge: &EdgeID) -> Option<EdgeAttributes> {
None
}
fn dot_is_compound(&self) -> bool {
false
}
fn dot_children(&self, _node: Option<&NodeID>) -> Nodes {
Nodes::new()
}
fn dot_is_group(&self, node: &NodeID) -> bool {
!self.dot_children(Some(node)).is_empty()
}
fn dot_group_attributes(&self, _group: &NodeID) -> Option<GroupAttributes> {
None
}
}
pub trait DotFormat<GData, NData, EData>: DotEncodable<GData, NData, EData> {
fn dot_format(&self) -> String {
let mut lines = Vec::new();
format_graph(self, &mut lines);
format_lines(&lines)
}
}
impl<GData, NData, EData, T> DotFormat<GData, NData, EData> for T where T: DotEncodable<GData, NData, EData> {}
fn format_graph<GData, NData, EData>(
graph: &impl DotEncodable<GData, NData, EData>,
lines: &mut Vec<(usize, String)>
) {
let indent = 0;
let graph_label = graph
.dot_graph_attributes()
.and_then(|attrs| attrs.label)
.unwrap_or_else(|| "G".to_string());
lines.push((indent, format!("digraph {} {{", graph_label)));
if graph.dot_is_compound() {
format_group(graph, lines, None, indent + 1);
} else {
format_nodes(graph, lines, &graph.all_nodes(), indent + 1);
}
format_edges(graph, lines, &graph.all_edges(), indent + 1);
lines.push((indent, "}".to_string()));
}
fn format_group<GData, NData, EData>(
graph: &impl DotEncodable<GData, NData, EData>,
lines: &mut Vec<(usize, String)>,
parent: Option<&NodeID>,
indent: usize
) {
if let Some(attrs) = parent
.and_then(|parent| graph.dot_group_attributes(parent))
.and_then(|attributes| attributes.attributes(false))
{
lines.push((indent, attrs));
}
format_nodes(graph, lines, &graph.dot_children(parent), indent);
}
fn format_subgraph<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, parent: &NodeID, indent: usize) {
lines.push((indent, format!("subgraph cluster_{} {{", parent)));
format_group(graph, lines, Some(parent), indent + 1);
lines.push((indent, "}".to_string()));
}
fn format_nodes<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, nodes: &Nodes, indent: usize) {
for node in nodes {
if graph.dot_is_group(node) {
format_subgraph(graph, lines, node, indent);
} else {
format_leaf_node(graph, lines, node, indent);
}
}
}
fn format_leaf_node<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, node: &NodeID, indent: usize) {
let mut line_components = vec![node.to_string()];
line_components.push(" ".to_string());
if let Some(attributes) = graph.dot_node_attributes(node) {
if let Some(attrs) = attributes.attributes() {
line_components.push(attrs);
}
}
lines.push((indent, line_components.join("")));
}
fn format_edges<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, edges: &Edges, indent: usize) {
for edge in edges {
format_edge(graph, lines, edge, indent);
}
}
fn format_edge<GData, NData, EData>(graph: &impl DotEncodable<GData, NData, EData>, lines: &mut Vec<(usize, String)>, edge: &EdgeID, indent: usize) {
let mut line_components = vec![graph.source(edge).unwrap().to_string()];
line_components.push(" -> ".to_string());
line_components.push(graph.target(edge).unwrap().to_string());
line_components.push(" ".to_string());
if let Some(attributes) = graph.dot_edge_attributes(edge) {
if let Some(attrs) = attributes.attributes() {
line_components.push(attrs);
}
}
lines.push((indent, line_components.join("")));
}
fn format_lines(lines: &[(usize, String)]) -> String {
lines
.iter()
.map(|(indent, s)| format!("{}{}", " ".repeat(indent * 4), s))
.collect::<Vec<_>>()
.join("\n")
}