Validate a single mermaid diagram. Args: diagram_content: The mermaid diagram content diagram_num: Diagram number for error reporting line_start: Starting line number in the file Returns: Error message if invalid, empty string if valid
(diagram_content: str, diagram_num: int, line_start: int)
| 226 | |
| 227 | |
| 228 | async def validate_single_diagram(diagram_content: str, diagram_num: int, line_start: int) -> str: |
| 229 | """ |
| 230 | Validate a single mermaid diagram. |
| 231 | |
| 232 | Args: |
| 233 | diagram_content: The mermaid diagram content |
| 234 | diagram_num: Diagram number for error reporting |
| 235 | line_start: Starting line number in the file |
| 236 | |
| 237 | Returns: |
| 238 | Error message if invalid, empty string if valid |
| 239 | """ |
| 240 | global _MERMAID_PY_BROKEN |
| 241 | core_error = await _try_pythonmonkey_parse(diagram_content) |
| 242 | if core_error is None: |
| 243 | if _MERMAID_PY_BROKEN: |
| 244 | # Validation disabled/unavailable is not a syntax error — a |
| 245 | # non-empty return here would make callers report valid diagrams |
| 246 | # as broken and send agents into fix loops. |
| 247 | logger.debug("Diagram %d: validation skipped (mermaid-py disabled or unavailable)", diagram_num) |
| 248 | return "" |
| 249 | try: |
| 250 | core_error = await asyncio.wait_for( |
| 251 | asyncio.to_thread(_parse_via_mermaid_py, diagram_content), |
| 252 | timeout=15.0, |
| 253 | ) |
| 254 | except asyncio.TimeoutError: |
| 255 | # Inconclusive, not a parse failure. Latch so the remaining |
| 256 | # diagrams (and later calls) don't each block 15s on the same |
| 257 | # broken Node.js setup. |
| 258 | _MERMAID_PY_BROKEN = True |
| 259 | logger.warning("Diagram %d: mermaid validation timed out (15s); skipping further validation", diagram_num) |
| 260 | return "" |
| 261 | except Exception as e: |
| 262 | return f" Diagram {diagram_num}: Exception during validation - {str(e)}" |
| 263 | |
| 264 | if not core_error: |
| 265 | return "" |
| 266 | |
| 267 | line_match = re.search(r'line (\d+)', core_error) |
| 268 | if line_match: |
| 269 | error_line_in_diagram = int(line_match.group(1)) |
| 270 | actual_line_in_file = line_start + error_line_in_diagram |
| 271 | newline = '\n' |
| 272 | return f"Diagram {diagram_num}: Parse error on line {actual_line_in_file}:{newline}{newline.join(core_error.split(newline)[1:])}" |
| 273 | return f"Diagram {diagram_num}: {core_error}" |
| 274 | |
| 275 | |
| 276 | if __name__ == "__main__": |
no test coverage detected