use crate::detect::{Detected, DiagramType, detect_diagram};
use crate::diagrams;
use crate::errors::{Error, ErrorCode};
use crate::layout::Options;
use crate::prepare::{ParseOptions, Prepared, prepare};
use crate::preprocess::preprocess;
use crate::scene::{Kind, Scene};
use crate::value::Map;
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug)]
enum Model {
#[cfg(feature = "flowchart")]
Flowchart(diagrams::flowchart::FlowDb),
#[cfg(feature = "pie")]
Pie(diagrams::pie::PieModel),
#[cfg(feature = "er")]
Er(diagrams::er::ErDb),
#[cfg(feature = "mindmap")]
Mindmap(diagrams::mindmap::MindmapDb),
#[cfg(feature = "kanban")]
Kanban(diagrams::kanban::KanbanDb),
#[cfg(feature = "timeline")]
Timeline(diagrams::timeline::TimelineDb),
#[cfg(feature = "journey")]
Journey(diagrams::journey::JourneyDb),
#[cfg(feature = "class")]
Class(diagrams::class::ClassDb),
#[cfg(feature = "quadrant")]
QuadrantChart(diagrams::quadrant::QuadrantDb),
#[cfg(feature = "xychart")]
XyChart(diagrams::xychart::XyChartDb),
#[cfg(feature = "state")]
State(diagrams::state::StateDb),
#[cfg(feature = "gantt")]
Gantt(diagrams::gantt::GanttDb),
#[cfg(feature = "architecture")]
Architecture(diagrams::architecture::ArchitectureDb),
#[cfg(feature = "sankey")]
Sankey(diagrams::sankey::SankeyDb),
#[cfg(feature = "packet")]
Packet(diagrams::packet::PacketModel),
#[cfg(feature = "radar")]
Radar(diagrams::radar::RadarModel),
#[cfg(feature = "treemap")]
Treemap(diagrams::treemap::TreemapModel),
#[cfg(feature = "info")]
Info(diagrams::info::InfoModel),
#[cfg(feature = "requirement")]
Requirement(diagrams::requirement::RequirementDb),
#[cfg(feature = "sequence")]
Sequence(diagrams::sequence::SeqDb),
#[cfg(feature = "gitgraph")]
GitGraph(diagrams::gitgraph::GitModel),
#[cfg(feature = "c4")]
C4(diagrams::c4::C4Db),
#[cfg(feature = "block")]
Block(diagrams::block::BlockDb),
#[cfg(feature = "railroad")]
Railroad(diagrams::railroad::RailroadDb),
#[cfg(feature = "agentflow")]
AgentFlow(diagrams::agentflow::AgentflowDb),
#[cfg(feature = "treeview")]
TreeView(diagrams::treeview::TreeViewModel),
#[cfg(feature = "cynefin")]
Cynefin(diagrams::cynefin::CynefinModel),
#[cfg(feature = "wardley")]
Wardley(diagrams::wardley::WardleyModel),
#[cfg(feature = "eventmodeling")]
EventModeling(diagrams::eventmodeling::EventModelingModel),
#[cfg(feature = "usecase")]
UseCase(diagrams::usecase::UsecaseModel),
#[cfg(feature = "venn")]
Venn(diagrams::venn::VennDb),
#[cfg(feature = "ishikawa")]
Ishikawa(diagrams::ishikawa::IshikawaDb),
#[cfg(feature = "swimlane")]
Swimlane(diagrams::flowchart::FlowDb),
}
const TYPES: &[DiagramType] = &[
#[cfg(feature = "flowchart")]
DiagramType::Flowchart,
#[cfg(feature = "pie")]
DiagramType::Pie,
#[cfg(feature = "er")]
DiagramType::Er,
#[cfg(feature = "mindmap")]
DiagramType::Mindmap,
#[cfg(feature = "kanban")]
DiagramType::Kanban,
#[cfg(feature = "timeline")]
DiagramType::Timeline,
#[cfg(feature = "journey")]
DiagramType::Journey,
#[cfg(feature = "class")]
DiagramType::Class,
#[cfg(feature = "quadrant")]
DiagramType::QuadrantChart,
#[cfg(feature = "xychart")]
DiagramType::XyChart,
#[cfg(feature = "state")]
DiagramType::State,
#[cfg(feature = "gantt")]
DiagramType::Gantt,
#[cfg(feature = "architecture")]
DiagramType::Architecture,
#[cfg(feature = "requirement")]
DiagramType::Requirement,
#[cfg(feature = "sankey")]
DiagramType::Sankey,
#[cfg(feature = "packet")]
DiagramType::Packet,
#[cfg(feature = "radar")]
DiagramType::Radar,
#[cfg(feature = "treemap")]
DiagramType::Treemap,
#[cfg(feature = "info")]
DiagramType::Info,
#[cfg(feature = "sequence")]
DiagramType::Sequence,
#[cfg(feature = "gitgraph")]
DiagramType::GitGraph,
#[cfg(feature = "c4")]
DiagramType::C4,
#[cfg(feature = "block")]
DiagramType::Block,
#[cfg(feature = "railroad")]
DiagramType::Railroad,
#[cfg(feature = "agentflow")]
DiagramType::AgentFlow,
#[cfg(feature = "treeview")]
DiagramType::TreeView,
#[cfg(feature = "cynefin")]
DiagramType::Cynefin,
#[cfg(feature = "wardley")]
DiagramType::Wardley,
#[cfg(feature = "eventmodeling")]
DiagramType::EventModeling,
#[cfg(feature = "usecase")]
DiagramType::UseCase,
#[cfg(feature = "venn")]
DiagramType::Venn,
#[cfg(feature = "ishikawa")]
DiagramType::Ishikawa,
#[cfg(feature = "swimlane")]
DiagramType::Swimlane,
];
pub fn types() -> &'static [DiagramType] {
TYPES
}
pub fn supports(src: &str) -> bool {
preprocess(src, None)
.ok()
.and_then(|pre| detect_diagram(&pre.text))
.is_some_and(|found| TYPES.contains(&found.diagram))
}
#[derive(Clone, Debug)]
pub struct Diagram {
found: Detected,
config: Map,
title: Option<String>,
model: Model,
}
pub fn parse(src: &str, options: &ParseOptions) -> Result<Diagram, Error> {
let p = prepare(src, options)?;
let model = Model::read(&p)?;
Ok(Diagram {
found: p.found,
config: p.config,
title: p.title,
model,
})
}
impl Model {
fn read(p: &Prepared) -> Result<Model, Error> {
match p.found.diagram {
#[cfg(feature = "flowchart")]
DiagramType::Flowchart => p
.parse_with(false, |s| {
diagrams::flowchart::parse(s, &p.config, p.title.as_deref(), p.max_edges)
})
.map(Model::Flowchart),
#[cfg(feature = "pie")]
DiagramType::Pie => p
.parse_with(false, |s| diagrams::pie::parse(s, p.title.as_deref()))
.map(Model::Pie),
#[cfg(feature = "er")]
DiagramType::Er => p
.parse_with(false, |s| diagrams::er::parse(s, p.title.as_deref()))
.map(Model::Er),
#[cfg(feature = "mindmap")]
DiagramType::Mindmap => p
.parse_with(false, |s| diagrams::mindmap::parse(s, p.title.as_deref()))
.map(Model::Mindmap),
#[cfg(feature = "kanban")]
DiagramType::Kanban => p
.parse_with(false, |s| diagrams::kanban::parse(s, p.title.as_deref()))
.map(Model::Kanban),
#[cfg(feature = "timeline")]
DiagramType::Timeline => p
.parse_with(false, |s| diagrams::timeline::parse(s, p.title.as_deref()))
.map(Model::Timeline),
#[cfg(feature = "journey")]
DiagramType::Journey => p
.parse_with(false, |s| diagrams::journey::parse(s, p.title.as_deref()))
.map(Model::Journey),
#[cfg(feature = "class")]
DiagramType::Class => p
.parse_with(false, |s| diagrams::class::parse(s, p.title.as_deref()))
.map(Model::Class),
#[cfg(feature = "quadrant")]
DiagramType::QuadrantChart => p
.parse_with(false, |s| diagrams::quadrant::parse(s, p.title.as_deref()))
.map(Model::QuadrantChart),
#[cfg(feature = "xychart")]
DiagramType::XyChart => p
.parse_with(false, |s| diagrams::xychart::parse(s, p.title.as_deref()))
.map(Model::XyChart),
#[cfg(feature = "state")]
DiagramType::State => p
.parse_with(false, |s| diagrams::state::parse(s, p.title.as_deref()))
.map(Model::State),
#[cfg(feature = "gantt")]
DiagramType::Gantt => p
.parse_with(false, |s| {
diagrams::gantt::parse(s, &p.config, p.title.as_deref())
})
.map(Model::Gantt),
#[cfg(feature = "architecture")]
DiagramType::Architecture => p
.parse_with(false, |s| {
diagrams::architecture::parse(s, p.title.as_deref())
})
.map(Model::Architecture),
#[cfg(feature = "sankey")]
DiagramType::Sankey => p
.parse_with(false, |s| diagrams::sankey::parse(s, p.title.as_deref()))
.map(Model::Sankey),
#[cfg(feature = "packet")]
DiagramType::Packet => p
.parse_with(false, |s| {
diagrams::packet::parse(s, &p.config, p.title.as_deref())
})
.map(Model::Packet),
#[cfg(feature = "radar")]
DiagramType::Radar => p
.parse_with(false, |s| diagrams::radar::parse(s, p.title.as_deref()))
.map(Model::Radar),
#[cfg(feature = "treemap")]
DiagramType::Treemap => p
.parse_with(false, |s| diagrams::treemap::parse(s, p.title.as_deref()))
.map(Model::Treemap),
#[cfg(feature = "info")]
DiagramType::Info => p
.parse_with(false, |s| diagrams::info::parse(s, p.title.as_deref()))
.map(Model::Info),
#[cfg(feature = "requirement")]
DiagramType::Requirement => p
.parse_with(false, |s| {
diagrams::requirement::parse(s, p.title.as_deref())
})
.map(Model::Requirement),
#[cfg(feature = "sequence")]
DiagramType::Sequence => p
.parse_with(false, |s| {
diagrams::sequence::parse(s, &p.config, p.title.as_deref())
})
.map(Model::Sequence),
#[cfg(feature = "gitgraph")]
DiagramType::GitGraph => p
.parse_with(false, |s| {
diagrams::gitgraph::parse(s, &p.config, p.title.as_deref())
})
.map(Model::GitGraph),
#[cfg(feature = "c4")]
DiagramType::C4 => p
.parse_with(false, |s| diagrams::c4::parse(s, p.title.as_deref()))
.map(Model::C4),
#[cfg(feature = "block")]
DiagramType::Block => p
.parse_with(false, |s| diagrams::block::parse(s, p.title.as_deref()))
.map(Model::Block),
#[cfg(feature = "railroad")]
DiagramType::Railroad => p
.parse_with(false, |s| diagrams::railroad::parse(s, p.title.as_deref()))
.map(Model::Railroad),
#[cfg(feature = "agentflow")]
DiagramType::AgentFlow => p
.parse_with(true, |s| {
diagrams::agentflow::parse(
s,
&p.config,
p.title.as_deref(),
p.line_offset,
p.max_edges,
)
})
.map(Model::AgentFlow),
#[cfg(feature = "treeview")]
DiagramType::TreeView => p
.parse_with(false, |s| diagrams::treeview::parse(s, p.title.as_deref()))
.map(Model::TreeView),
#[cfg(feature = "cynefin")]
DiagramType::Cynefin => p
.parse_with(false, |s| diagrams::cynefin::parse(s, p.title.as_deref()))
.map(Model::Cynefin),
#[cfg(feature = "wardley")]
DiagramType::Wardley => p
.parse_with(false, |s| diagrams::wardley::parse(s, p.title.as_deref()))
.map(Model::Wardley),
#[cfg(feature = "eventmodeling")]
DiagramType::EventModeling => p
.parse_with(true, |s| {
diagrams::eventmodeling::parse(s, p.title.as_deref())
})
.map(Model::EventModeling),
#[cfg(feature = "usecase")]
DiagramType::UseCase => p
.parse_with(false, |s| diagrams::usecase::parse(s, p.title.as_deref()))
.map(Model::UseCase),
#[cfg(feature = "venn")]
DiagramType::Venn => p
.parse_with(false, |s| diagrams::venn::parse(s, p.title.as_deref()))
.map(Model::Venn),
#[cfg(feature = "ishikawa")]
DiagramType::Ishikawa => p
.parse_with(false, |s| diagrams::ishikawa::parse(s, p.title.as_deref()))
.map(Model::Ishikawa),
#[cfg(feature = "swimlane")]
DiagramType::Swimlane => p
.parse_with(false, |s| {
diagrams::swimlane::parse(s, &p.config, p.title.as_deref(), p.max_edges)
})
.map(Model::Swimlane),
#[allow(unreachable_patterns)]
_ => {
let message = format!("Diagram type \"{}\" is not supported yet.", p.found.diagram);
Err(Error::new(message, ErrorCode::UnsupportedDiagram).of(p.found))
}
}
}
#[allow(unused_variables, unreachable_code)]
fn layout(&self, config: &Map, o: &Options) -> Result<Scene, Error> {
let sc = match *self {
#[cfg(feature = "flowchart")]
Model::Flowchart(ref db) => diagrams::flowchart::layout(db, config, o),
#[cfg(feature = "pie")]
Model::Pie(ref m) => diagrams::pie::layout(m, config, o),
#[cfg(feature = "er")]
Model::Er(ref m) => diagrams::er::layout(m, config, o),
#[cfg(feature = "mindmap")]
Model::Mindmap(ref m) => diagrams::mindmap::layout(m, config, o),
#[cfg(feature = "kanban")]
Model::Kanban(ref m) => diagrams::kanban::layout(m, config, o),
#[cfg(feature = "timeline")]
Model::Timeline(ref m) => diagrams::timeline::layout(m, config, o),
#[cfg(feature = "journey")]
Model::Journey(ref m) => diagrams::journey::layout(m, config, o),
#[cfg(feature = "class")]
Model::Class(ref m) => diagrams::class::layout(m, config, o),
#[cfg(feature = "quadrant")]
Model::QuadrantChart(ref m) => diagrams::quadrant::layout(m, config, o),
#[cfg(feature = "xychart")]
Model::XyChart(ref m) => diagrams::xychart::layout(m, config, o)?,
#[cfg(feature = "state")]
Model::State(ref m) => diagrams::state::layout(m, config, o),
#[cfg(feature = "gantt")]
Model::Gantt(ref m) => diagrams::gantt::layout(m, config, o)?,
#[cfg(feature = "architecture")]
Model::Architecture(ref m) => diagrams::architecture::layout(m, config, o),
#[cfg(feature = "sankey")]
Model::Sankey(ref m) => diagrams::sankey::layout(m, config, o)?,
#[cfg(feature = "packet")]
Model::Packet(ref m) => diagrams::packet::layout(m, config, o),
#[cfg(feature = "radar")]
Model::Radar(ref m) => diagrams::radar::layout(m, config, o),
#[cfg(feature = "treemap")]
Model::Treemap(ref m) => diagrams::treemap::layout(m, config, o),
#[cfg(feature = "info")]
Model::Info(ref m) => diagrams::info::layout(m, config, o),
#[cfg(feature = "requirement")]
Model::Requirement(ref db) => diagrams::requirement::layout(db, config, o),
#[cfg(feature = "sequence")]
Model::Sequence(ref m) => diagrams::sequence::layout(m, config, o),
#[cfg(feature = "gitgraph")]
Model::GitGraph(ref m) => diagrams::gitgraph::layout(m, config, o),
#[cfg(feature = "c4")]
Model::C4(ref m) => diagrams::c4::layout(m, config, o)?,
#[cfg(feature = "block")]
Model::Block(ref m) => diagrams::block::layout(m, config, o)?,
#[cfg(feature = "railroad")]
Model::Railroad(ref m) => diagrams::railroad::layout(m, config, o),
#[cfg(feature = "agentflow")]
Model::AgentFlow(ref m) => diagrams::agentflow::layout(m, config, o),
#[cfg(feature = "treeview")]
Model::TreeView(ref m) => diagrams::treeview::layout(m, config, o),
#[cfg(feature = "cynefin")]
Model::Cynefin(ref m) => diagrams::cynefin::layout(m, config, o),
#[cfg(feature = "wardley")]
Model::Wardley(ref m) => diagrams::wardley::layout(m, config, o),
#[cfg(feature = "eventmodeling")]
Model::EventModeling(ref m) => diagrams::eventmodeling::layout(m, config, o),
#[cfg(feature = "usecase")]
Model::UseCase(ref m) => diagrams::usecase::layout(m, config, o),
#[cfg(feature = "venn")]
Model::Venn(ref m) => diagrams::venn::layout(m, config, o),
#[cfg(feature = "ishikawa")]
Model::Ishikawa(ref m) => diagrams::ishikawa::layout(m, config, o),
#[cfg(feature = "swimlane")]
Model::Swimlane(ref db) => diagrams::swimlane::layout(db, config, o),
};
Ok(sc)
}
}
impl Diagram {
pub fn diagram_type(&self) -> DiagramType {
self.found.diagram
}
pub fn mermaid_type(&self) -> &'static str {
self.found.mermaid_type
}
pub fn title(&self) -> Option<&str> {
self.title.as_deref()
}
pub fn config(&self) -> &Map {
&self.config
}
pub fn layout(&self, o: &Options) -> Result<Scene, Error> {
let mut sc = self
.model
.layout(&self.config, o)
.map_err(|e| e.tagged(self.found))?;
for it in &mut sc.items {
if matches!(it.kind, Kind::Text) && it.bounds.is_unset() {
it.bounds = it.label_bounds();
}
}
if let Some(grid) = o.snap {
sc.snap(grid);
}
if !crate::items::finite(&sc) {
let message = "Diagram cannot be laid out in finite numbers.";
return Err(Error::new(message, ErrorCode::Limit).tagged(self.found));
}
Ok(sc)
}
}
pub fn layout(src: &str, o: &Options) -> Result<Scene, Error> {
parse(src, &ParseOptions::default())?.layout(o)
}
#[cfg(test)]
mod tests;