#![cfg_attr(not(feature = "render_graph"), allow(dead_code))]
use std::borrow::Cow;
pub struct DotGraph {
buffer: String,
}
fn escape_quote(input: &str) -> Cow<'_, str> {
if input.contains('"') {
Cow::Owned(input.replace('"', "\\\""))
} else {
Cow::Borrowed(input)
}
}
fn escape_id(input: &str) -> Cow<'_, str> {
if input.starts_with('<') && input.ends_with('>') {
input.into()
} else {
format!("\"{}\"", escape_quote(input)).into()
}
}
fn format_attributes(attrs: &[(&str, &str)]) -> String {
let attrs: Vec<_> = attrs
.iter()
.map(|(a, b)| format!("{}={}", escape_id(a), escape_id(b)))
.collect();
let attrs = attrs.join(", ");
format!("[{}]", attrs)
}
pub fn font_tag(text: &str, color: &str, size: u8) -> String {
if text.is_empty() {
return "".to_string();
}
format!(
"<FONT COLOR=\"{}\" POINT-SIZE=\"{}\">{}</FONT>",
color,
size,
html_escape(text)
)
}
pub fn html_escape(input: &str) -> String {
input
.replace('&', "&")
.replace('\"', """)
.replace('<', "<")
.replace('>', ">")
}
impl DotGraph {
pub fn new(name: &str, kind: &str, attrs: &[(&str, &str)]) -> DotGraph {
let mut dot = DotGraph {
buffer: String::new(),
};
dot.write(format!("{} {} {{", kind, escape_id(name)));
for (key, val) in attrs {
dot.write(format!("\t{}={};", escape_id(key), escape_id(val)));
}
dot
}
pub fn digraph(name: &str, options: &[(&str, &str)]) -> DotGraph {
DotGraph::new(name, "digraph", options)
}
pub fn subgraph(name: &str, options: &[(&str, &str)]) -> DotGraph {
DotGraph::new(&format!("cluster{}", name), "subgraph", options)
}
#[allow(dead_code)]
pub fn graph_attributes(mut self, attrs: &[(&str, &str)]) -> Self {
self.write(format!("\tgraph {};", format_attributes(attrs)));
self
}
pub fn edge_attributes(mut self, attrs: &[(&str, &str)]) -> Self {
self.write(format!("\tedge {};", format_attributes(attrs)));
self
}
pub fn node_attributes(mut self, attrs: &[(&str, &str)]) -> Self {
self.write(format!("\tnode {};", format_attributes(attrs)));
self
}
#[allow(unused)]
pub fn same_rank<I, S>(&mut self, nodes: I)
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.write_no_newline("{ rank = same;");
for item in nodes {
self.write(item);
self.write("; ");
}
self.write("}");
}
pub fn finish(mut self) -> String {
self.write("}");
self.buffer
}
pub fn add_sub_graph(&mut self, graph: DotGraph) {
let subgraph = graph.finish().replace('\n', "\n\t");
self.write_no_newline("\t");
self.write(subgraph);
}
pub fn add_node(&mut self, id: &str, attrs: &[(&str, &str)]) {
self.write(format!("\t{} {}", escape_id(id), format_attributes(attrs)));
}
pub fn add_invisible_node(&mut self, id: &str) {
self.add_node(
id,
&[
("style", "invis"),
("label", ""),
("height", "0"),
("shape", "point"),
],
);
}
pub fn add_edge(&mut self, from: &str, to: &str, attrs: &[(&str, &str)]) {
self.add_edge_with_ports(from, None, to, None, attrs);
}
pub fn add_edge_with_ports(
&mut self,
from: &str,
from_port: Option<&str>,
to: &str,
to_port: Option<&str>,
attrs: &[(&str, &str)],
) {
let from = if let Some(from_port) = from_port {
format!("{}:{}", escape_id(from), escape_id(from_port))
} else {
escape_id(from).to_string()
};
let to = if let Some(to_port) = to_port {
format!("{}:{}", escape_id(to), escape_id(to_port))
} else {
escape_id(to).to_string()
};
self.write(format!(
"\t{} -> {} {}",
&from,
&to,
format_attributes(attrs)
));
}
fn write_no_newline(&mut self, text: impl AsRef<str>) {
self.buffer.push_str(text.as_ref());
}
fn write(&mut self, text: impl AsRef<str>) {
self.buffer.push_str(text.as_ref());
self.buffer.push('\n');
}
}