1#[cfg(feature = "std")]
3use std::fmt::{self, Display, Write};
4
5#[cfg(not(feature = "std"))]
6use core::fmt::{self, Display, Write};
7
8#[cfg(not(feature = "std"))]
9use alloc::string::{String, ToString};
10
11use crate::visit::{
12 Data, EdgeRef, GraphBase, GraphProp, GraphRef, IntoEdgeReferences, IntoNodeReferences,
13 NodeIndexable, NodeRef,
14};
15
16pub struct Dot<'a, G>
58where
59 G: IntoEdgeReferences + IntoNodeReferences,
60{
61 graph: G,
62 config: &'a [Config],
63 get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
64 get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
65}
66
67static TYPE: [&str; 2] = ["graph", "digraph"];
68static EDGE: [&str; 2] = ["--", "->"];
69static INDENT: &str = " ";
70
71impl<'a, G> Dot<'a, G>
72where
73 G: GraphRef + IntoEdgeReferences + IntoNodeReferences,
74{
75 pub fn new(graph: G) -> Self {
77 Self::with_config(graph, &[])
78 }
79
80 pub fn with_config(graph: G, config: &'a [Config]) -> Self {
82 Self::with_attr_getters(graph, config, &|_, _| "".to_string(), &|_, _| {
83 "".to_string()
84 })
85 }
86
87 pub fn with_attr_getters(
88 graph: G,
89 config: &'a [Config],
90 get_edge_attributes: &'a dyn Fn(G, G::EdgeRef) -> String,
91 get_node_attributes: &'a dyn Fn(G, G::NodeRef) -> String,
92 ) -> Self {
93 Dot {
94 graph,
95 config,
96 get_edge_attributes,
97 get_node_attributes,
98 }
99 }
100}
101
102#[derive(Debug, PartialEq, Eq)]
106pub enum Config {
107 NodeIndexLabel,
109 EdgeIndexLabel,
111 EdgeNoLabel,
113 NodeNoLabel,
115 GraphContentOnly,
117 #[doc(hidden)]
118 _Incomplete(()),
119}
120
121impl<'a, G> Dot<'a, G>
122where
123 G: GraphBase + IntoNodeReferences + IntoEdgeReferences,
124{
125 fn graph_fmt<NF, EF, NW, EW>(
126 &self,
127 g: G,
128 f: &mut fmt::Formatter,
129 mut node_fmt: NF,
130 mut edge_fmt: EF,
131 ) -> fmt::Result
132 where
133 G: NodeIndexable + IntoNodeReferences + IntoEdgeReferences,
134 G: GraphProp + GraphBase,
135 G: Data<NodeWeight = NW, EdgeWeight = EW>,
136 NF: FnMut(&NW, &mut dyn FnMut(&dyn Display) -> fmt::Result) -> fmt::Result,
137 EF: FnMut(&EW, &mut dyn FnMut(&dyn Display) -> fmt::Result) -> fmt::Result,
138 {
139 if !self.config.contains(&Config::GraphContentOnly) {
140 writeln!(f, "{} {{", TYPE[g.is_directed() as usize])?;
141 }
142
143 for node in g.node_references() {
145 write!(f, "{}{} [ ", INDENT, g.to_index(node.id()),)?;
146 if !self.config.contains(&Config::NodeNoLabel) {
147 write!(f, "label = \"")?;
148 if self.config.contains(&Config::NodeIndexLabel) {
149 write!(f, "{}", g.to_index(node.id()))?;
150 } else {
151 node_fmt(node.weight(), &mut |d| Escaped(d).fmt(f))?;
152 }
153 write!(f, "\" ")?;
154 }
155 writeln!(f, "{}]", (self.get_node_attributes)(g, node))?;
156 }
157 for (i, edge) in g.edge_references().enumerate() {
159 write!(
160 f,
161 "{}{} {} {} [ ",
162 INDENT,
163 g.to_index(edge.source()),
164 EDGE[g.is_directed() as usize],
165 g.to_index(edge.target()),
166 )?;
167 if !self.config.contains(&Config::EdgeNoLabel) {
168 write!(f, "label = \"")?;
169 if self.config.contains(&Config::EdgeIndexLabel) {
170 write!(f, "{}", i)?;
171 } else {
172 edge_fmt(edge.weight(), &mut |d| Escaped(d).fmt(f))?;
173 }
174 write!(f, "\" ")?;
175 }
176 writeln!(f, "{}]", (self.get_edge_attributes)(g, edge))?;
177 }
178
179 if !self.config.contains(&Config::GraphContentOnly) {
180 writeln!(f, "}}")?;
181 }
182 Ok(())
183 }
184}
185
186impl<'a, G> fmt::Display for Dot<'a, G>
187where
188 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
189 G::EdgeWeight: fmt::Display,
190 G::NodeWeight: fmt::Display,
191{
192 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193 self.graph_fmt(self.graph, f, |n, cb| cb(n), |e, cb| cb(e))
194 }
195}
196
197impl<'a, G> fmt::Debug for Dot<'a, G>
198where
199 G: IntoEdgeReferences + IntoNodeReferences + NodeIndexable + GraphProp,
200 G::EdgeWeight: fmt::Debug,
201 G::NodeWeight: fmt::Debug,
202{
203 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204 self.graph_fmt(
205 self.graph,
206 f,
207 |n, cb| cb(&DebugFmt(n)),
208 |e, cb| cb(&DebugFmt(e)),
209 )
210 }
211}
212
213struct Escaper<W>(W);
215
216impl<W> fmt::Write for Escaper<W>
217where
218 W: fmt::Write,
219{
220 fn write_str(&mut self, s: &str) -> fmt::Result {
221 for c in s.chars() {
222 self.write_char(c)?;
223 }
224 Ok(())
225 }
226
227 fn write_char(&mut self, c: char) -> fmt::Result {
228 match c {
229 '"' | '\\' => self.0.write_char('\\')?,
230 '\n' => return self.0.write_str("\\l"),
232 _ => {}
233 }
234 self.0.write_char(c)
235 }
236}
237
238struct Escaped<T>(T);
240
241impl<T> fmt::Display for Escaped<T>
242where
243 T: fmt::Display,
244{
245 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246 if f.alternate() {
247 writeln!(&mut Escaper(f), "{:#}", &self.0)
248 } else {
249 write!(&mut Escaper(f), "{}", &self.0)
250 }
251 }
252}
253
254struct DebugFmt<T>(T);
256
257impl<T> fmt::Display for DebugFmt<T>
258where
259 T: fmt::Debug,
260{
261 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262 self.0.fmt(f)
263 }
264}
265
266#[cfg(test)]
267mod test {
268 use super::{Config, Dot, Escaper};
269 use crate::prelude::Graph;
270 use crate::visit::NodeRef;
271
272 #[cfg(feature = "std")]
273 use std::fmt::Write;
274
275 #[cfg(not(feature = "std"))]
276 use core::fmt::Write;
277
278 #[cfg(not(feature = "std"))]
279 use alloc::string::String;
280
281 #[test]
282 fn test_escape() {
283 let mut buff = String::new();
284 {
285 let mut e = Escaper(&mut buff);
286 let _ = e.write_str("\" \\ \n");
287 }
288 assert_eq!(buff, "\\\" \\\\ \\l");
289 }
290
291 fn simple_graph() -> Graph<&'static str, &'static str> {
292 let mut graph = Graph::<&str, &str>::new();
293 let a = graph.add_node("A");
294 let b = graph.add_node("B");
295 graph.add_edge(a, b, "edge_label");
296 graph
297 }
298
299 #[test]
300 fn test_nodeindexlable_option() {
301 let graph = simple_graph();
302 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeIndexLabel]));
303 assert_eq!(dot, "digraph {\n 0 [ label = \"0\" ]\n 1 [ label = \"1\" ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n");
304 }
305
306 #[test]
307 fn test_edgeindexlable_option() {
308 let graph = simple_graph();
309 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeIndexLabel]));
310 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ label = \"0\" ]\n}\n");
311 }
312
313 #[test]
314 fn test_edgenolable_option() {
315 let graph = simple_graph();
316 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
317 assert_eq!(dot, "digraph {\n 0 [ label = \"\\\"A\\\"\" ]\n 1 [ label = \"\\\"B\\\"\" ]\n 0 -> 1 [ ]\n}\n");
318 }
319
320 #[test]
321 fn test_nodenolable_option() {
322 let graph = simple_graph();
323 let dot = format!("{:?}", Dot::with_config(&graph, &[Config::NodeNoLabel]));
324 assert_eq!(
325 dot,
326 "digraph {\n 0 [ ]\n 1 [ ]\n 0 -> 1 [ label = \"\\\"edge_label\\\"\" ]\n}\n"
327 );
328 }
329
330 #[test]
331 fn test_with_attr_getters() {
332 let graph = simple_graph();
333 let dot = format!(
334 "{:?}",
335 Dot::with_attr_getters(
336 &graph,
337 &[Config::NodeNoLabel, Config::EdgeNoLabel],
338 &|_, er| format!("label = \"{}\"", er.weight().to_uppercase()),
339 &|_, nr| format!("label = \"{}\"", nr.weight().to_lowercase()),
340 ),
341 );
342 assert_eq!(dot, "digraph {\n 0 [ label = \"a\"]\n 1 [ label = \"b\"]\n 0 -> 1 [ label = \"EDGE_LABEL\"]\n}\n");
343 }
344}