Skip to content

Commit 0ebe16a

Browse files
committed
use .svg instead of native mermaid
1 parent 69f70cd commit 0ebe16a

17 files changed

+159
-2
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
"""Convert mermaid diagrams in RST/MD files to static SVG files."""
3+
4+
import re
5+
import subprocess
6+
from pathlib import Path
7+
8+
DIAGRAMS_DIR = Path(__file__).parent.parent / "source/_generated/diagrams"
9+
SVG_OUTPUT_DIR = Path(__file__).parent.parent / "source/_static/diagrams"
10+
11+
12+
def extract_mermaid_from_rst(content: str) -> str | None:
13+
"""Extract mermaid code from RST file with .. mermaid:: directive."""
14+
pattern = r"\.\. mermaid::\s*\n((?:\n| .+\n)+)"
15+
match = re.search(pattern, content)
16+
if match:
17+
# Remove the 4-space indentation
18+
lines = match.group(1).split("\n")
19+
dedented = "\n".join(
20+
line[4:] if line.startswith(" ") else line for line in lines
21+
)
22+
return dedented.strip()
23+
return None
24+
25+
26+
def extract_mermaid_from_md(content: str) -> str | None:
27+
"""Extract mermaid code from MD file with ```mermaid block."""
28+
pattern = r"```mermaid\n(.*?)```"
29+
match = re.search(pattern, content, re.DOTALL)
30+
if match:
31+
return match.group(1).strip()
32+
return None
33+
34+
35+
def generate_svg(mermaid_code: str, output_path: Path) -> bool:
36+
"""Generate SVG from mermaid code using mmdc CLI."""
37+
# Write temp mermaid file
38+
temp_mmd = output_path.with_suffix(".mmd")
39+
temp_mmd.write_text(mermaid_code)
40+
41+
try:
42+
result = subprocess.run(
43+
[
44+
"mmdc",
45+
"-i",
46+
str(temp_mmd),
47+
"-o",
48+
str(output_path),
49+
"-b",
50+
"transparent",
51+
"-p",
52+
"/tmp/puppeteer-config.json",
53+
],
54+
capture_output=True,
55+
text=True,
56+
timeout=30,
57+
)
58+
if result.returncode != 0:
59+
print(f" Error: {result.stderr}")
60+
return False
61+
return True
62+
except subprocess.TimeoutExpired:
63+
print(" Error: mmdc timed out")
64+
return False
65+
finally:
66+
temp_mmd.unlink(missing_ok=True)
67+
68+
69+
def update_rst_file(file_path: Path, svg_filename: str) -> str:
70+
"""Update RST file to use image reference instead of mermaid directive."""
71+
content = file_path.read_text()
72+
73+
# Pattern to match the mermaid directive and its content
74+
pattern = r"\.\. mermaid::\s*\n((?:\n| .+\n)+)"
75+
76+
# Replacement with image directive
77+
replacement = f""".. image:: /_static/diagrams/{svg_filename}
78+
:alt: {file_path.stem.replace('_', ' ').title()} Diagram
79+
:align: center
80+
:class: diagram
81+
82+
"""
83+
return re.sub(pattern, replacement, content)
84+
85+
86+
def update_md_file(file_path: Path, svg_filename: str) -> str:
87+
"""Update MD file to use image reference instead of mermaid block."""
88+
content = file_path.read_text()
89+
90+
# Pattern to match the mermaid code block
91+
pattern = r"```mermaid\n.*?```"
92+
93+
# Replacement with image reference (MyST markdown syntax)
94+
replacement = f"""```{{image}} /_static/diagrams/{svg_filename}
95+
:alt: {file_path.stem.replace('_', ' ').title()} Diagram
96+
:align: center
97+
:class: diagram
98+
```"""
99+
return re.sub(pattern, replacement, content, flags=re.DOTALL)
100+
101+
102+
def main():
103+
"""Convert all mermaid diagrams to SVG."""
104+
SVG_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
105+
106+
# Find all files with mermaid content
107+
rst_files = list(DIAGRAMS_DIR.glob("*.rst"))
108+
md_files = list(DIAGRAMS_DIR.glob("*.md"))
109+
110+
converted = 0
111+
for file_path in rst_files + md_files:
112+
content = file_path.read_text()
113+
114+
# Check if file has mermaid content
115+
if file_path.suffix == ".rst":
116+
mermaid_code = extract_mermaid_from_rst(content)
117+
else:
118+
mermaid_code = extract_mermaid_from_md(content)
119+
120+
if not mermaid_code:
121+
continue
122+
123+
svg_filename = f"{file_path.stem}.svg"
124+
svg_path = SVG_OUTPUT_DIR / svg_filename
125+
126+
print(f"Converting {file_path.name} -> {svg_filename}")
127+
128+
if generate_svg(mermaid_code, svg_path):
129+
# Update the source file
130+
if file_path.suffix == ".rst":
131+
new_content = update_rst_file(file_path, svg_filename)
132+
else:
133+
new_content = update_md_file(file_path, svg_filename)
134+
135+
file_path.write_text(new_content)
136+
converted += 1
137+
print(f" Done")
138+
else:
139+
print(f" Failed to generate SVG")
140+
141+
print(f"\nConverted {converted} diagrams")
142+
143+
144+
if __name__ == "__main__":
145+
main()

docs/requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ sphinx-autobuild
66
sphinx-copybutton
77
sphinx-design
88
sphinx-issues
9-
sphinxcontrib-mermaid
109
myst-parser
1110
numpydoc
1211

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/base_classes_overview.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/bbob_overview.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/cec_overview.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/class_hierarchy.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/engineering_overview.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/examples_overview.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/source/_static/diagrams/ml_overview.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)