#![forbid(unsafe_code)]
pub mod common;
pub mod common_db;
pub mod config;
pub mod detect;
pub mod diagram;
pub mod diagrams;
pub mod entities;
pub mod error;
pub mod generated;
pub mod geom;
pub mod models;
pub mod preprocess;
mod runtime;
pub mod sanitize;
mod theme;
pub mod time;
pub mod utils;
pub use config::MermaidConfig;
pub use detect::{Detector, DetectorRegistry};
pub use diagram::{
DiagramRegistry, DiagramSemanticParser, ParsedDiagram, ParsedDiagramRender, RenderSemanticModel,
};
pub use error::{Error, Result};
pub use preprocess::{PreprocessResult, preprocess_diagram, preprocess_diagram_with_known_type};
#[derive(Debug, Clone, Copy, Default)]
pub struct ParseOptions {
pub suppress_errors: bool,
}
impl ParseOptions {
pub fn strict() -> Self {
Self {
suppress_errors: false,
}
}
pub fn lenient() -> Self {
Self {
suppress_errors: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ParseMetadata {
pub diagram_type: String,
pub config: MermaidConfig,
pub effective_config: MermaidConfig,
pub title: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Engine {
registry: DetectorRegistry,
diagram_registry: DiagramRegistry,
site_config: MermaidConfig,
fixed_today_local: Option<chrono::NaiveDate>,
fixed_local_offset_minutes: Option<i32>,
}
impl Default for Engine {
fn default() -> Self {
let site_config = generated::default_site_config();
Self {
registry: DetectorRegistry::default_mermaid_11_12_2(),
diagram_registry: DiagramRegistry::default_mermaid_11_12_2(),
site_config,
fixed_today_local: None,
fixed_local_offset_minutes: None,
}
}
}
impl Engine {
fn parse_timing_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| {
matches!(
std::env::var("MERMAN_PARSE_TIMING").as_deref(),
Ok("1") | Ok("true")
)
})
}
pub fn new() -> Self {
Self::default()
}
pub fn with_fixed_today(mut self, today: Option<chrono::NaiveDate>) -> Self {
self.fixed_today_local = today;
self
}
pub fn with_fixed_local_offset_minutes(mut self, offset_minutes: Option<i32>) -> Self {
self.fixed_local_offset_minutes = offset_minutes;
self
}
pub fn with_site_config(mut self, site_config: MermaidConfig) -> Self {
self.site_config.deep_merge(site_config.as_value());
self
}
pub fn registry(&self) -> &DetectorRegistry {
&self.registry
}
pub fn registry_mut(&mut self) -> &mut DetectorRegistry {
&mut self.registry
}
pub fn diagram_registry(&self) -> &DiagramRegistry {
&self.diagram_registry
}
pub fn diagram_registry_mut(&mut self) -> &mut DiagramRegistry {
&mut self.diagram_registry
}
pub fn parse_metadata_sync(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParseMetadata>> {
let Some((_, meta)) = self.preprocess_and_detect(text, options)? else {
return Ok(None);
};
Ok(Some(meta))
}
pub fn parse_metadata_as_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParseMetadata>> {
let Some((_, meta)) = self.preprocess_and_assume_type(diagram_type, text, options)? else {
return Ok(None);
};
Ok(Some(meta))
}
pub async fn parse_metadata(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParseMetadata>> {
self.parse_metadata_sync(text, options)
}
pub async fn parse_metadata_as(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParseMetadata>> {
self.parse_metadata_as_sync(diagram_type, text, options)
}
pub fn parse_diagram_sync(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
let timing_enabled = Self::parse_timing_enabled();
let total_start = timing_enabled.then(std::time::Instant::now);
let preprocess_start = timing_enabled.then(std::time::Instant::now);
let Some((code, meta)) = self.preprocess_and_detect(text, options)? else {
return Ok(None);
};
let preprocess = preprocess_start.map(|s| s.elapsed());
let parse_start = timing_enabled.then(std::time::Instant::now);
let parse = crate::runtime::with_fixed_today_local(self.fixed_today_local, || {
crate::runtime::with_fixed_local_offset_minutes(self.fixed_local_offset_minutes, || {
diagram::parse_or_unsupported(
&self.diagram_registry,
&meta.diagram_type,
&code,
&meta,
)
})
});
let mut model = match parse {
Ok(v) => v,
Err(err) => {
if !options.suppress_errors {
return Err(err);
}
if let Some(start) = total_start {
let parse = parse_start.map(|s| s.elapsed()).unwrap_or_default();
eprintln!(
"[parse-timing] diagram=error total={:?} preprocess={:?} parse={:?} sanitize={:?} input_bytes={}",
start.elapsed(),
preprocess.unwrap_or_default(),
parse,
std::time::Duration::default(),
text.len(),
);
}
return Ok(Some(
crate::diagrams::error_diagram::suppressed_error_diagram(&meta),
));
}
};
let parse = parse_start.map(|s| s.elapsed());
let sanitize_start = timing_enabled.then(std::time::Instant::now);
common_db::apply_common_db_sanitization(&mut model, &meta.effective_config);
let sanitize = sanitize_start.map(|s| s.elapsed());
if let Some(start) = total_start {
eprintln!(
"[parse-timing] diagram={} total={:?} preprocess={:?} parse={:?} sanitize={:?} input_bytes={}",
meta.diagram_type,
start.elapsed(),
preprocess.unwrap_or_default(),
parse.unwrap_or_default(),
sanitize.unwrap_or_default(),
text.len(),
);
}
Ok(Some(ParsedDiagram { meta, model }))
}
pub async fn parse_diagram(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
self.parse_diagram_sync(text, options)
}
pub fn parse_diagram_for_render_model_sync(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
self.parse_diagram_for_render_model_inner(text, options, |engine| {
engine.preprocess_and_detect(text, options)
})
}
pub async fn parse_diagram_for_render_model(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
self.parse_diagram_for_render_model_sync(text, options)
}
pub fn parse_diagram_for_render_model_as_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
self.parse_diagram_for_render_model_inner(text, options, |engine| {
engine.preprocess_and_assume_type(diagram_type, text, options)
})
}
fn parse_diagram_for_render_model_inner(
&self,
text: &str,
options: ParseOptions,
preprocess: impl FnOnce(&Self) -> Result<Option<(String, ParseMetadata)>>,
) -> Result<Option<ParsedDiagramRender>> {
let timing_enabled = Self::parse_timing_enabled();
let total_start = timing_enabled.then(std::time::Instant::now);
let preprocess_start = timing_enabled.then(std::time::Instant::now);
let Some((code, meta)) = preprocess(self)? else {
return Ok(None);
};
let preprocess = preprocess_start.map(|s| s.elapsed());
let parse_start = timing_enabled.then(std::time::Instant::now);
let parse_res = self.parse_render_semantic_model(&code, &meta);
let parse = parse_start.map(|s| s.elapsed());
let mut model = match parse_res {
Ok(v) => v,
Err(err) => {
if !options.suppress_errors {
return Err(err);
}
if let Some(start) = total_start {
eprintln!(
"[parse-render-timing] diagram=error model=json total={:?} preprocess={:?} parse={:?} sanitize={:?} input_bytes={}",
start.elapsed(),
preprocess.unwrap_or_default(),
parse.unwrap_or_default(),
std::time::Duration::default(),
text.len(),
);
}
return Ok(Some(
crate::diagrams::error_diagram::suppressed_error_render_diagram(&meta),
));
}
};
let sanitize_start = timing_enabled.then(std::time::Instant::now);
Self::sanitize_render_semantic_model(&mut model, &meta.effective_config);
let sanitize = sanitize_start.map(|s| s.elapsed());
if let Some(start) = total_start {
let model_kind = Self::render_model_kind(&model);
eprintln!(
"[parse-render-timing] diagram={} model={} total={:?} preprocess={:?} parse={:?} sanitize={:?} input_bytes={}",
meta.diagram_type,
model_kind,
start.elapsed(),
preprocess.unwrap_or_default(),
parse.unwrap_or_default(),
sanitize.unwrap_or_default(),
text.len(),
);
}
Ok(Some(ParsedDiagramRender { meta, model }))
}
fn parse_render_semantic_model(
&self,
code: &str,
meta: &ParseMetadata,
) -> Result<RenderSemanticModel> {
match meta.diagram_type.as_str() {
"mindmap" => crate::diagrams::mindmap::parse_mindmap_model_for_render(code, meta)
.map(RenderSemanticModel::Mindmap),
"stateDiagram" | "state" => {
crate::diagrams::state::parse_state_model_for_render(code, meta)
.map(RenderSemanticModel::State)
}
"zenuml" => crate::diagrams::zenuml::parse_zenuml_model_for_render(code, meta)
.map(RenderSemanticModel::Sequence),
"sequence" => crate::diagrams::sequence::parse_sequence_model_for_render(code, meta)
.map(RenderSemanticModel::Sequence),
"flowchart-v2" | "flowchart" | "flowchart-elk" => {
crate::diagrams::flowchart::parse_flowchart_model_for_render(code, meta)
.map(RenderSemanticModel::Flowchart)
}
"classDiagram" | "class" => crate::diagrams::class::parse_class_typed(code, meta)
.map(RenderSemanticModel::Class),
"c4" => crate::diagrams::c4::parse_c4_model_for_render(code, meta)
.map(RenderSemanticModel::C4),
"architecture" => {
crate::diagrams::architecture::parse_architecture_model_for_render(code, meta)
.map(RenderSemanticModel::Architecture)
}
"kanban" => crate::diagrams::kanban::parse_kanban_model_for_render(code, meta)
.map(RenderSemanticModel::Kanban),
"gantt" => crate::diagrams::gantt::parse_gantt_model_for_render(code, meta)
.map(RenderSemanticModel::Gantt),
"pie" => crate::diagrams::pie::parse_pie_model_for_render(code, meta)
.map(RenderSemanticModel::Pie),
"packet" => crate::diagrams::packet::parse_packet_model_for_render(code, meta)
.map(RenderSemanticModel::Packet),
"timeline" => crate::diagrams::timeline::parse_timeline_model_for_render(code, meta)
.map(RenderSemanticModel::Timeline),
"journey" => crate::diagrams::journey::parse_journey_model_for_render(code, meta)
.map(RenderSemanticModel::Journey),
"requirement" => {
crate::diagrams::requirement::parse_requirement_model_for_render(code, meta)
.map(RenderSemanticModel::Requirement)
}
"sankey" => crate::diagrams::sankey::parse_sankey_model_for_render(code, meta)
.map(RenderSemanticModel::Sankey),
"radar" => crate::diagrams::radar::parse_radar_model_for_render(code, meta)
.map(RenderSemanticModel::Radar),
"info" => crate::diagrams::info::parse_info_model_for_render(code, meta)
.map(RenderSemanticModel::Info),
"treemap" => crate::diagrams::treemap::parse_treemap_model_for_render(code, meta)
.map(RenderSemanticModel::Treemap),
"block" => crate::diagrams::block::parse_block_model_for_render(code, meta)
.map(RenderSemanticModel::Block),
"er" | "erDiagram" => crate::diagrams::er::parse_er_model_for_render(code, meta)
.map(RenderSemanticModel::Er),
"quadrantChart" => {
crate::diagrams::quadrant_chart::parse_quadrant_chart_model_for_render(code, meta)
.map(RenderSemanticModel::QuadrantChart)
}
"xychart" => crate::diagrams::xychart::parse_xychart_model_for_render(code, meta)
.map(RenderSemanticModel::XyChart),
"gitGraph" => crate::diagrams::git_graph::parse_git_graph_model_for_render(code, meta)
.map(RenderSemanticModel::GitGraph),
_ => diagram::parse_or_unsupported(
&self.diagram_registry,
&meta.diagram_type,
code,
meta,
)
.map(RenderSemanticModel::Json),
}
}
fn sanitize_render_semantic_model(
model: &mut RenderSemanticModel,
effective_config: &MermaidConfig,
) {
match model {
RenderSemanticModel::Json(v) => {
common_db::apply_common_db_sanitization(v, effective_config);
}
RenderSemanticModel::State(v) => {
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Sequence(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Mindmap(_) => {}
RenderSemanticModel::Flowchart(_) => {}
RenderSemanticModel::Class(v) => {
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Architecture(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::C4(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Kanban(_) => {}
RenderSemanticModel::Gantt(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Pie(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Packet(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Timeline(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Journey(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Requirement(v) => {
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Sankey(_) => {}
RenderSemanticModel::Radar(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Info(_) => {}
RenderSemanticModel::Treemap(v) => {
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::Block(_) => {}
RenderSemanticModel::Er(v) => {
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::QuadrantChart(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::XyChart(v) => {
if let Some(s) = v.title.as_deref() {
v.title = Some(crate::sanitize::sanitize_text(s, effective_config));
}
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
RenderSemanticModel::GitGraph(v) => {
if let Some(s) = v.acc_title.as_deref() {
v.acc_title = Some(common_db::sanitize_acc_title(s, effective_config));
}
if let Some(s) = v.acc_descr.as_deref() {
v.acc_descr = Some(common_db::sanitize_acc_descr(s, effective_config));
}
}
}
}
fn render_model_kind(model: &RenderSemanticModel) -> &'static str {
match model {
RenderSemanticModel::Json(_) => "json",
RenderSemanticModel::State(_) => "state",
RenderSemanticModel::Sequence(_) => "sequence",
RenderSemanticModel::Mindmap(_) => "mindmap",
RenderSemanticModel::Flowchart(_) => "flowchart",
RenderSemanticModel::Architecture(_) => "architecture",
RenderSemanticModel::Class(_) => "class",
RenderSemanticModel::C4(_) => "c4",
RenderSemanticModel::Kanban(_) => "kanban",
RenderSemanticModel::Gantt(_) => "gantt",
RenderSemanticModel::Pie(_) => "pie",
RenderSemanticModel::Packet(_) => "packet",
RenderSemanticModel::Timeline(_) => "timeline",
RenderSemanticModel::Journey(_) => "journey",
RenderSemanticModel::Requirement(_) => "requirement",
RenderSemanticModel::Sankey(_) => "sankey",
RenderSemanticModel::Radar(_) => "radar",
RenderSemanticModel::Info(_) => "info",
RenderSemanticModel::Treemap(_) => "treemap",
RenderSemanticModel::Block(_) => "block",
RenderSemanticModel::Er(_) => "er",
RenderSemanticModel::QuadrantChart(_) => "quadrantChart",
RenderSemanticModel::XyChart(_) => "xychart",
RenderSemanticModel::GitGraph(_) => "gitGraph",
}
}
pub async fn parse_diagram_for_render_model_as(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
self.parse_diagram_for_render_model_as_sync(diagram_type, text, options)
}
pub fn parse_diagram_as_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
let Some((code, meta)) = self.preprocess_and_assume_type(diagram_type, text, options)?
else {
return Ok(None);
};
let parse = crate::runtime::with_fixed_today_local(self.fixed_today_local, || {
crate::runtime::with_fixed_local_offset_minutes(self.fixed_local_offset_minutes, || {
diagram::parse_or_unsupported(
&self.diagram_registry,
&meta.diagram_type,
&code,
&meta,
)
})
});
let mut model = match parse {
Ok(v) => v,
Err(err) => {
if !options.suppress_errors {
return Err(err);
}
return Ok(Some(
crate::diagrams::error_diagram::suppressed_error_diagram(&meta),
));
}
};
common_db::apply_common_db_sanitization(&mut model, &meta.effective_config);
Ok(Some(ParsedDiagram { meta, model }))
}
pub async fn parse_diagram_as(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
self.parse_diagram_as_sync(diagram_type, text, options)
}
pub async fn parse(&self, text: &str, options: ParseOptions) -> Result<Option<ParseMetadata>> {
self.parse_metadata(text, options).await
}
fn preprocess_and_detect(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<(String, ParseMetadata)>> {
let pre = preprocess_diagram(text, &self.registry)?;
if pre.code.trim_start().starts_with("---") {
return Err(Error::MalformedFrontMatter);
}
let mut effective_config = self.site_config.clone();
effective_config.deep_merge(pre.config.as_value());
let diagram_type = match self
.registry
.detect_type_precleaned(&pre.code, &mut effective_config)
{
Ok(t) => t.to_string(),
Err(err) => {
if options.suppress_errors {
return Ok(None);
}
return Err(err);
}
};
theme::apply_theme_defaults(&mut effective_config);
let title = pre
.title
.as_ref()
.map(|t| crate::sanitize::sanitize_text(t, &effective_config))
.filter(|t| !t.is_empty());
Ok(Some((
pre.code,
ParseMetadata {
diagram_type,
config: pre.config,
effective_config,
title,
},
)))
}
fn preprocess_and_assume_type(
&self,
diagram_type: &str,
text: &str,
_options: ParseOptions,
) -> Result<Option<(String, ParseMetadata)>> {
let pre = preprocess_diagram_with_known_type(text, &self.registry, Some(diagram_type))?;
if pre.code.trim_start().starts_with("---") {
return Err(Error::MalformedFrontMatter);
}
let mut effective_config = self.site_config.clone();
effective_config.deep_merge(pre.config.as_value());
apply_detector_side_effects_for_known_type(diagram_type, &mut effective_config);
theme::apply_theme_defaults(&mut effective_config);
let title = pre
.title
.as_ref()
.map(|t| crate::sanitize::sanitize_text(t, &effective_config))
.filter(|t| !t.is_empty());
Ok(Some((
pre.code,
ParseMetadata {
diagram_type: diagram_type.to_string(),
config: pre.config,
effective_config,
title,
},
)))
}
}
fn apply_detector_side_effects_for_known_type(
diagram_type: &str,
effective_config: &mut MermaidConfig,
) {
if diagram_type == "flowchart-elk" {
effective_config.set_value("layout", serde_json::Value::String("elk".to_string()));
return;
}
if matches!(diagram_type, "flowchart-v2" | "flowchart")
&& effective_config.get_str("flowchart.defaultRenderer") == Some("elk")
{
effective_config.set_value("layout", serde_json::Value::String("elk".to_string()));
}
}
#[cfg(test)]
mod tests;