use std::collections::HashMap;
use std::fmt;
pub use crate::preview::mermaid::flowchart::Direction;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Marker {
#[default]
None,
Extension,
Composition,
Aggregation,
Dependency,
Lollipop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Line {
#[default]
Solid,
Dotted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum MemberKind {
#[default]
Attribute,
Method,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Member {
pub visibility: Option<char>,
pub name: String,
pub kind: MemberKind,
pub parameters: String,
pub return_type: String,
pub classifier: Option<char>,
}
impl Member {
pub fn display(&self) -> String {
let mut out = String::new();
if let Some(v) = self.visibility {
out.push(v);
}
out.push_str(&self.name);
if self.kind == MemberKind::Method {
out.push('(');
out.push_str(&self.parameters);
out.push(')');
if !self.return_type.is_empty() {
out.push_str(" : ");
out.push_str(&self.return_type);
}
}
if let Some(c) = self.classifier {
out.push(c);
}
out
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Class {
pub id: String,
pub generic: String,
pub label: String,
pub annotations: Vec<String>,
pub attributes: Vec<Member>,
pub methods: Vec<Member>,
pub css_classes: Vec<String>,
pub parent: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Relation {
pub id: String,
pub from: String,
pub to: String,
pub start: Marker,
pub end: Marker,
pub line: Line,
pub label: Option<String>,
pub start_cardinality: Option<String>,
pub end_cardinality: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Note {
pub id: String,
pub text: String,
pub target: Option<String>,
pub parent: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Namespace {
pub id: String,
pub label: String,
pub members: Vec<String>,
pub parent: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ClassDiagram {
pub direction: Direction,
pub classes: Vec<Class>,
pub relations: Vec<Relation>,
pub notes: Vec<Note>,
pub namespaces: Vec<Namespace>,
pub title: Option<String>,
pub acc_title: Option<String>,
pub acc_descr: Option<String>,
index: HashMap<String, usize>,
}
impl ClassDiagram {
pub(super) fn new(direction: Direction, title: Option<String>) -> ClassDiagram {
ClassDiagram {
direction,
title,
..ClassDiagram::default()
}
}
pub fn class(&self, id: &str) -> Option<&Class> {
self.index.get(id).and_then(|i| self.classes.get(*i))
}
pub fn class_ids(&self) -> Vec<&str> {
self.classes.iter().map(|c| c.id.as_str()).collect()
}
pub(super) fn intern(&mut self, id: &str, generic: &str) -> usize {
if let Some(i) = self.index.get(id) {
return *i;
}
let i = self.classes.len();
let label = if generic.is_empty() {
id.to_string()
} else {
format!("{id}<{generic}>")
};
self.classes.push(Class {
id: id.to_string(),
generic: generic.to_string(),
label,
..Class::default()
});
self.index.insert(id.to_string(), i);
i
}
pub(super) fn class_mut(&mut self, id: &str) -> Option<&mut Class> {
let i = *self.index.get(id)?;
self.classes.get_mut(i)
}
pub(super) fn contains(&self, id: &str) -> bool {
self.index.contains_key(id)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
Empty,
NotAClassDiagram {
header: String,
},
NoClasses,
UnclosedBody {
id: String,
line: usize,
},
UnclosedString {
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::NotAClassDiagram { header } => {
write!(f, "not a class diagram: diagram starts with `{header}`")
}
ParseError::NoClasses => write!(f, "class diagram declares no classes"),
ParseError::UnclosedBody { id, line } => {
write!(f, "unclosed `{id} {{` opened at line {line}")
}
ParseError::UnclosedString { line } => write!(f, "unclosed `\"` at line {line}"),
}
}
}
impl std::error::Error for ParseError {}