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