Add configurable functions for adding attributes to dotfile features
diff --git a/src/dot.rs b/src/dot.rs
index b9f44dc..be39a69 100644
--- a/src/dot.rs
+++ b/src/dot.rs
@@ -2,7 +2,7 @@
use std::fmt::{self, Display, Write};
-use crate::visit::GraphRef;
+use crate::visit::{GraphBase, GraphRef, Data, GraphProp, NodeRef, EdgeRef, IntoEdgeReferences, IntoNodeReferences, NodeIndexable};
/// `Dot` implements output to graphviz .dot format for a graph.
///
@@ -45,9 +45,14 @@
///
/// // If you need multiple config options, just list them all in the slice.
/// ```
-pub struct Dot<'a, G> {
+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"];
@@ -56,7 +61,7 @@
impl<'a, G> Dot<'a, G>
where
- G: GraphRef,
+ G: GraphRef + IntoEdgeReferences + IntoNodeReferences,
{
/// Create a `Dot` formatting wrapper with default configuration.
pub fn new(graph: G) -> Self {
@@ -65,7 +70,16 @@
/// Create a `Dot` formatting wrapper with custom configuration.
pub fn with_config(graph: G, config: &'a [Config]) -> Self {
- Dot { graph, config }
+ 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 }
}
}
@@ -80,16 +94,19 @@
EdgeIndexLabel,
/// Use no edge labels.
EdgeNoLabel,
+ /// Use no node labels.
+ NodeNoLabel,
/// Do not print the graph/digraph string.
GraphContentOnly,
#[doc(hidden)]
_Incomplete(()),
}
-use crate::visit::{Data, GraphProp, NodeRef};
-use crate::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences, NodeIndexable};
-impl<'a, G> Dot<'a, G> {
+impl<'a, G> Dot<'a, G>
+where
+ G: GraphBase + IntoNodeReferences + IntoEdgeReferences,
+{
fn graph_fmt<NF, EF, NW, EW>(
&self,
g: G,
@@ -99,7 +116,7 @@
) -> fmt::Result
where
G: NodeIndexable + IntoNodeReferences + IntoEdgeReferences,
- G: GraphProp,
+ 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,
@@ -110,34 +127,44 @@
// output all labels
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, "\"]")?;
+ 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())
+ 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::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) {
@@ -227,12 +254,72 @@
}
}
-#[test]
-fn test_escape() {
- let mut buff = String::new();
- {
- let mut e = Escaper(&mut buff);
- let _ = e.write_str("\" \\ \n");
+#[cfg(test)]
+mod test {
+ use crate::prelude::Graph;
+ use crate::visit::NodeRef;
+ use super::{Dot, Config, Escaper};
+ 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");
}
- 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");
+ }
}
diff --git a/tests/graph.rs b/tests/graph.rs
index 6a1522a..e76c04f 100644
--- a/tests/graph.rs
+++ b/tests/graph.rs
@@ -1770,8 +1770,8 @@
dot_output,
// The single \ turns into four \\\\ because of Debug which turns it to \\ and then escaping each \ to \\.
r#"digraph {
- 0 [label="Record { a: 1, b: \"abc\\\\\" }"]
- 0 -> 0 [label="(1, 2)"]
+ 0 [ label = "Record { a: 1, b: \"abc\\\\\" }" ]
+ 0 -> 0 [ label = "(1, 2)" ]
}
"#
);
diff --git a/tests/stable_graph.rs b/tests/stable_graph.rs
index 2ca04ba..82cbcc7 100644
--- a/tests/stable_graph.rs
+++ b/tests/stable_graph.rs
@@ -303,10 +303,10 @@
assert_eq!(
dot_output,
r#"digraph {
- 0 [label="x"]
- 1 [label="y"]
- 0 -> 0 [label="10"]
- 0 -> 1 [label="20"]
+ 0 [ label = "x" ]
+ 1 [ label = "y" ]
+ 0 -> 0 [ label = "10" ]
+ 0 -> 1 [ label = "20" ]
}
"#
);