use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ParticipantKind {
#[default]
Participant,
Actor,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Participant {
pub id: String,
pub label: String,
pub kind: ParticipantKind,
pub created_at: Option<usize>,
pub destroyed_at: Option<usize>,
pub box_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ParticipantBox {
pub id: String,
pub title: String,
pub color: Option<String>,
pub members: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Line {
#[default]
Solid,
Dotted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Head {
#[default]
None,
Arrow,
Cross,
Async,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Signal {
pub line: Line,
pub start: Head,
pub end: Head,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Message {
pub from: String,
pub to: String,
pub text: String,
pub signal: Signal,
pub line: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Placement {
LeftOf,
RightOf,
#[default]
Over,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Note {
pub actors: Vec<String>,
pub placement: Placement,
pub text: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockKind {
Loop,
Alt,
Opt,
Par,
ParOver,
Critical,
Break,
Rect,
Try,
}
impl BlockKind {
pub fn keyword(self) -> &'static str {
match self {
BlockKind::Loop => "loop",
BlockKind::Alt => "alt",
BlockKind::Opt => "opt",
BlockKind::Par | BlockKind::ParOver => "par",
BlockKind::Critical => "critical",
BlockKind::Break => "break",
BlockKind::Rect => "rect",
BlockKind::Try => "try",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SectionKind {
Else,
And,
Option,
Catch,
Finally,
}
impl SectionKind {
pub fn keyword(self) -> &'static str {
match self {
SectionKind::Else => "else",
SectionKind::And => "and",
SectionKind::Option => "option",
SectionKind::Catch => "catch",
SectionKind::Finally => "finally",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum AutoNumber {
On {
start: f64,
step: f64,
},
#[default]
Off,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Message(Message),
Note(Note),
Activate(String),
Deactivate(String),
BlockStart {
kind: BlockKind,
title: String,
},
Section {
kind: SectionKind,
title: String,
},
BlockEnd,
AutoNumber(AutoNumber),
Create(String),
Destroy(String),
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SequenceDiagram {
pub title: Option<String>,
pub acc_title: Option<String>,
pub acc_descr: Option<String>,
pub participants: Vec<Participant>,
pub boxes: Vec<ParticipantBox>,
pub events: Vec<Event>,
pub(super) index: HashMap<String, usize>,
}
impl SequenceDiagram {
pub fn participant(&self, id: &str) -> Option<&Participant> {
self.index.get(id).map(|i| &self.participants[*i])
}
pub fn has(&self, id: &str) -> bool {
self.index.contains_key(id)
}
pub fn push_front(&mut self, participant: Participant) {
if self.index.contains_key(&participant.id) {
return;
}
self.participants.insert(0, participant);
self.index = self
.participants
.iter()
.enumerate()
.map(|(i, p)| (p.id.clone(), i))
.collect();
}
pub fn ensure(&mut self, id: &str, kind: ParticipantKind) -> usize {
if let Some(i) = self.index.get(id) {
return *i;
}
let i = self.participants.len();
self.participants.push(Participant {
id: id.to_string(),
label: id.to_string(),
kind,
..Participant::default()
});
self.index.insert(id.to_string(), i);
i
}
pub fn participant_mut(&mut self, id: &str) -> Option<&mut Participant> {
self.index
.get(id)
.copied()
.map(|i| &mut self.participants[i])
}
pub fn messages(&self) -> impl Iterator<Item = &Message> {
self.events.iter().filter_map(|e| match e {
Event::Message(m) => Some(m),
_ => None,
})
}
pub fn notes(&self) -> impl Iterator<Item = &Note> {
self.events.iter().filter_map(|e| match e {
Event::Note(n) => Some(n),
_ => None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
NotASequenceDiagram {
header: String,
},
Empty,
NoParticipants,
UnmatchedEnd {
line: usize,
},
UnclosedBlock {
keyword: &'static str,
line: usize,
},
SectionOutsideBlock {
keyword: &'static str,
line: usize,
},
NotActive {
name: String,
line: usize,
},
DuplicateCreate {
name: String,
line: usize,
},
MissingActor {
keyword: &'static str,
line: usize,
},
TooManyMessages {
limit: usize,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::NotASequenceDiagram { header } => {
write!(f, "not a sequence diagram: `{header}`")
}
ParseError::Empty => write!(f, "sequence diagram is empty"),
ParseError::NoParticipants => write!(f, "sequence diagram declares no participants"),
ParseError::UnmatchedEnd { line } => {
write!(f, "`end` with no open block at line {line}")
}
ParseError::UnclosedBlock { keyword, line } => {
write!(f, "`{keyword}` at line {line} was never closed with `end`")
}
ParseError::SectionOutsideBlock { keyword, line } => {
write!(f, "`{keyword}` outside any block at line {line}")
}
ParseError::NotActive { name, line } => {
write!(f, "`{name}` is not active at line {line}")
}
ParseError::DuplicateCreate { name, line } => {
write!(
f,
"`{name}` already exists and cannot be created at line {line}"
)
}
ParseError::MissingActor { keyword, line } => {
write!(f, "`{keyword}` names no participant at line {line}")
}
ParseError::TooManyMessages { limit } => {
write!(f, "sequence diagram has more than {limit} messages")
}
}
}
}
impl std::error::Error for ParseError {}