1use alloc::string::String;
4use core::fmt::{self, Display, Write};
5
6use crate::visit::{
7 EdgeRef, GraphProp, IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef,
8};
9
10pub struct Dot<'a, G>
52where
53 G: IntoEdgeReferences + IntoNodeReferences,
54{
55 graph: G,
56 get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
57 get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
58 config: Configs,
59}
60
61static TYPE: [&str; 2] = ["graph", "digraph"];
62static EDGE: [&str; 2] = ["--", "->"];
63static INDENT: &str = " ";
64
65impl<'a, G> Dot<'a, G>
66where
67 G: IntoNodeReferences + IntoEdgeReferences,
68{
69 #[inline]
71 pub fn new(graph: G) -> Self {
72 Self::with_config(graph, &[])
73 }
74
75 #[inline]
77 pub fn with_config(graph: G, config: &'a [Config]) -> Self {
78 Self::with_attr_getters(graph, config, &|_, _| String::new(), &|_, _| String::new())
79 }
80
81 #[inline]
82 pub fn with_attr_getters(
83 graph: G,
84 config: &'a [Config],
85 get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
86 get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
87 ) -> Self {
88 let config = Configs::extract(config);
89 Dot {
90 graph,
91 get_edge_attributes,
92 get_node_attributes,
93 config,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum RankDir {
103 TB,
105 BT,
107 LR,
109 RL,
111}
112
113#[non_exhaustive]
117#[derive(Debug, PartialEq, Eq)]
118pub enum Config {
119 NodeIndexLabel,
121 EdgeIndexLabel,
123 EdgeNoLabel,
125 NodeNoLabel,
127 GraphContentOnly,
129 RankDir(RankDir),
131}
132macro_rules! make_config_struct {
133 ($($variant:ident,)*) => {
134 #[allow(non_snake_case)]
135 #[derive(Default)]
136 struct Configs {
137 $($variant: bool,)*
138 RankDir: Option<RankDir>,
139 }
140 impl Configs {
141 #[inline]
142 fn extract(configs: &[Config]) -> Self {
143 let mut conf = Self::default();
144 for c in configs {
145 match c {
146 $(Config::$variant => conf.$variant = true,)*
147 Config::RankDir(dir) => conf.RankDir = Some(*dir),
148 }
149 }
150 conf
151 }
152 }
153 }
154}
155make_config_struct!(
156 NodeIndexLabel,
157 EdgeIndexLabel,
158 EdgeNoLabel,
159 NodeNoLabel,
160 GraphContentOnly,
161);
162
163impl<G> Dot<'_, G>
164where
165 G: IntoNodeReferences + IntoEdgeReferences + NodeIndexable + GraphProp,
166{
167 fn graph_fmt<NF, EF>(&self, f: &mut fmt::Formatter, node_fmt: NF, edge_fmt: EF) -> fmt::Result
168 where
169 NF: Fn(&G::NodeWeight, &mut fmt::Formatter) -> fmt::Result,
170 EF: Fn(&G::EdgeWeight, &mut fmt::Formatter) -> fmt::Result,
171 {
172 let g = self.graph;
173 if !self.config.GraphContentOnly {
174 writeln!(f, "{} {{", TYPE[g.is_directed() as usize])?;
175 }
176
177 if let Some(rank_dir) = &self.config.RankDir {
178 let value = match rank_dir {
179 RankDir::TB => "TB",
180 RankDir::BT => "BT",
181 RankDir::LR => "LR",
182 RankDir::RL => "RL",
183 };
184 writeln!(f, "{}rankdir=\"{}\"", INDENT, value)?;
185 }
186
187 for node in g.node_references() {
189 write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?;
190 if !self.config.NodeNoLabel {
191 write!(f, "label = \"")?;
192 if self.config.NodeIndexLabel {
193 write!(f, "{}", g.to_index(node.id()))?;
194 } else {
195 Escaped(FnFmt(node.weight(), &node_fmt)).fmt(f)?;
196 }
197 write!(f, "\" ")?;
198 }
199 writeln!(f, "{}]", (self.get_node_attributes)(g, node))?;
200 }
201 for (i, edge) in g.edge_references().enumerate() {
203 write!(
204 f,
205 "{}{} {} {} [ ",
206 INDENT,
207 g.to_index(edge.source()),
208 EDGE[g.is_directed() as usize],
209 g.to_index(edge.target()),
210 )?;
211 if !self.config.EdgeNoLabel {
212 write!(f, "label = \"")?;
213 if self.config.EdgeIndexLabel {
214 write!(f, "{}", i)?;
215 } else {
216 Escaped(FnFmt(edge.weight(), &edge_fmt)).fmt(f)?;
217 }
218 write!(f, "\" ")?;
219 }
220 writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?;
221 }
222
223 if !self.config.GraphContentOnly {
224 writeln!(f, "}}")?;
225 }
226 Ok(())
227 }
228}
229
230impl<G> fmt::Display for Dot<'_, G>
231where
232 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
233 G::EdgeWeight: fmt::Display,
234 G::NodeWeight: fmt::Display,
235{
236 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237 self.graph_fmt(f, fmt::Display::fmt, fmt::Display::fmt)
238 }
239}
240
241impl<G> fmt::LowerHex for Dot<'_, G>
242where
243 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
244 G::EdgeWeight: fmt::LowerHex,
245 G::NodeWeight: fmt::LowerHex,
246{
247 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
248 self.graph_fmt(f, fmt::LowerHex::fmt, fmt::LowerHex::fmt)
249 }
250}
251
252impl<G> fmt::UpperHex for Dot<'_, G>
253where
254 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
255 G::EdgeWeight: fmt::UpperHex,
256 G::NodeWeight: fmt::UpperHex,
257{
258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259 self.graph_fmt(f, fmt::UpperHex::fmt, fmt::UpperHex::fmt)
260 }
261}
262
263impl<G> fmt::Debug for Dot<'_, G>
264where
265 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
266 G::EdgeWeight: fmt::Debug,
267 G::NodeWeight: fmt::Debug,
268{
269 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270 self.graph_fmt(f, fmt::Debug::fmt, fmt::Debug::fmt)
271 }
272}
273
274struct Escaper<W>(W);
276
277impl<W> fmt::Write for Escaper<W>
278where
279 W: fmt::Write,
280{
281 fn write_str(&mut self, s: &str) -> fmt::Result {
282 for c in s.chars() {
283 self.write_char(c)?;
284 }
285 Ok(())
286 }
287
288 fn write_char(&mut self, c: char) -> fmt::Result {
289 match c {
290 '"' | '\\' => self.0.write_char('\\')?,
291 '\n' => return self.0.write_str("\\l"),
293 _ => {}
294 }
295 self.0.write_char(c)
296 }
297}
298
299struct Escaped<T>(T);
301
302impl<T> fmt::Display for Escaped<T>
303where
304 T: fmt::Display,
305{
306 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307 if f.alternate() {
308 writeln!(&mut Escaper(f), "{:#}", &self.0)
309 } else {
310 write!(&mut Escaper(f), "{}", &self.0)
311 }
312 }
313}
314
315struct FnFmt<'a, T, F>(&'a T, F);
317
318impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
319where
320 F: Fn(&'a T, &mut fmt::Formatter<'_>) -> fmt::Result,
321{
322 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323 self.1(self.0, f)
324 }
325}
326
327#[cfg(feature = "dot_parser")]
328#[macro_use]
329pub mod dot_parser;
330
331#[cfg(test)]
332mod test {
333 use alloc::{format, string::String};
334 use core::fmt::Write;
335
336 use super::{Config, Dot, Escaper, RankDir};
337 use crate::prelude::Graph;
338 use crate::visit::NodeRef;
339
340 #[test]
341 fn test_escape() {
342 let mut buff = String::new();
343 {
344 let mut e = Escaper(&mut buff);
345 let _ = e.write_str("\" \\ \n");
346 }
347 assert_eq!(buff, "\\\" \\\\ \\l");
348 }
349
350 fn simple_graph() -> Graph<&'static str, &'static str> {
351 let mut graph = Graph::<&str, &str>::new();
352 let a = graph.add_node("A");
353 let b = graph.add_node("B");
354 graph.add_edge(a, b, "edge_label");
355 graph
356 }
357
358 #[test]
359 fn test_nodeindexlable_option() {
360 let graph = simple_graph();
361 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel]));
362 assert_eq!(dot, "digraph {\n 0 [ label = \"0\" ]\n 1 [ label = \"1\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n");
363 }
364
365 #[test]
366 fn test_edgeindexlable_option() {
367 let graph = simple_graph();
368 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel]));
369 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"0\" ]\n}\n");
370 }
371
372 #[test]
373 fn test_edgenolable_option() {
374 let graph = simple_graph();
375 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
376 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ ]\n}\n");
377 }
378
379 #[test]
380 fn test_nodenolable_option() {
381 let graph = simple_graph();
382 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel]));
383 assert_eq!(
384 dot,
385 "digraph {\n 0 [ ]\n 1 [ ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
386 );
387 }
388
389 #[test]
390 fn test_rankdir_bt_option() {
391 let graph = simple_graph();
392 let dot = format!(
393 "{:?}",
394 Dot::with_config(&graph, &[Config::RankDir(RankDir::TB)])
395 );
396 assert_eq!(
397 dot,
398 "digraph {\n rankdir=\"TB\"\n 0 [ label = \"\\\"A\\\"\" ]\n \
399 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
400 );
401 }
402
403 #[test]
404 fn test_rankdir_tb_option() {
405 let graph = simple_graph();
406 let dot = format!(
407 "{:?}",
408 Dot::with_config(&graph, &[Config::RankDir(RankDir::BT)])
409 );
410 assert_eq!(
411 dot,
412 "digraph {\n rankdir=\"BT\"\n 0 [ label = \"\\\"A\\\"\" ]\n \
413 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
414 );
415 }
416
417 #[test]
418 fn test_rankdir_lr_option() {
419 let graph = simple_graph();
420 let dot = format!(
421 "{:?}",
422 Dot::with_config(&graph, &[Config::RankDir(RankDir::LR)])
423 );
424 assert_eq!(
425 dot,
426 "digraph {\n rankdir=\"LR\"\n 0 [ label = \"\\\"A\\\"\" ]\n \
427 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
428 );
429 }
430
431 #[test]
432 fn test_rankdir_rl_option() {
433 let graph = simple_graph();
434 let dot = format!(
435 "{:?}",
436 Dot::with_config(&graph, &[Config::RankDir(RankDir::RL)])
437 );
438 assert_eq!(
439 dot,
440 "digraph {\n rankdir=\"RL\"\n 0 [ label = \"\\\"A\\\"\" ]\n \
441 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
442 );
443 }
444
445 #[test]
446 fn test_with_attr_getters() {
447 let graph = simple_graph();
448 let dot = format!(
449 "{:?}",
450 Dot::with_attr_getters(
451 &graph,
452 &[Config::NodeNoLabel, Config::EdgeNoLabel],
453 &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()),
454 &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()),
455 ),
456 );
457 assert_eq!(dot, "digraph {\n 0 [ label = \"a\"]\n 1 [ label = \"b\"]\n 0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n");
458 }
459}