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}