use std::fmt::{self, Display, Write};
use crate::visit::GraphRef;
pub struct Dot<'a, G> {
graph: G,
config: &'a [Config],
}
static TYPE: [&str; 2] = ["graph", "digraph"];
static EDGE: [&str; 2] = ["--", "->"];
static INDENT: &str = " ";
impl<'a, G> Dot<'a, G>
where
G: GraphRef,
{
pub fn new(graph: G) -> Self {
Self::with_config(graph, &[])
}
pub fn with_config(graph: G, config: &'a [Config]) -> Self {
Dot { graph, config }
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Config {
NodeIndexLabel,
EdgeIndexLabel,
EdgeNoLabel,
GraphContentOnly,
#[doc(hidden)]
_Incomplete(()),
}
use crate::visit::{Data, GraphProp, NodeRef};
use crate::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences, NodeIndexable};
impl<'a, G> Dot<'a, G> {
fn graph_fmt<NF, EF, NW, EW>(
&self,
g: G,
f: &mut fmt::Formatter,
mut node_fmt: NF,
mut edge_fmt: EF,
) -> fmt::Result
where
G: NodeIndexable + IntoNodeReferences + IntoEdgeReferences,
G: GraphProp,
G: Data<NodeWeight = NW, EdgeWeight = EW>,
NF: FnMut(&NW, &mut dyn FnMut(&dyn Display) -> fmt::Result) -> fmt::Result,
EF: FnMut(&EW, &mut dyn FnMut(&dyn Display) -> fmt::Result) -> fmt::Result,
{
if !self.config.contains(&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.contains(&Config::NodeIndexLabel) {
writeln!(f)?;
} else {
write!(f, " [label=\"")?;
node_fmt(node.weight(), &mut |d| Escaped(d).fmt(f))?;
writeln!(f, "\"]")?;
}
}
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.contains(&Config::EdgeNoLabel) {
writeln!(f)?;
} else if self.config.contains(&Config::EdgeIndexLabel) {
writeln!(f, " [label=\"{}\"]", i)?;
} else {
write!(f, " [label=\"")?;
edge_fmt(edge.weight(), &mut |d| Escaped(d).fmt(f))?;
writeln!(f, "\"]")?;
}
}
if !self.config.contains(&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(self.graph, f, |n, cb| cb(n), |e, cb| cb(e))
}
}
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(
self.graph,
f,
|n, cb| cb(&DebugFmt(n)),
|e, cb| cb(&DebugFmt(e)),
)
}
}
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 DebugFmt<T>(T);
impl<T> fmt::Display for DebugFmt<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[test]
fn test_escape() {
let mut buff = String::new();
{
let mut e = Escaper(&mut buff);
let _ = e.write_str("\" \\ \n");
}
assert_eq!(buff, "\\\" \\\\ \\l");
}