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<'a, G> Dot<'a, 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<'a, G> fmt::Display for Dot<'a, 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<'a, G> fmt::Debug for Dot<'a, G>
216where
217 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
218 G::EdgeWeight: fmt::Debug,
219 G::NodeWeight: fmt::Debug,
220{
221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222 self.graph_fmt(f, fmt::Debug::fmt, fmt::Debug::fmt)
223 }
224}
225
226struct Escaper<W>(W);
228
229impl<W> fmt::Write for Escaper<W>
230where
231 W: fmt::Write,
232{
233 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
240 fn write_char(&mut self, c: char) -> fmt::Result {
241 match c {
242 '"' | '\\' => self.0.write_char('\\')?,
243 '\n' => return self.0.write_str("\\l"),
245 _ => {}
246 }
247 self.0.write_char(c)
248 }
249}
250
251struct Escaped<T>(T);
253
254impl<T> fmt::Display for Escaped<T>
255where
256 T: fmt::Display,
257{
258 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
267struct FnFmt<'a, T, F>(&'a T, F);
269
270impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
271where
272 F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result,
273{
274 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275 self.1(self.0, f)
276 }
277}
278
279#[cfg(test)]
280mod 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]
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
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]
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]
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]
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]
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]
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}