diff options
Diffstat (limited to 'vendor/petgraph/src/dot.rs')
-rw-r--r-- | vendor/petgraph/src/dot.rs | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/vendor/petgraph/src/dot.rs b/vendor/petgraph/src/dot.rs new file mode 100644 index 000000000..fa31fdfe8 --- /dev/null +++ b/vendor/petgraph/src/dot.rs @@ -0,0 +1,330 @@ +//! Simple graphviz dot file format output. + +use std::fmt::{self, Display, Write}; + +use crate::visit::{ + Data, EdgeRef, GraphBase, GraphProp, GraphRef, IntoEdgeReferences, IntoNodeReferences, + NodeIndexable, NodeRef, +}; + +/// `Dot` implements output to graphviz .dot format for a graph. +/// +/// Formatting and options are rather simple, this is mostly intended +/// for debugging. Exact output may change. +/// +/// # Examples +/// +/// ``` +/// use petgraph::Graph; +/// use petgraph::dot::{Dot, Config}; +/// +/// let mut graph = Graph::<_, ()>::new(); +/// graph.add_node("A"); +/// graph.add_node("B"); +/// graph.add_node("C"); +/// graph.add_node("D"); +/// graph.extend_with_edges(&[ +/// (0, 1), (0, 2), (0, 3), +/// (1, 2), (1, 3), +/// (2, 3), +/// ]); +/// +/// println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); +/// +/// // In this case the output looks like this: +/// // +/// // digraph { +/// // 0 [label="\"A\""] +/// // 1 [label="\"B\""] +/// // 2 [label="\"C\""] +/// // 3 [label="\"D\""] +/// // 0 -> 1 +/// // 0 -> 2 +/// // 0 -> 3 +/// // 1 -> 2 +/// // 1 -> 3 +/// // 2 -> 3 +/// // } +/// +/// // If you need multiple config options, just list them all in the slice. +/// ``` +pub struct Dot<'a, G> +where + G: IntoEdgeReferences + IntoNodeReferences, +{ + 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, +} + +static TYPE: [&str; 2] = ["graph", "digraph"]; +static EDGE: [&str; 2] = ["--", "->"]; +static INDENT: &str = " "; + +impl<'a, G> Dot<'a, G> +where + G: GraphRef + IntoEdgeReferences + IntoNodeReferences, +{ + /// Create a `Dot` formatting wrapper with default configuration. + pub fn new(graph: G) -> Self { + Self::with_config(graph, &[]) + } + + /// Create a `Dot` formatting wrapper with custom configuration. + pub fn with_config(graph: G, config: &'a [Config]) -> Self { + Self::with_attr_getters(graph, config, &|_, _| "".to_string(), &|_, _| { + "".to_string() + }) + } + + 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 { + Dot { + graph, + config, + get_edge_attributes, + get_node_attributes, + } + } +} + +/// `Dot` configuration. +/// +/// This enum does not have an exhaustive definition (will be expanded) +#[derive(Debug, PartialEq, Eq)] +pub enum Config { + /// Use indices for node labels. + NodeIndexLabel, + /// Use indices for edge labels. + EdgeIndexLabel, + /// Use no edge labels. + EdgeNoLabel, + /// Use no node labels. + NodeNoLabel, + /// Do not print the graph/digraph string. + GraphContentOnly, + #[doc(hidden)] + _Incomplete(()), +} + +impl<'a, G> Dot<'a, G> +where + G: GraphBase + IntoNodeReferences + IntoEdgeReferences, +{ + 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 + GraphBase, + 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])?; + } + + // output all labels + for node in g.node_references() { + write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?; + if !self.config.contains(&Config::NodeNoLabel) { + write!(f, "label = \"")?; + if self.config.contains(&Config::NodeIndexLabel) { + write!(f, "{}", g.to_index(node.id()))?; + } else { + node_fmt(node.weight(), &mut |d| Escaped(d).fmt(f))?; + } + write!(f, "\" ")?; + } + writeln!(f, "{}]", (self.get_node_attributes)(g, node))?; + } + // output all edges + 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) { + write!(f, "label = \"")?; + if self.config.contains(&Config::EdgeIndexLabel) { + write!(f, "{}", i)?; + } else { + edge_fmt(edge.weight(), &mut |d| Escaped(d).fmt(f))?; + } + write!(f, "\" ")?; + } + writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?; + } + + 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)), + ) + } +} + +/// Escape for Graphviz +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('\\')?, + // \l is for left justified linebreak + '\n' => return self.0.write_str("\\l"), + _ => {} + } + self.0.write_char(c) + } +} + +/// Pass Display formatting through a simple escaping filter +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) + } + } +} + +/// Pass Debug formatting to Display +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) + } +} + +#[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"); + } +} |