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