use std::collections::HashMap;
use std::fmt;
pub type NodeId = String;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
LR,
RL,
TB,
BT,
}
impl Direction {
pub fn parse(s: &str) -> Option<Direction> {
match s.to_uppercase().as_str() {
"LR" => Some(Direction::LR),
"RL" => Some(Direction::RL),
"TB" | "TD" => Some(Direction::TB),
"BT" => Some(Direction::BT),
_ => None,
}
}
pub fn is_horizontal(&self) -> bool {
matches!(self, Direction::LR | Direction::RL)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NodeShape {
#[default]
Rectangle,
Rounded,
Circle,
Diamond,
Cylinder,
Stadium,
Subroutine,
Hexagon,
Parallelogram,
ParallelogramAlt,
Trapezoid,
TrapezoidAlt,
Table,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EdgeStyle {
#[default]
Arrow,
Line,
DottedArrow,
DottedLine,
ThickArrow,
ThickLine,
}
#[derive(Debug, Clone)]
pub struct Subgraph {
pub id: String,
pub label: String,
pub nodes: Vec<NodeId>,
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
}
impl Subgraph {
pub fn new(id: String, label: String) -> Self {
Self {
id,
label,
nodes: Vec::new(),
x: 0,
y: 0,
width: 0,
height: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct Node {
pub id: NodeId,
pub label: String,
pub shape: NodeShape,
pub subgraph: Option<String>,
pub width: usize,
pub height: usize,
pub x: usize,
pub y: usize,
}
impl Node {
pub fn new(id: NodeId, label: String) -> Self {
Self {
id,
label,
shape: NodeShape::default(),
subgraph: None,
width: 0,
height: 0,
x: 0,
y: 0,
}
}
pub fn with_shape(id: NodeId, label: String, shape: NodeShape) -> Self {
Self {
id,
label,
shape,
subgraph: None,
width: 0,
height: 0,
x: 0,
y: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Edge {
pub from: NodeId,
pub to: NodeId,
pub label: Option<String>,
pub style: EdgeStyle,
}
#[derive(Debug, Clone)]
pub struct Graph {
pub direction: Direction,
pub nodes: HashMap<NodeId, Node>,
pub edges: Vec<Edge>,
pub subgraphs: Vec<Subgraph>,
}
impl Graph {
pub fn new(direction: Direction) -> Self {
Self {
direction,
nodes: HashMap::new(),
edges: Vec::new(),
subgraphs: Vec::new(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RenderOptions {
pub ascii: bool,
pub max_width: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DiagramWarning {
CycleDetected { nodes: Vec<String> },
LabelDropped {
marker: String,
edge_from: String,
edge_to: String,
label: String,
},
}
impl fmt::Display for DiagramWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DiagramWarning::CycleDetected { nodes } => {
write!(f, "Cycle detected involving nodes: {}", nodes.join(", "))
}
DiagramWarning::LabelDropped {
marker,
edge_from,
edge_to,
label,
} => {
write!(
f,
"Label '{}' on edge {} -> {} moved to legend as {}",
label, edge_from, edge_to, marker
)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderResult {
pub output: String,
pub warnings: Vec<DiagramWarning>,
}