use std::fmt::{self, Display, Write};
use crate::visit::{
EdgeRef, GraphProp, IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef,
};
pub struct Dot<'a, G>
where
G: IntoEdgeReferences + IntoNodeReferences,
{
graph: G,
get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
config: Configs,
}
static TYPE: [&str; 2] = ["graph", "digraph"];
static EDGE: [&str; 2] = ["--", "->"];
static INDENT: &str = " ";
impl<'a, G> Dot<'a, G>
where
G: IntoNodeReferences + IntoEdgeReferences,
{
#[inline]
pub fn new(graph: G) -> Self {
Self::with_config(graph, &[])
}
#[inline]
pub fn with_config(graph: G, config: &'a [Config]) -> Self {
Self::with_attr_getters(graph, config, &|_, _| String::new(), &|_, _| String::new())
}
#[inline]
pub fn with_attr_getters(
graph: G,
config: &'a [Config],
get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
) -> Self {
let config = Configs::extract(config);
Dot {
graph,
get_edge_attributes,
get_node_attributes,
config,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Config {
NodeIndexLabel,
EdgeIndexLabel,
EdgeNoLabel,
NodeNoLabel,
GraphContentOnly,
#[doc(hidden)]
_Incomplete(()),
}
macro_rules! make_config_struct {
($($variant:ident,)*) => {
#[allow(non_snake_case)]
#[derive(Default)]
struct Configs {
$($variant: bool,)*
}
impl Configs {
#[inline]
fn extract(configs: &[Config]) -> Self {
let mut conf = Self::default();
for c in configs {
match *c {
$(Config::$variant => conf.$variant = true,)*
Config::_Incomplete(()) => {}
}
}
conf
}
}
}
}
make_config_struct!(
NodeIndexLabel,
EdgeIndexLabel,
EdgeNoLabel,
NodeNoLabel,
GraphContentOnly,
);
impl<'a, G> Dot<'a, G>
where
G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable + GraphProp,
{
fn graph_fmt<NF, EF>(&self, f: &mut fmt::Formatter, node_fmt: NF, edge_fmt: EF) -> fmt::Result
where
NF: Fn(&G::NodeWeight, &mut fmt::Formatter) -> fmt::Result,
EF: Fn(&G::EdgeWeight, &mut fmt::Formatter) -> fmt::Result,
{
let g = self.graph;
if !self.config.GraphContentOnly {
writeln!(f, "{} {{", TYPE[g.is_directed() as usize])?;
}
for node in g.node_references() {
write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?;
if !self.config.NodeNoLabel {
write!(f, "label = \"")?;
if self.config.NodeIndexLabel {
write!(f, "{}", g.to_index(node.id()))?;
} else {
Escaped(FnFmt(node.weight(), &node_fmt)).fmt(f)?;
}
write!(f, "\" ")?;
}
writeln!(f, "{}]", (self.get_node_attributes)(g, node))?;
}
for (i, edge) in g.edge_references().enumerate() {
write!(
f,
"{}{} {} {} [ ",
INDENT,
g.to_index(edge.source()),
EDGE[g.is_directed() as usize],
g.to_index(edge.target()),
)?;
if !self.config.EdgeNoLabel {
write!(f, "label = \"")?;
if self.config.EdgeIndexLabel {
write!(f, "{}", i)?;
} else {
Escaped(FnFmt(edge.weight(), &edge_fmt)).fmt(f)?;
}
write!(f, "\" ")?;
}
writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?;
}
if !self.config.GraphContentOnly {
writeln!(f, "}}")?;
}
Ok(())
}
}
impl<'a, G> fmt::Display for Dot<'a, G>
where
G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
G::EdgeWeight: fmt::Display,
G::NodeWeight: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.graph_fmt(f, fmt::Display::fmt, fmt::Display::fmt)
}
}
impl<'a, G> fmt::Debug for Dot<'a, G>
where
G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
G::EdgeWeight: fmt::Debug,
G::NodeWeight: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.graph_fmt(f, fmt::Debug::fmt, fmt::Debug::fmt)
}
}
struct Escaper<W>(W);
impl<W> fmt::Write for Escaper<W>
where
W: fmt::Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.write_char(c)?;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
match c {
'"' | '\\' => self.0.write_char('\\')?,
'\n' => return self.0.write_str("\\l"),
_ => {}
}
self.0.write_char(c)
}
}
struct Escaped<T>(T);
impl<T> fmt::Display for Escaped<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
writeln!(&mut Escaper(f), "{:#}", &self.0)
} else {
write!(&mut Escaper(f), "{}", &self.0)
}
}
}
struct FnFmt<'a, T, F>(&'a T, F);
impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
where
F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.1(self.0, f)
}
}
#[cfg(test)]
mod test {
use super::{Config, Dot, Escaper};
use crate::prelude::Graph;
use crate::visit::NodeRef;
use std::fmt::Write;
#[test]
fn test_escape() {
let mut buff = String::new();
{
let mut e = Escaper(&mut buff);
let _ = e.write_str("\" \\ \n");
}
assert_eq!(buff, "\\\" \\\\ \\l");
}
fn simple_graph() -> Graph<&'static str, &'static str> {
let mut graph = Graph::<&str, &str>::new();
let a = graph.add_node("A");
let b = graph.add_node("B");
graph.add_edge(a, b, "edge_label");
graph
}
#[test]
fn test_nodeindexlable_option() {
let graph = simple_graph();
let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel]));
assert_eq!(dot, "digraph {\n 0 [ label = \"0\" ]\n 1 [ label = \"1\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n");
}
#[test]
fn test_edgeindexlable_option() {
let graph = simple_graph();
let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel]));
assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"0\" ]\n}\n");
}
#[test]
fn test_edgenolable_option() {
let graph = simple_graph();
let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ ]\n}\n");
}
#[test]
fn test_nodenolable_option() {
let graph = simple_graph();
let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel]));
assert_eq!(
dot,
"digraph {\n 0 [ ]\n 1 [ ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
);
}
#[test]
fn test_with_attr_getters() {
let graph = simple_graph();
let dot = format!(
"{:?}",
Dot::with_attr_getters(
&graph,
&[Config::NodeNoLabel, Config::EdgeNoLabel],
&|_, er| format!("label = \"{}\"", er.weight().to_uppercase()),
&|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()),
),
);
assert_eq!(dot, "digraph {\n 0 [ label = \"a\"]\n 1 [ label = \"b\"]\n 0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n");
}
}