use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Direction {
#[default]
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft,
}
impl Direction {
pub fn as_str(self) -> &'static str {
match self {
Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
}
}
pub fn parse(s: &str) -> Option<Direction> {
match s {
"TB" | "TD" => Some(Direction::TopToBottom),
"BT" => Some(Direction::BottomToTop),
"LR" => Some(Direction::LeftToRight),
"RL" => Some(Direction::RightToLeft),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Shape {
#[default]
Rect,
RoundedRect,
Stadium,
Subroutine,
Cylinder,
Circle,
DoubleCircle,
Diamond,
Hexagon,
Odd,
Trapezoid,
InvTrapezoid,
LeanRight,
LeanLeft,
Text,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Arrow {
None,
Point,
Cross,
Circle,
DoublePoint,
DoubleCross,
DoubleCircle,
Invalid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Stroke {
Normal,
Thick,
Dotted,
Invisible,
Invalid,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
pub id: String,
pub label: String,
pub shape: Shape,
pub classes: Vec<String>,
pub styles: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Edge {
pub id: String,
pub user_defined_id: bool,
pub from: String,
pub to: String,
pub arrow: Arrow,
pub stroke: Stroke,
pub length: usize,
pub label: Option<String>,
pub classes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subgraph {
pub id: String,
pub title: String,
pub members: Vec<String>,
pub direction: Option<Direction>,
pub classes: Vec<String>,
pub auto_id: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassDef {
pub name: String,
pub styles: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LinkStyleTarget {
Default,
Indices(Vec<usize>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinkStyle {
pub target: LinkStyleTarget,
pub styles: Vec<String>,
pub interpolate: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Flowchart {
pub direction: Direction,
pub nodes: Vec<Node>,
pub edges: Vec<Edge>,
pub subgraphs: Vec<Subgraph>,
pub class_defs: Vec<ClassDef>,
pub link_styles: Vec<LinkStyle>,
pub title: Option<String>,
pub curve: Option<String>,
pub acc_title: Option<String>,
pub acc_descr: Option<String>,
index: HashMap<String, usize>,
}
impl Flowchart {
pub(super) fn new(
direction: Direction,
title: Option<String>,
curve: Option<String>,
) -> Flowchart {
Flowchart {
direction,
title,
curve,
..Flowchart::default()
}
}
pub fn node(&self, id: &str) -> Option<&Node> {
self.index.get(id).and_then(|i| self.nodes.get(*i))
}
pub fn edge(&self, id: &str) -> Option<&Edge> {
self.edges.iter().find(|e| e.id == id)
}
pub fn subgraph(&self, id: &str) -> Option<&Subgraph> {
self.subgraphs.iter().find(|s| s.id == id)
}
pub fn node_ids(&self) -> Vec<&str> {
self.nodes.iter().map(|n| n.id.as_str()).collect()
}
pub(super) fn intern_node(&mut self, id: &str) -> usize {
if let Some(i) = self.index.get(id) {
return *i;
}
let i = self.nodes.len();
self.nodes.push(Node {
id: id.to_string(),
label: id.to_string(),
shape: Shape::Rect,
classes: Vec::new(),
styles: Vec::new(),
});
self.index.insert(id.to_string(), i);
i
}
pub(super) fn reindex(&mut self) {
self.index.clear();
for (i, n) in self.nodes.iter().enumerate() {
self.index.insert(n.id.clone(), i);
}
}
pub(super) fn node_mut(&mut self, id: &str) -> Option<&mut Node> {
let i = *self.index.get(id)?;
self.nodes.get_mut(i)
}
pub(super) fn edge_mut(&mut self, id: &str) -> Option<&mut Edge> {
self.edges.iter_mut().find(|e| e.id == id)
}
pub(super) fn subgraph_mut(&mut self, id: &str) -> Option<&mut Subgraph> {
self.subgraphs.iter_mut().find(|s| s.id == id)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
Empty,
NotAFlowchart {
header: String,
},
NoNodes,
UnclosedShape {
line: usize,
},
UnclosedString {
line: usize,
},
UnknownShape {
name: String,
line: usize,
},
UnclosedShapeData {
line: usize,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Empty => write!(f, "empty diagram"),
ParseError::NotAFlowchart { header } => {
write!(f, "not a flowchart: diagram starts with `{header}`")
}
ParseError::NoNodes => write!(f, "flowchart declares no nodes"),
ParseError::UnclosedShape { line } => {
write!(f, "unclosed node shape at line {line}")
}
ParseError::UnclosedString { line } => {
write!(f, "unclosed `\"` at line {line}")
}
ParseError::UnknownShape { name, line } => write!(
f,
"no such shape: `{name}` at line {line} (shape names are lowercase and use `-`)"
),
ParseError::UnclosedShapeData { line } => {
write!(f, "unclosed `@{{` at line {line}")
}
}
}
}
impl std::error::Error for ParseError {}