1 //! Simple graphviz dot file format output.
2 
3 use std::fmt::{self, Display, Write};
4 
5 use crate::visit::{
6     Data, EdgeRef, GraphBase, GraphProp, GraphRef, IntoEdgeReferences, IntoNodeReferences,
7     NodeIndexable, NodeRef,
8 };
9 
10 /// `Dot` implements output to graphviz .dot format for a graph.
11 ///
12 /// Formatting and options are rather simple, this is mostly intended
13 /// for debugging. Exact output may change.
14 ///
15 /// # Examples
16 ///
17 /// ```
18 /// use petgraph::Graph;
19 /// use petgraph::dot::{Dot, Config};
20 ///
21 /// let mut graph = Graph::<_, ()>::new();
22 /// graph.add_node("A");
23 /// graph.add_node("B");
24 /// graph.add_node("C");
25 /// graph.add_node("D");
26 /// graph.extend_with_edges(&[
27 ///     (0, 1), (0, 2), (0, 3),
28 ///     (1, 2), (1, 3),
29 ///     (2, 3),
30 /// ]);
31 ///
32 /// println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
33 ///
34 /// // In this case the output looks like this:
35 /// //
36 /// // digraph {
37 /// //     0 [label="\"A\""]
38 /// //     1 [label="\"B\""]
39 /// //     2 [label="\"C\""]
40 /// //     3 [label="\"D\""]
41 /// //     0 -> 1
42 /// //     0 -> 2
43 /// //     0 -> 3
44 /// //     1 -> 2
45 /// //     1 -> 3
46 /// //     2 -> 3
47 /// // }
48 ///
49 /// // If you need multiple config options, just list them all in the slice.
50 /// ```
51 pub struct Dot<'a, G>
52 where
53     G: IntoEdgeReferences + IntoNodeReferences,
54 {
55     graph: G,
56     config: &'a [Config],
57     get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
58     get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
59 }
60 
61 static TYPE: [&str; 2] = ["graph", "digraph"];
62 static EDGE: [&str; 2] = ["--", "->"];
63 static INDENT: &str = "    ";
64 
65 impl<'a, G> Dot<'a, G>
66 where
67     G: GraphRef + IntoEdgeReferences + IntoNodeReferences,
68 {
69     /// Create a `Dot` formatting wrapper with default configuration.
new(graph: G) -> Self70     pub fn new(graph: G) -> Self {
71         Self::with_config(graph, &[])
72     }
73 
74     /// Create a `Dot` formatting wrapper with custom configuration.
with_config(graph: G, config: &'a [Config]) -> Self75     pub fn with_config(graph: G, config: &'a [Config]) -> Self {
76         Self::with_attr_getters(graph, config, &|_, _| "".to_string(), &|_, _| {
77             "".to_string()
78         })
79     }
80 
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, ) -> Self81     pub fn with_attr_getters(
82         graph: G,
83         config: &'a [Config],
84         get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
85         get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
86     ) -> Self {
87         Dot {
88             graph,
89             config,
90             get_edge_attributes,
91             get_node_attributes,
92         }
93     }
94 }
95 
96 /// `Dot` configuration.
97 ///
98 /// This enum does not have an exhaustive definition (will be expanded)
99 #[derive(Debug, PartialEq, Eq)]
100 pub enum Config {
101     /// Use indices for node labels.
102     NodeIndexLabel,
103     /// Use indices for edge labels.
104     EdgeIndexLabel,
105     /// Use no edge labels.
106     EdgeNoLabel,
107     /// Use no node labels.
108     NodeNoLabel,
109     /// Do not print the graph/digraph string.
110     GraphContentOnly,
111     #[doc(hidden)]
112     _Incomplete(()),
113 }
114 
115 impl<'a, G> Dot<'a, G>
116 where
117     G: GraphBase + IntoNodeReferences + IntoEdgeReferences,
118 {
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,119     fn graph_fmt<NF, EF, NW, EW>(
120         &self,
121         g: G,
122         f: &mut fmt::Formatter,
123         mut node_fmt: NF,
124         mut edge_fmt: EF,
125     ) -> fmt::Result
126     where
127         G: NodeIndexable + IntoNodeReferences + IntoEdgeReferences,
128         G: GraphProp + GraphBase,
129         G: Data<NodeWeight = NW, EdgeWeight = EW>,
130         NF: FnMut(&NW, &mut dyn FnMut(&dyn Display) -> fmt::Result) -> fmt::Result,
131         EF: FnMut(&EW, &mut dyn FnMut(&dyn Display) -> fmt::Result) -> fmt::Result,
132     {
133         if !self.config.contains(&Config::GraphContentOnly) {
134             writeln!(f, "{} {{", TYPE[g.is_directed() as usize])?;
135         }
136 
137         // output all labels
138         for node in g.node_references() {
139             write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?;
140             if !self.config.contains(&Config::NodeNoLabel) {
141                 write!(f, "label = \"")?;
142                 if self.config.contains(&Config::NodeIndexLabel) {
143                     write!(f, "{}", g.to_index(node.id()))?;
144                 } else {
145                     node_fmt(node.weight(), &mut |d| Escaped(d).fmt(f))?;
146                 }
147                 write!(f, "\" ")?;
148             }
149             writeln!(f, "{}]", (self.get_node_attributes)(g, node))?;
150         }
151         // output all edges
152         for (i, edge) in g.edge_references().enumerate() {
153             write!(
154                 f,
155                 "{}{} {} {} [ ",
156                 INDENT,
157                 g.to_index(edge.source()),
158                 EDGE[g.is_directed() as usize],
159                 g.to_index(edge.target()),
160             )?;
161             if !self.config.contains(&Config::EdgeNoLabel) {
162                 write!(f, "label = \"")?;
163                 if self.config.contains(&Config::EdgeIndexLabel) {
164                     write!(f, "{}", i)?;
165                 } else {
166                     edge_fmt(edge.weight(), &mut |d| Escaped(d).fmt(f))?;
167                 }
168                 write!(f, "\" ")?;
169             }
170             writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?;
171         }
172 
173         if !self.config.contains(&Config::GraphContentOnly) {
174             writeln!(f, "}}")?;
175         }
176         Ok(())
177     }
178 }
179 
180 impl<'a, G> fmt::Display for Dot<'a, G>
181 where
182     G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
183     G::EdgeWeight: fmt::Display,
184     G::NodeWeight: fmt::Display,
185 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result186     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187         self.graph_fmt(self.graph, f, |n, cb| cb(n), |e, cb| cb(e))
188     }
189 }
190 
191 impl<'a, G> fmt::Debug for Dot<'a, G>
192 where
193     G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
194     G::EdgeWeight: fmt::Debug,
195     G::NodeWeight: fmt::Debug,
196 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result197     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198         self.graph_fmt(
199             self.graph,
200             f,
201             |n, cb| cb(&DebugFmt(n)),
202             |e, cb| cb(&DebugFmt(e)),
203         )
204     }
205 }
206 
207 /// Escape for Graphviz
208 struct Escaper<W>(W);
209 
210 impl<W> fmt::Write for Escaper<W>
211 where
212     W: fmt::Write,
213 {
write_str(&mut self, s: &str) -> fmt::Result214     fn write_str(&mut self, s: &str) -> fmt::Result {
215         for c in s.chars() {
216             self.write_char(c)?;
217         }
218         Ok(())
219     }
220 
write_char(&mut self, c: char) -> fmt::Result221     fn write_char(&mut self, c: char) -> fmt::Result {
222         match c {
223             '"' | '\\' => self.0.write_char('\\')?,
224             // \l is for left justified linebreak
225             '\n' => return self.0.write_str("\\l"),
226             _ => {}
227         }
228         self.0.write_char(c)
229     }
230 }
231 
232 /// Pass Display formatting through a simple escaping filter
233 struct Escaped<T>(T);
234 
235 impl<T> fmt::Display for Escaped<T>
236 where
237     T: fmt::Display,
238 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result239     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
240         if f.alternate() {
241             writeln!(&mut Escaper(f), "{:#}", &self.0)
242         } else {
243             write!(&mut Escaper(f), "{}", &self.0)
244         }
245     }
246 }
247 
248 /// Pass Debug formatting to Display
249 struct DebugFmt<T>(T);
250 
251 impl<T> fmt::Display for DebugFmt<T>
252 where
253     T: fmt::Debug,
254 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result255     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
256         self.0.fmt(f)
257     }
258 }
259 
260 #[cfg(test)]
261 mod test {
262     use super::{Config, Dot, Escaper};
263     use crate::prelude::Graph;
264     use crate::visit::NodeRef;
265     use std::fmt::Write;
266 
267     #[test]
test_escape()268     fn test_escape() {
269         let mut buff = String::new();
270         {
271             let mut e = Escaper(&mut buff);
272             let _ = e.write_str("\" \\ \n");
273         }
274         assert_eq!(buff, "\\\" \\\\ \\l");
275     }
276 
simple_graph() -> Graph<&'static str, &'static str>277     fn simple_graph() -> Graph<&'static str, &'static str> {
278         let mut graph = Graph::<&str, &str>::new();
279         let a = graph.add_node("A");
280         let b = graph.add_node("B");
281         graph.add_edge(a, b, "edge_label");
282         graph
283     }
284 
285     #[test]
test_nodeindexlable_option()286     fn test_nodeindexlable_option() {
287         let graph = simple_graph();
288         let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel]));
289         assert_eq!(dot, "digraph {\n    0 [ label = \"0\" ]\n    1 [ label = \"1\" ]\n    0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n");
290     }
291 
292     #[test]
test_edgeindexlable_option()293     fn test_edgeindexlable_option() {
294         let graph = simple_graph();
295         let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel]));
296         assert_eq!(dot, "digraph {\n    0 [ label = \"\\\"A\\\"\" ]\n    1 [ label = \"\\\"B\\\"\" ]\n    0 -> 1 [ label = \"0\" ]\n}\n");
297     }
298 
299     #[test]
test_edgenolable_option()300     fn test_edgenolable_option() {
301         let graph = simple_graph();
302         let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
303         assert_eq!(dot, "digraph {\n    0 [ label = \"\\\"A\\\"\" ]\n    1 [ label = \"\\\"B\\\"\" ]\n    0 -> 1 [ ]\n}\n");
304     }
305 
306     #[test]
test_nodenolable_option()307     fn test_nodenolable_option() {
308         let graph = simple_graph();
309         let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel]));
310         assert_eq!(
311             dot,
312             "digraph {\n    0 [ ]\n    1 [ ]\n    0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
313         );
314     }
315 
316     #[test]
test_with_attr_getters()317     fn test_with_attr_getters() {
318         let graph = simple_graph();
319         let dot = format!(
320             "{:?}",
321             Dot::with_attr_getters(
322                 &graph,
323                 &[Config::NodeNoLabel, Config::EdgeNoLabel],
324                 &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()),
325                 &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()),
326             ),
327         );
328         assert_eq!(dot, "digraph {\n    0 [ label = \"a\"]\n    1 [ label = \"b\"]\n    0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n");
329     }
330 }
331