use crate::attributes::{LHEAD, LTAIL};
use crate::graph::*;
#[must_use]
pub fn render_graph(graph: &Graph) -> String {
render_root(graph, "graph", "--")
}
#[must_use]
pub fn render_digraph(graph: &Graph) -> String {
render_root(graph, "digraph", "->")
}
#[must_use]
pub fn render_strict_graph(graph: &Graph) -> String {
render_root(graph, "strict graph", "--")
}
#[must_use]
pub fn render_strict_digraph(graph: &Graph) -> String {
render_root(graph, "strict digraph", "->")
}
fn indent(mut lines: Vec<String>) -> Vec<String> {
for line in &mut lines {
*line = format!(" {}", *line);
}
lines
}
fn render_root(graph: &Graph, kind: &str, arrow: &str) -> String {
let header = format!("{kind} {{");
let subgraph = &graph.subgraphs[&ROOT];
let attributes = &graph.attributes.get(&ROOT).unwrap();
let mut result = render_group(graph, arrow, header, subgraph, attributes).join("\n");
result.push('\n');
result
}
fn render_subgraph(graph: &Graph, arrow: &str, entity: Entity) -> Vec<String> {
let header = format!("subgraph {} {{", render_entity(graph, entity));
let subgraph = &graph.subgraphs.get(&entity).unwrap();
let attributes = &graph.attributes.get(&entity).unwrap();
render_group(graph, arrow, header, subgraph, attributes)
}
fn render_group(
graph: &Graph,
arrow: &str,
header: String,
subgraph: &SubgraphInfo,
attributes: &Attributes,
) -> Vec<String> {
let attributes = render_attributes(attributes);
let nodes = subgraph
.nodes
.iter()
.map(|&entity| render_node(graph, entity))
.collect();
let edges = subgraph
.edges
.iter()
.map(|&entity| render_edge(graph, arrow, entity))
.collect();
let subgraphs = subgraph
.subgraphs
.iter()
.flat_map(|&entity| render_subgraph(graph, arrow, entity))
.collect();
[
vec![header],
indent(attributes),
indent(nodes),
indent(edges),
indent(subgraphs),
vec!["}".to_string()],
]
.concat()
}
fn render_node(graph: &Graph, entity: Entity) -> String {
let node = render_entity(graph, entity);
let attributes = render_attributes(graph.attributes.get(&entity).unwrap());
if attributes.is_empty() {
node
} else {
format!("{node} [{}]", attributes.join(", "))
}
}
fn render_edge(graph: &Graph, arrow: &str, entity: Entity) -> String {
let edge = graph.edges.get(&entity).unwrap();
let head = render_entity(graph, edge.head_node);
let tail = render_entity(graph, edge.tail_node);
let mut attributes = render_attributes(graph.attributes.get(&entity).unwrap());
let mut attributes = match edge.head_subgraph {
None => attributes,
Some(entity) => {
attributes.push(render_attribute(LHEAD, &render_entity(graph, entity)));
attributes
}
};
let attributes = match edge.tail_subgraph {
None => attributes,
Some(entity) => {
attributes.push(render_attribute(LTAIL, &render_entity(graph, entity)));
attributes
}
};
if attributes.is_empty() {
format!("{head} {arrow} {tail}")
} else {
format!("{head} {arrow} {tail} [{}]", attributes.join(", "))
}
}
fn render_entity(graph: &Graph, entity: Entity) -> String {
let max = (f64::from(graph.latest).log10() + 1.0) as usize;
match entity.kind {
Kind::Node => format!("node_{:0>1$}", entity.id, max),
Kind::Edge => format!("edge_{:0>1$}", entity.id, max),
Kind::Cluster => format!("cluster_{:0>1$}", entity.id, max),
Kind::Subgraph => format!("subgraph_{:0>1$}", entity.id, max),
}
}
fn render_attributes(attributes: &Attributes) -> Vec<String> {
let mut result: Vec<String> = attributes
.iter()
.map(|(k, v)| render_attribute(k, v))
.collect();
result.sort();
result
}
fn render_attribute(key: &str, value: &String) -> String {
format!("{key}=\"{value}\"")
}