nordabiz/verify_diagrams.py
Maciej Pienczyn 8ee5945ccd fix: Handle NULL views_count in forum and classifieds
- Forum topics and classifieds now handle NULL views_count gracefully
- Prevents TypeError when incrementing view counter
2026-01-11 06:03:13 +01:00

255 lines
8.7 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Verify all Mermaid diagrams in architecture documentation.
Checks for:
- Proper Mermaid code block syntax
- Common syntax errors
- Readability issues (too long lines, etc.)
- Missing diagram titles/descriptions
"""
import re
import os
from pathlib import Path
from typing import List, Dict, Tuple
class DiagramVerifier:
def __init__(self):
self.errors = []
self.warnings = []
self.info = []
self.diagrams_found = 0
def verify_file(self, filepath: Path) -> Dict:
"""Verify a single markdown file."""
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
file_result = {
'file': str(filepath),
'diagrams': 0,
'errors': [],
'warnings': [],
'info': []
}
# Find all Mermaid code blocks
mermaid_pattern = r'```mermaid\n(.*?)```'
matches = re.finditer(mermaid_pattern, content, re.DOTALL)
for idx, match in enumerate(matches, 1):
self.diagrams_found += 1
file_result['diagrams'] += 1
diagram_content = match.group(1)
# Check for common issues
issues = self.check_diagram(diagram_content, idx)
file_result['errors'].extend(issues['errors'])
file_result['warnings'].extend(issues['warnings'])
file_result['info'].extend(issues['info'])
return file_result
def check_diagram(self, diagram: str, diagram_num: int) -> Dict:
"""Check a single diagram for issues."""
issues = {'errors': [], 'warnings': [], 'info': []}
lines = diagram.strip().split('\n')
if not lines:
issues['errors'].append(f"Diagram {diagram_num}: Empty diagram")
return issues
first_line = lines[0].strip()
# Check for valid diagram type
valid_types = [
'graph', 'flowchart', 'sequenceDiagram', 'classDiagram',
'stateDiagram', 'erDiagram', 'gantt', 'pie', 'journey',
'gitGraph', 'C4Context', 'C4Container', 'C4Component'
]
# Also accept diagrams starting with comments or init blocks
valid_prefixes = ['%%', '%%{init:']
# Find the first non-comment line to check for diagram type
diagram_type_line = first_line
for line in lines:
stripped = line.strip()
if not stripped.startswith('%%'):
diagram_type_line = stripped
break
diagram_type = diagram_type_line.split()[0] if diagram_type_line else ''
is_comment_or_init = any(first_line.startswith(p) for p in valid_prefixes)
has_valid_type = any(diagram_type_line.startswith(t) for t in valid_types)
if not has_valid_type and not is_comment_or_init:
issues['errors'].append(
f"Diagram {diagram_num}: Invalid or missing diagram type. "
f"First line: '{first_line[:50]}...'"
)
# Check for extremely long lines (readability)
for line_num, line in enumerate(lines, 1):
if len(line) > 200:
issues['warnings'].append(
f"Diagram {diagram_num}, Line {line_num}: "
f"Very long line ({len(line)} chars) may affect readability"
)
# Check for common syntax errors
# 1. Unmatched quotes
for line_num, line in enumerate(lines, 1):
# Count quotes (ignoring escaped quotes)
quote_count = line.count('"') - line.count('\\"')
if quote_count % 2 != 0:
issues['warnings'].append(
f"Diagram {diagram_num}, Line {line_num}: "
f"Unmatched quotes detected"
)
# 2. Unclosed brackets/parentheses
for line_num, line in enumerate(lines, 1):
open_count = line.count('[') + line.count('(') + line.count('{')
close_count = line.count(']') + line.count(')') + line.count('}')
# Note: This is a simple check, might have false positives for multi-line
# statements, but it's good for catching obvious errors
# 3. Check for common Mermaid syntax patterns
diagram_str = '\n'.join(lines)
# For sequence diagrams, check for proper participant declarations
if 'sequenceDiagram' in first_line:
if 'participant' not in diagram_str and 'actor' not in diagram_str:
issues['info'].append(
f"Diagram {diagram_num}: Sequence diagram without explicit "
f"participant/actor declarations (auto-generated)"
)
# For flowcharts, check for proper node definitions
if 'flowchart' in first_line or 'graph' in first_line:
# Check for at least one node definition
if not re.search(r'\w+\[.*?\]|\w+\(.*?\)|\w+\{.*?\}', diagram_str):
issues['warnings'].append(
f"Diagram {diagram_num}: Flowchart might be missing node definitions"
)
# For ERD, check for entity definitions
if 'erDiagram' in first_line:
if '{' not in diagram_str or '}' not in diagram_str:
issues['warnings'].append(
f"Diagram {diagram_num}: ERD might be missing entity attribute blocks"
)
# Check diagram size (too many lines might be hard to render)
if len(lines) > 500:
issues['warnings'].append(
f"Diagram {diagram_num}: Very large diagram ({len(lines)} lines) "
f"might have rendering issues"
)
elif len(lines) > 200:
issues['info'].append(
f"Diagram {diagram_num}: Large diagram ({len(lines)} lines)"
)
return issues
def verify_all(self, base_path: Path) -> List[Dict]:
"""Verify all markdown files in the directory."""
results = []
# Find all .md files
md_files = sorted(base_path.rglob('*.md'))
for md_file in md_files:
result = self.verify_file(md_file)
results.append(result)
return results
def print_report(self, results: List[Dict]):
"""Print a formatted report."""
print("=" * 80)
print("MERMAID DIAGRAM VERIFICATION REPORT")
print("=" * 80)
print()
total_files = len(results)
total_diagrams = sum(r['diagrams'] for r in results)
total_errors = sum(len(r['errors']) for r in results)
total_warnings = sum(len(r['warnings']) for r in results)
total_info = sum(len(r['info']) for r in results)
print(f"📊 Summary:")
print(f" - Files checked: {total_files}")
print(f" - Diagrams found: {total_diagrams}")
print(f" - Errors: {total_errors}")
print(f" - Warnings: {total_warnings}")
print(f" - Info messages: {total_info}")
print()
# Print details for each file
for result in results:
if result['diagrams'] == 0:
continue
# Handle both relative and absolute paths
try:
filename = Path(result['file']).relative_to(Path.cwd())
except ValueError:
filename = result['file']
print(f"\n{'' * 80}")
print(f"📄 {filename}")
print(f" Diagrams: {result['diagrams']}")
if result['errors']:
print(f"\n ❌ ERRORS ({len(result['errors'])}):")
for error in result['errors']:
print(f"{error}")
if result['warnings']:
print(f"\n ⚠️ WARNINGS ({len(result['warnings'])}):")
for warning in result['warnings']:
print(f"{warning}")
if result['info']:
print(f"\n INFO ({len(result['info'])}):")
for info in result['info']:
print(f"{info}")
print("\n" + "=" * 80)
if total_errors == 0 and total_warnings == 0:
print("✅ All diagrams passed verification!")
elif total_errors == 0:
print("✅ No errors found (warnings are informational)")
else:
print("❌ Errors found - please review and fix")
print("=" * 80)
return total_errors == 0
def main():
"""Main entry point."""
base_path = Path('./docs/architecture')
if not base_path.exists():
print(f"❌ Directory not found: {base_path}")
return False
verifier = DiagramVerifier()
results = verifier.verify_all(base_path)
success = verifier.print_report(results)
return success
if __name__ == '__main__':
import sys
success = main()
sys.exit(0 if success else 1)