fix(mermaid): parse compact multidirectional labels (#131)
* fix(mermaid): parse compact multidirectional labels * fix(mermaid): preserve source ids ending in markers * chore: bump plugin version to 2.6.2 * chore: bump plugin version to 2.6.4 * fix: preserve single-character Mermaid source IDs * fix: preserve chained Mermaid marker IDs --------- Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
This commit is contained in:
co-authored by
Cathryn Lavery
parent
ddaeae936e
commit
c79585edb4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "diagram-design",
|
||||
"description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, polar chart (polar/radial lollipop), loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, treemap, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, DP security matrix, sankey, fishbone, Wardley map, kanban, user journey, deployment, dependency graph, UML class, story map, or database schema diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.",
|
||||
"version": "2.6.2",
|
||||
"version": "2.6.4",
|
||||
"author": {
|
||||
"name": "Cathryn Lavery",
|
||||
"url": "https://github.com/cathrynlavery"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "diagram-design",
|
||||
"description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, polar chart (polar/radial lollipop), loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, treemap, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, DP security matrix, sankey, fishbone, Wardley map, kanban, user journey, deployment, dependency graph, UML class, story map, or database schema diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.",
|
||||
"version": "2.6.2",
|
||||
"version": "2.6.4",
|
||||
"author": {
|
||||
"name": "Cathryn Lavery",
|
||||
"url": "https://github.com/cathrynlavery"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "diagram-design",
|
||||
"description": "Create branded architecture, IT current-state, flowchart, sequence, state machine, ER/data model, timeline, swimlane, quadrant, radar/spider, polar chart (polar/radial lollipop), loop/flywheel, nested, tree, org chart, layer stack, Venn, pyramid/funnel, treemap, bar, line, Gantt and scatter charts, high-level, process, medallion, data flow, DP integration, DP security matrix, sankey, fishbone, Wardley map, kanban, user journey, deployment, dependency graph, UML class, story map, or database schema diagrams as standalone HTML/SVG/PNG. Redraw .drawio/.drawio.png/.drawio.svg or Mermaid .mmd sources at a chosen size/detail; onboard brand tokens from a website; add semantic patterns, callouts, accessible motion, or sketchy/hand-drawn styling.",
|
||||
"version": "2.6.2",
|
||||
"version": "2.6.4",
|
||||
"author": {
|
||||
"name": "Cathryn Lavery",
|
||||
"url": "https://github.com/cathrynlavery"
|
||||
|
||||
@@ -284,6 +284,11 @@ B--no-->A
|
||||
C-.fast.->D
|
||||
D==crit==>E
|
||||
E--tie---A
|
||||
K<--both-->L
|
||||
M o--circle--o N
|
||||
O x--blocked--x P
|
||||
Box--yes-->C
|
||||
Echo--go-->D
|
||||
F --o G --> H
|
||||
I----->J
|
||||
""",
|
||||
@@ -291,18 +296,80 @@ I----->J
|
||||
)
|
||||
compact = json.loads(run_extract([str(compact_file), "--json"]))["diagrams"][0]
|
||||
compact_ids = sorted(node["id"] for node in compact["nodes"])
|
||||
if compact_ids != ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]:
|
||||
if compact_ids != [
|
||||
"A", "B", "Box", "C", "D", "E", "Echo", "F", "G", "H", "I", "J",
|
||||
"K", "L", "M", "N", "O", "P",
|
||||
]:
|
||||
fail(f"compact edge labels materialized phantom nodes: {compact_ids}")
|
||||
compact_labels = [edge["label"] for edge in compact["edges"]]
|
||||
if compact_labels[:6] != ["", "yes", "no", "fast", "crit", "tie"]:
|
||||
if compact_labels[:11] != [
|
||||
"", "yes", "no", "fast", "crit", "tie", "both", "circle", "blocked",
|
||||
"yes", "go",
|
||||
]:
|
||||
fail(f"compact edge labels were not retained: {compact_labels}")
|
||||
if compact_labels[6:] != ["", "", ""]:
|
||||
if compact_labels[11:] != ["", "", ""]:
|
||||
fail(
|
||||
"operator characters or chained links were misread as compact "
|
||||
f"labels: {compact_labels}"
|
||||
)
|
||||
if compact["edges"][3]["style"] != "dashed" or compact["edges"][4]["style"] != "thick":
|
||||
fail("compact dotted/thick labeled links lost their styles")
|
||||
arrow_bidir, circle_bidir, cross_bidir = compact["edges"][6:9]
|
||||
if not arrow_bidir["bidirectional"] or arrow_bidir["arrowhead"] != "arrow":
|
||||
fail("compact labeled `<-- -->` link lost bidirectional arrow semantics")
|
||||
if not circle_bidir["bidirectional"] or circle_bidir["arrowhead"] != "circle":
|
||||
fail("compact labeled `o-- --o` link lost bidirectional circle semantics")
|
||||
if not cross_bidir["bidirectional"] or cross_bidir["arrowhead"] != "cross":
|
||||
fail("compact labeled `x-- --x` link lost bidirectional cross semantics")
|
||||
if compact["edges"][9]["source"] != "Box" or compact["edges"][10]["source"] != "Echo":
|
||||
fail("source IDs ending in x/o were consumed as left edge markers")
|
||||
|
||||
single_marker_source_file = tmp / "single-marker-source-links.mmd"
|
||||
single_marker_source_file.write_text(
|
||||
"""flowchart LR
|
||||
x--yes-->B
|
||||
o--go-->C
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
single_marker_source = json.loads(
|
||||
run_extract([str(single_marker_source_file), "--json"])
|
||||
)["diagrams"][0]
|
||||
single_marker_edges = [
|
||||
(edge["source"], edge["target"], edge["label"])
|
||||
for edge in single_marker_source["edges"]
|
||||
]
|
||||
if single_marker_edges != [
|
||||
("x", "B", "yes"),
|
||||
("o", "C", "go"),
|
||||
]:
|
||||
fail("single-character x/o source IDs were consumed as left edge markers")
|
||||
|
||||
chained_marker_source_file = tmp / "chained-marker-source-links.mmd"
|
||||
chained_marker_source_file.write_text(
|
||||
"""flowchart LR
|
||||
A--yes-->x--go-->B
|
||||
C--no--> o--wait-->D
|
||||
E-->|maybe|x--next-->F
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
chained_marker_source = json.loads(
|
||||
run_extract([str(chained_marker_source_file), "--json"])
|
||||
)["diagrams"][0]
|
||||
chained_marker_edges = [
|
||||
(edge["source"], edge["target"], edge["label"])
|
||||
for edge in chained_marker_source["edges"]
|
||||
]
|
||||
if chained_marker_edges != [
|
||||
("A", "x", "yes"),
|
||||
("x", "B", "go"),
|
||||
("C", "o", "no"),
|
||||
("o", "D", "wait"),
|
||||
("E", "x", "maybe"),
|
||||
("x", "F", "next"),
|
||||
]:
|
||||
fail("chained x/o endpoint IDs were consumed as left edge markers")
|
||||
|
||||
modern_file = tmp / "modern-flowchart.mmd"
|
||||
modern_file.write_text(
|
||||
|
||||
@@ -605,21 +605,39 @@ def _edge_operators(text: str) -> list[_Operator]:
|
||||
# Labeled links carry the label between the opening and closing operator:
|
||||
# `A-- text -->B`, `A-. retry .-> B`, `A== critical ==> B`, and the
|
||||
# undirected forms of each. The compact form drops the spaces —
|
||||
# `B--yes-->C` — so its label may not contain whitespace, and the operator
|
||||
# characters themselves may not open one (keeping `A----->B` unlabeled and
|
||||
# `A --o B --> C` two separate links).
|
||||
# `B--yes-->C` — and may retain a left arrow/circle/cross marker, as in
|
||||
# `A<--yes-->B` or `A o--yes--o B`. Its label may not contain whitespace,
|
||||
# and the operator characters themselves may not open one (keeping
|
||||
# `A----->B` unlabeled and `A --o B --> C` two separate links).
|
||||
text_edge = re.compile(
|
||||
r"(?:--|-\.|==)"
|
||||
r"(?:\s+(.+?)\s+|(?![-=.\s])([^\s|<>]+?))"
|
||||
r"(\.-+[>xo]|\.-+|-{2,}>|--[xo]|=+>|={2,}|-{3,})"
|
||||
r"(?P<opening>"
|
||||
r"<(?:--|-\.|==)"
|
||||
r"|(?<![\w.:-])[xo](?:--|-\.|==)"
|
||||
r"|(?:--|-\.|==)"
|
||||
r")"
|
||||
r"(?:\s+(?P<spaced>.+?)\s+|(?![-=.\s])(?P<compact>[^\s|<>]+?))"
|
||||
r"(?P<closing>\.-+[>xo]|\.-+|-{2,}>|--[xo]|=+>|={2,}|-{3,})"
|
||||
)
|
||||
trailing_operator = re.compile(
|
||||
r"(?:\.-+[>xo]|\.-+|-{2,}>|--[xo]|=+>|={2,}|-{3,})"
|
||||
r"(?:\|[^|\n]*\|)?\s*$"
|
||||
)
|
||||
for match in text_edge.finditer(mask):
|
||||
token = match.group(3)
|
||||
label_group = 1 if match.group(1) is not None else 2
|
||||
opening = match.group("opening")
|
||||
operator_start = match.start()
|
||||
if opening.startswith(("x", "o")):
|
||||
prefix = mask[:operator_start]
|
||||
if not prefix.strip() or trailing_operator.search(prefix):
|
||||
# Here x/o is the endpoint before a regular opening operator,
|
||||
# not a left marker: `x--yes-->B` or `A-->x--go-->B`.
|
||||
operator_start += 1
|
||||
opening = opening[1:]
|
||||
token = opening + match.group("closing")
|
||||
label_group = "spaced" if match.group("spaced") is not None else "compact"
|
||||
style, arrowhead, bidirectional, undirected = _operator_style(token)
|
||||
operators.append(
|
||||
_Operator(
|
||||
match.start(),
|
||||
operator_start,
|
||||
match.end(),
|
||||
clean_label(text[match.start(label_group) : match.end(label_group)]),
|
||||
style,
|
||||
@@ -628,7 +646,7 @@ def _edge_operators(text: str) -> list[_Operator]:
|
||||
undirected,
|
||||
)
|
||||
)
|
||||
occupied.append((match.start(), match.end()))
|
||||
occupied.append((operator_start, match.end()))
|
||||
|
||||
pattern = re.compile(
|
||||
r"[xo][-=.]+[xo]|<[-=.]+>|-+\.-+>|=+>|-+(?:>|x|o)|-+\.-+|={3,}|-{3,}"
|
||||
|
||||
Reference in New Issue
Block a user