use std::collections::HashMap;
use std::fmt;
pub use crate::preview::mermaid::flowchart::Direction;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Kind {
#[default]
Simple,
Start,
End,
Choice,
Fork,
Join,
Composite,
Concurrent,
Note,
}
impl Kind {
pub fn is_block(self) -> bool {
matches!(self, Kind::Composite | Kind::Concurrent)
}
pub fn is_textless(self) -> bool {
matches!(
self,
Kind::Start | Kind::End | Kind::Choice | Kind::Fork | Kind::Join | Kind::Concurrent
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NotePosition {
Left,
Right,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct State {
pub id: String,
pub label: String,
pub kind: Kind,
pub parent: Option<String>,
pub members: Vec<String>,
pub classes: Vec<String>,
pub note_position: Option<NotePosition>,
pub titled: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transition {
pub id: String,
pub from: String,
pub to: String,
pub label: Option<String>,
pub is_note_link: bool,
}
#[derive(Debug, Clone, Default)]
pub struct StateDiagram {
pub direction: Direction,
pub states: Vec<State>,
pub transitions: Vec<Transition>,
pub roots: Vec<String>,
pub title: Option<String>,
pub acc_title: Option<String>,
pub acc_descr: Option<String>,
index: HashMap<String, usize>,
}
impl StateDiagram {
pub(super) fn new(direction: Direction, title: Option<String>) -> StateDiagram {
StateDiagram {
direction,
title,
..StateDiagram::default()
}
}
pub fn state(&self, id: &str) -> Option<&State> {
self.index.get(id).and_then(|i| self.states.get(*i))
}
pub fn state_ids(&self) -> Vec<&str> {
self.states.iter().map(|s| s.id.as_str()).collect()
}
pub fn boxes(&self) -> impl Iterator<Item = &State> {
self.states.iter().filter(|s| !s.kind.is_block())
}
pub fn blocks(&self) -> impl Iterator<Item = &State> {
self.states.iter().filter(|s| s.kind.is_block())
}
pub(super) fn intern(&mut self, id: &str, kind: Kind) -> usize {
if let Some(i) = self.index.get(id) {
let i = *i;
if self.states[i].kind == Kind::Simple && kind != Kind::Simple {
self.states[i].kind = kind;
}
return i;
}
let i = self.states.len();
self.states.push(State {
id: id.to_string(),
label: String::new(),
kind,
parent: None,
members: Vec::new(),
classes: Vec::new(),
note_position: None,
titled: false,
});
self.index.insert(id.to_string(), i);
i
}
pub(super) fn state_mut(&mut self, id: &str) -> Option<&mut State> {
let i = *self.index.get(id)?;
self.states.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,
NotAStateDiagram {
header: String,
},
NoStates,
UnclosedComposite {
id: String,
line: usize,
},
UnclosedString {
line: usize,
},
UnclosedNote {
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::NotAStateDiagram { header } => {
write!(f, "not a state diagram: diagram starts with `{header}`")
}
ParseError::NoStates => write!(f, "state diagram declares no states"),
ParseError::UnclosedComposite { id, line } => {
write!(f, "unclosed `state {id} {{` opened at line {line}")
}
ParseError::UnclosedString { line } => write!(f, "unclosed `\"` at line {line}"),
ParseError::UnclosedNote { line } => {
write!(
f,
"unclosed `note` at line {line} (needs `: text` or `end note`)"
)
}
}
}
}
impl std::error::Error for ParseError {}