rxp/
graphviz.rs

1#[derive(Copy, Clone, Debug)]
2pub enum Shape {
3    None,
4    Box,
5    Polygon,
6    Ellipse,
7    Oval,
8    Circle,
9    DoubleCircle,
10    Point,
11    Egg,
12    Triangle,
13    PlainText,
14    Plain,
15    Diamond,
16    Trapezium,
17}
18
19impl std::fmt::Display for Shape {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        write!(
22            f,
23            "{}",
24            match self {
25                Self::None => "none",
26                Self::Box => "box",
27                Self::Polygon => "polygon",
28                Self::Ellipse => "ellipse",
29                Self::Oval => "oval",
30                Self::Circle => "circle",
31                Self::DoubleCircle => "doublecircle",
32                Self::Point => "point",
33                Self::Egg => "egg",
34                Self::Triangle => "triangle",
35                Self::PlainText => "plaintext",
36                Self::Plain => "plain",
37                Self::Diamond => "diamond",
38                Self::Trapezium => "trapezium",
39            }
40        )
41    }
42}
43
44#[derive(Clone, Default, Debug)]
45pub struct Style {
46    label: Option<String>,
47    shape: Option<Shape>,
48    height: Option<f32>,
49    width: Option<f32>,
50}
51
52impl std::fmt::Display for Style {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        let label = self.label.clone().map(|s| format!("label=\"{s}\""));
55        let shape = self.shape.map(|s| format!("shape={s}"));
56        let height = self.height.map(|s| format!("height={s}"));
57        let width = self.width.map(|s| format!("width={s}"));
58
59        let s = [label, shape, height, width]
60            .into_iter()
61            .flatten()
62            .collect::<Vec<_>>()
63            .join(", ");
64
65        write!(f, "{s}")
66    }
67}
68
69impl Style {
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    pub fn label(mut self, label: impl ToString) -> Self {
75        self.label = Some(label.to_string());
76        self
77    }
78
79    pub fn shape(mut self, shape: Shape) -> Self {
80        self.shape = Some(shape);
81        self
82    }
83
84    pub fn height(mut self, height: f32) -> Self {
85        self.height = Some(height);
86        self
87    }
88
89    pub fn width(mut self, width: f32) -> Self {
90        self.width = Some(width);
91        self
92    }
93
94    pub fn is_styled(&self) -> bool {
95        self.label.is_some()
96            || self.shape.is_some()
97            || self.height.is_some()
98            || self.width.is_some()
99    }
100}
101
102#[derive(Clone, Debug)]
103pub struct Vertex {
104    id: String,
105    style: Style,
106}
107
108impl std::fmt::Display for Vertex {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        if self.style.is_styled() {
111            write!(f, "{} [{}]", self.id, self.style)
112        } else {
113            write!(f, "{}", self.id)
114        }
115    }
116}
117
118#[derive(Clone, Debug)]
119pub struct Edge {
120    from: String,
121    to: String,
122    style: Style,
123}
124
125impl std::fmt::Display for Edge {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        if self.style.is_styled() {
128            write!(f, "{} -> {} [{}]", self.from, self.to, self.style)
129        } else {
130            write!(f, "{} -> {}", self.from, self.to)
131        }
132    }
133}
134
135#[derive(Debug, Copy, Clone)]
136pub enum RankDir {
137    TopBottom,
138    BottomTop,
139    LeftRight,
140    RightLeft,
141}
142
143impl std::fmt::Display for RankDir {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(
146            f,
147            "{}",
148            match self {
149                Self::TopBottom => "TB",
150                Self::BottomTop => "BT",
151                Self::LeftRight => "LR",
152                Self::RightLeft => "RL",
153            }
154        )
155    }
156}
157
158#[derive(Debug)]
159pub struct DiGraph {
160    name: String,
161    rankdir: Option<RankDir>,
162    vertices: Vec<Vertex>,
163    edges: Vec<Edge>,
164}
165
166impl DiGraph {
167    pub fn new(name: impl ToString) -> Self {
168        Self {
169            name: name.to_string(),
170            vertices: Vec::default(),
171            rankdir: None,
172            edges: Vec::default(),
173        }
174    }
175
176    pub fn rankdir(&mut self, dir: RankDir) -> &mut Self {
177        self.rankdir = Some(dir);
178        self
179    }
180
181    pub fn vertex(&mut self, id: impl ToString, style: impl Into<Option<Style>>) -> &mut Self {
182        self.vertices.push(Vertex {
183            id: id.to_string(),
184            style: style.into().unwrap_or_default(),
185        });
186
187        self
188    }
189
190    pub fn edge(
191        &mut self,
192        from: impl ToString,
193        to: impl ToString,
194        style: impl Into<Option<Style>>,
195    ) -> &mut Self {
196        self.edges.push(Edge {
197            from: from.to_string(),
198            to: to.to_string(),
199            style: style.into().unwrap_or_default(),
200        });
201
202        self
203    }
204}
205
206impl std::fmt::Display for DiGraph {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        let vertices = self
209            .vertices
210            .iter()
211            .map(|v| v.to_string())
212            .collect::<Vec<_>>()
213            .join("\n");
214
215        let edges = self
216            .edges
217            .iter()
218            .map(|v| v.to_string())
219            .collect::<Vec<_>>()
220            .join("\n");
221
222        write!(
223            f,
224            "digraph {} {{ 
225        {}
226        {vertices}\n
227        {edges}\
228        }}",
229            self.name,
230            match self.rankdir {
231                Some(rd) => format!("rankdir={rd}"),
232                None => "".to_string(),
233            }
234        )
235    }
236}
237
238#[cfg(test)]
239mod test {
240    use super::*;
241
242    #[test]
243    fn builder_compiles() {
244        let _ = DiGraph::new("test graph")
245            .vertex("a", None)
246            .vertex("b", Style::new().label("Boo!").shape(Shape::Diamond))
247            .edge("a", "b", None);
248    }
249}