1use std::fmt::{self, Display, Write};
4
5use crate::visit::{
6 EdgeRef, GraphProp, IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef,
7};
8
9pub struct Dot<'a, G>
51where
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
60static TYPE: [&str; 2] = ["graph", "digraph"];
61static EDGE: [&str; 2] = ["--", "->"];
62static INDENT: &str = " ";
63
64impl<'a, G> Dot<'a, G>
65where
66 G: IntoNodeReferences + IntoEdgeReferences,
67{
68 #[inline]
70 pub fn new(graph: G) -> Self {
71 Self::with_config(graph, &[])
72 }
73
74 #[inline]
76 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]
81 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#[derive(Debug, PartialEq, Eq)]
103pub enum Config {
104 NodeIndexLabel,
106 EdgeIndexLabel,
108 EdgeNoLabel,
110 NodeNoLabel,
112 GraphContentOnly,
114 #[doc(hidden)]
115 _Incomplete(()),
116}
117macro_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}
139make_config_struct!(
140 NodeIndexLabel,
141 EdgeIndexLabel,
142 EdgeNoLabel,
143 NodeNoLabel,
144 GraphContentOnly,
145);
146
147impl<G> Dot<'_, G>
148where
149 G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable + GraphProp,
150{
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 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 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
204impl<G> fmt::Display for Dot<'_, G>
205where
206 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
207 G::EdgeWeight: fmt::Display,
208 G::NodeWeight: fmt::Display,
209{
210 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211 self.graph_fmt(f, fmt::Display::fmt, fmt::Display::fmt)
212 }
213}
214
215impl<G> fmt::LowerHex for Dot<'_, G>
216where
217 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
218 G::EdgeWeight: fmt::LowerHex,
219 G::NodeWeight: fmt::LowerHex,
220{
221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222 self.graph_fmt(f, fmt::LowerHex::fmt, fmt::LowerHex::fmt)
223 }
224}
225
226impl<G> fmt::UpperHex for Dot<'_, G>
227where
228 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
229 G::EdgeWeight: fmt::UpperHex,
230 G::NodeWeight: fmt::UpperHex,
231{
232 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233 self.graph_fmt(f, fmt::UpperHex::fmt, fmt::UpperHex::fmt)
234 }
235}
236
237impl<G> fmt::Debug for Dot<'_, G>
238where
239 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
240 G::EdgeWeight: fmt::Debug,
241 G::NodeWeight: fmt::Debug,
242{
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 self.graph_fmt(f, fmt::Debug::fmt, fmt::Debug::fmt)
245 }
246}
247
248struct Escaper<W>(W);
250
251impl<W> fmt::Write for Escaper<W>
252where
253 W: fmt::Write,
254{
255 fn write_str(&mut self, s: &str) -> fmt::Result {
256 for c in s.chars() {
257 self.write_char(c)?;
258 }
259 Ok(())
260 }
261
262 fn write_char(&mut self, c: char) -> fmt::Result {
263 match c {
264 '"' | '\\' => self.0.write_char('\\')?,
265 '\n' => return self.0.write_str("\\l"),
267 _ => {}
268 }
269 self.0.write_char(c)
270 }
271}
272
273struct Escaped<T>(T);
275
276impl<T> fmt::Display for Escaped<T>
277where
278 T: fmt::Display,
279{
280 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
281 if f.alternate() {
282 writeln!(&mut Escaper(f), "{:#}", &self.0)
283 } else {
284 write!(&mut Escaper(f), "{}", &self.0)
285 }
286 }
287}
288
289struct FnFmt<'a, T, F>(&'a T, F);
291
292impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
293where
294 F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result,
295{
296 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297 self.1(self.0, f)
298 }
299}
300
301#[cfg(test)]
302mod test {
303 use super::{Config, Dot, Escaper};
304 use crate::prelude::Graph;
305 use crate::visit::NodeRef;
306 use std::fmt::Write;
307
308 #[test]
309 fn test_escape() {
310 let mut buff = String::new();
311 {
312 let mut e = Escaper(&mut buff);
313 let _ = e.write_str("\" \\ \n");
314 }
315 assert_eq!(buff, "\\\" \\\\ \\l");
316 }
317
318 fn simple_graph() -> Graph<&'static str, &'static str> {
319 let mut graph = Graph::<&str, &str>::new();
320 let a = graph.add_node("A");
321 let b = graph.add_node("B");
322 graph.add_edge(a, b, "edge_label");
323 graph
324 }
325
326 #[test]
327 fn test_nodeindexlable_option() {
328 let graph = simple_graph();
329 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel]));
330 assert_eq!(dot, "digraph {\n 0 [ label = \"0\" ]\n 1 [ label = \"1\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n");
331 }
332
333 #[test]
334 fn test_edgeindexlable_option() {
335 let graph = simple_graph();
336 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel]));
337 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"0\" ]\n}\n");
338 }
339
340 #[test]
341 fn test_edgenolable_option() {
342 let graph = simple_graph();
343 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
344 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ ]\n}\n");
345 }
346
347 #[test]
348 fn test_nodenolable_option() {
349 let graph = simple_graph();
350 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel]));
351 assert_eq!(
352 dot,
353 "digraph {\n 0 [ ]\n 1 [ ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
354 );
355 }
356
357 #[test]
358 fn test_with_attr_getters() {
359 let graph = simple_graph();
360 let dot = format!(
361 "{:?}",
362 Dot::with_attr_getters(
363 &graph,
364 &[Config::NodeNoLabel, Config::EdgeNoLabel],
365 &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()),
366 &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()),
367 ),
368 );
369 assert_eq!(dot, "digraph {\n 0 [ label = \"a\"]\n 1 [ label = \"b\"]\n 0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n");
370 }
371}