1use std::collections::HashMap;
2use std::fmt;
3
4pub type NodeId = String;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Direction {
10 LR,
12 RL,
14 TB,
16 BT,
18}
19
20impl Direction {
21 pub fn parse(s: &str) -> Option<Direction> {
23 match s.to_uppercase().as_str() {
24 "LR" => Some(Direction::LR),
25 "RL" => Some(Direction::RL),
26 "TB" | "TD" => Some(Direction::TB),
27 "BT" => Some(Direction::BT),
28 _ => None,
29 }
30 }
31
32 pub fn is_horizontal(&self) -> bool {
34 matches!(self, Direction::LR | Direction::RL)
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum NodeShape {
41 #[default]
43 Rectangle,
44 Rounded,
46 Circle,
48 Diamond,
50 Cylinder,
52 Stadium,
54 Subroutine,
56 Hexagon,
58 Parallelogram,
60 ParallelogramAlt,
62 Trapezoid,
64 TrapezoidAlt,
66 Table,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub enum EdgeStyle {
73 #[default]
75 Arrow,
76 Line,
78 DottedArrow,
80 DottedLine,
82 ThickArrow,
84 ThickLine,
86}
87
88#[derive(Debug, Clone)]
90pub struct Subgraph {
91 pub id: String,
92 pub label: String,
93 pub nodes: Vec<NodeId>,
94 pub x: usize,
95 pub y: usize,
96 pub width: usize,
97 pub height: usize,
98}
99
100impl Subgraph {
101 pub fn new(id: String, label: String) -> Self {
102 Self {
103 id,
104 label,
105 nodes: Vec::new(),
106 x: 0,
107 y: 0,
108 width: 0,
109 height: 0,
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct Node {
117 pub id: NodeId,
118 pub label: String,
119 pub shape: NodeShape,
120 pub subgraph: Option<String>,
121 pub width: usize,
122 pub height: usize,
123 pub x: usize,
124 pub y: usize,
125}
126
127impl Node {
128 pub fn new(id: NodeId, label: String) -> Self {
130 Self {
131 id,
132 label,
133 shape: NodeShape::default(),
134 subgraph: None,
135 width: 0,
136 height: 0,
137 x: 0,
138 y: 0,
139 }
140 }
141
142 pub fn with_shape(id: NodeId, label: String, shape: NodeShape) -> Self {
144 Self {
145 id,
146 label,
147 shape,
148 subgraph: None,
149 width: 0,
150 height: 0,
151 x: 0,
152 y: 0,
153 }
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct Edge {
160 pub from: NodeId,
161 pub to: NodeId,
162 pub label: Option<String>,
163 pub style: EdgeStyle,
164}
165
166#[derive(Debug, Clone)]
168pub struct Graph {
169 pub direction: Direction,
170 pub nodes: HashMap<NodeId, Node>,
171 pub edges: Vec<Edge>,
172 pub subgraphs: Vec<Subgraph>,
173}
174
175impl Graph {
176 pub fn new(direction: Direction) -> Self {
178 Self {
179 direction,
180 nodes: HashMap::new(),
181 edges: Vec::new(),
182 subgraphs: Vec::new(),
183 }
184 }
185}
186
187#[derive(Debug, Clone, Default)]
189pub struct RenderOptions {
190 pub ascii: bool,
192 pub max_width: Option<usize>,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
198pub enum DiagramWarning {
199 CycleDetected { nodes: Vec<String> },
201 LabelDropped {
203 marker: String,
204 edge_from: String,
205 edge_to: String,
206 label: String,
207 },
208}
209
210impl fmt::Display for DiagramWarning {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 match self {
213 DiagramWarning::CycleDetected { nodes } => {
214 write!(f, "Cycle detected involving nodes: {}", nodes.join(", "))
215 }
216 DiagramWarning::LabelDropped {
217 marker,
218 edge_from,
219 edge_to,
220 label,
221 } => {
222 write!(
223 f,
224 "Label '{}' on edge {} -> {} moved to legend as {}",
225 label, edge_from, edge_to, marker
226 )
227 }
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct RenderResult {
235 pub output: String,
237 pub warnings: Vec<DiagramWarning>,
239}