esvc_core/
dot.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// large parts of this were taken from `petgraph`
3// ref = https://github.com/petgraph/petgraph/blob/9ff688872b467d3e1b5adef19f5c52f519d3279c/src/dot.rs
4
5use crate::Graph;
6use core::fmt::{self, Formatter, Result, Write};
7
8/// A formatter which can format a graph into the .dot format,
9/// useful for debugging and visualization
10pub struct Dot<'a, Arg>(pub &'a Graph<Arg>);
11
12impl<Arg> Dot<'_, Arg> {
13    fn graph_fmt<AF>(&self, f: &mut Formatter<'_>, argfmtf: AF) -> Result
14    where
15        AF: Fn(&Arg, &mut Formatter<'_>) -> Result,
16    {
17        writeln!(f, "digraph {{")?;
18
19        // labels
20        for (h, i) in &self.0.events {
21            writeln!(
22                f,
23                "  \"{h}\" [label=\"{h}\\n{}:{}\"];",
24                i.cmd,
25                Escaped(FnFmt(&i.arg, &argfmtf)),
26                h = h,
27            )?;
28        }
29
30        // edges
31        for (h, i) in &self.0.events {
32            for dep in &i.deps {
33                writeln!(f, "  \"{}\" -> \"{}\";", h, dep)?;
34            }
35        }
36
37        // clusters
38        for (nstate, deps) in &self.0.nstates {
39            writeln!(f, "  subgraph \"cluster_{}\" {{", Escaped(&nstate))?;
40            for dep in deps {
41                writeln!(f, "  \"{}\";", dep)?;
42            }
43            writeln!(f, "  }}")?;
44        }
45
46        writeln!(f, "}}")
47    }
48}
49
50impl<Arg: fmt::Display> fmt::Display for Dot<'_, Arg> {
51    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
52        self.graph_fmt(f, fmt::Display::fmt)
53    }
54}
55
56impl<Arg: fmt::Debug> fmt::Debug for Dot<'_, Arg> {
57    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
58        self.graph_fmt(f, fmt::Debug::fmt)
59    }
60}
61
62/// Escape for Graphviz
63struct Escaper<W>(W);
64
65impl<W> fmt::Write for Escaper<W>
66where
67    W: fmt::Write,
68{
69    fn write_str(&mut self, s: &str) -> Result {
70        for c in s.chars() {
71            self.write_char(c)?;
72        }
73        Ok(())
74    }
75
76    fn write_char(&mut self, c: char) -> Result {
77        match c {
78            '"' | '\\' => self.0.write_char('\\')?,
79            // \l is for left justified linebreak
80            '\n' => return self.0.write_str("\\l"),
81            _ => {}
82        }
83        self.0.write_char(c)
84    }
85}
86
87/// Pass Display formatting through a simple escaping filter
88struct Escaped<T>(T);
89
90impl<T> fmt::Display for Escaped<T>
91where
92    T: fmt::Display,
93{
94    fn fmt(&self, f: &mut Formatter) -> Result {
95        if f.alternate() {
96            writeln!(&mut Escaper(f), "{:#}", &self.0)
97        } else {
98            write!(&mut Escaper(f), "{}", &self.0)
99        }
100    }
101}
102
103/// Format data using a specific format function
104struct FnFmt<'a, T, F>(&'a T, F);
105
106impl<'a, T, F> fmt::Display for FnFmt<'a, T, F>
107where
108    F: Fn(&'a T, &mut Formatter<'_>) -> Result,
109{
110    fn fmt(&self, f: &mut Formatter) -> Result {
111        self.1(self.0, f)
112    }
113}