#!/usr/bin/env python3 """Generate API reference documentation for McRogueFace. This script generates comprehensive API documentation in multiple formats: - Markdown for GitHub/documentation sites - HTML for local browsing - RST for Sphinx integration (future) """ import os import sys import inspect import datetime from typing import Dict, List, Any, Optional from pathlib import Path # We need to run this with McRogueFace as the interpreter # so mcrfpy is available import mcrfpy def escape_markdown(text: str) -> str: """Escape special markdown characters.""" if not text: return "" # Escape backticks in inline code return text.replace("`", "\\`") def format_signature(name: str, doc: str) -> str: """Extract and format function signature from docstring.""" if not doc: return f"{name}(...)" lines = doc.strip().split('\n') if lines and '(' in lines[0]: # First line contains signature return lines[0].split('->')[0].strip() return f"{name}(...)" def get_class_info(cls: type) -> Dict[str, Any]: """Extract comprehensive information about a class.""" info = { 'name': cls.__name__, 'doc': cls.__doc__ or "", 'methods': [], 'properties': [], 'bases': [base.__name__ for base in cls.__bases__ if base.__name__ != 'object'], } # Get all attributes for attr_name in sorted(dir(cls)): if attr_name.startswith('_') and not attr_name.startswith('__'): continue try: attr = getattr(cls, attr_name) if isinstance(attr, property): prop_info = { 'name': attr_name, 'doc': (attr.fget.__doc__ if attr.fget else "") or "", 'readonly': attr.fset is None } info['properties'].append(prop_info) elif callable(attr) and not attr_name.startswith('__'): method_info = { 'name': attr_name, 'doc': attr.__doc__ or "", 'signature': format_signature(attr_name, attr.__doc__) } info['methods'].append(method_info) except: pass return info def get_function_info(func: Any, name: str) -> Dict[str, Any]: """Extract information about a function.""" return { 'name': name, 'doc': func.__doc__ or "", 'signature': format_signature(name, func.__doc__) } def generate_markdown_class(cls_info: Dict[str, Any]) -> List[str]: """Generate markdown documentation for a class.""" lines = [] # Class header lines.append(f"### class `{cls_info['name']}`") if cls_info['bases']: lines.append(f"*Inherits from: {', '.join(cls_info['bases'])}*") lines.append("") # Class description if cls_info['doc']: doc_lines = cls_info['doc'].strip().split('\n') # First line is usually the constructor signature if doc_lines and '(' in doc_lines[0]: lines.append(f"```python") lines.append(doc_lines[0]) lines.append("```") lines.append("") # Rest is description if len(doc_lines) > 2: lines.extend(doc_lines[2:]) lines.append("") else: lines.extend(doc_lines) lines.append("") # Properties if cls_info['properties']: lines.append("#### Properties") lines.append("") for prop in cls_info['properties']: readonly = " *(readonly)*" if prop['readonly'] else "" lines.append(f"- **`{prop['name']}`**{readonly}") if prop['doc']: lines.append(f" - {prop['doc'].strip()}") lines.append("") # Methods if cls_info['methods']: lines.append("#### Methods") lines.append("") for method in cls_info['methods']: lines.append(f"##### `{method['signature']}`") if method['doc']: # Parse docstring for better formatting doc_lines = method['doc'].strip().split('\n') # Skip the signature line if it's repeated start = 1 if doc_lines and method['name'] in doc_lines[0] else 0 for line in doc_lines[start:]: lines.append(line) lines.append("") lines.append("---") lines.append("") return lines def generate_markdown_function(func_info: Dict[str, Any]) -> List[str]: """Generate markdown documentation for a function.""" lines = [] lines.append(f"### `{func_info['signature']}`") lines.append("") if func_info['doc']: doc_lines = func_info['doc'].strip().split('\n') # Skip signature line if present start = 1 if doc_lines and func_info['name'] in doc_lines[0] else 0 # Process documentation sections in_section = None for line in doc_lines[start:]: if line.strip() in ['Args:', 'Returns:', 'Raises:', 'Note:', 'Example:']: in_section = line.strip() lines.append(f"**{in_section}**") elif in_section and line.strip(): # Indent content under sections lines.append(f"{line}") else: lines.append(line) lines.append("") lines.append("---") lines.append("") return lines def generate_markdown_docs() -> str: """Generate complete markdown API documentation.""" lines = [] # Header lines.append("# McRogueFace API Reference") lines.append("") lines.append(f"*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*") lines.append("") # Module description if mcrfpy.__doc__: lines.append("## Overview") lines.append("") lines.extend(mcrfpy.__doc__.strip().split('\n')) lines.append("") # Table of contents lines.append("## Table of Contents") lines.append("") lines.append("- [Classes](#classes)") lines.append("- [Functions](#functions)") lines.append("- [Automation Module](#automation-module)") lines.append("") # Collect all components classes = [] functions = [] constants = [] for name in sorted(dir(mcrfpy)): if name.startswith('_'): continue obj = getattr(mcrfpy, name) if isinstance(obj, type): classes.append((name, obj)) elif callable(obj): functions.append((name, obj)) elif not inspect.ismodule(obj): constants.append((name, obj)) # Document classes lines.append("## Classes") lines.append("") # Group classes by category ui_classes = [] collection_classes = [] system_classes = [] other_classes = [] for name, cls in classes: if name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']: ui_classes.append((name, cls)) elif 'Collection' in name: collection_classes.append((name, cls)) elif name in ['Color', 'Vector', 'Texture', 'Font']: system_classes.append((name, cls)) else: other_classes.append((name, cls)) # UI Classes if ui_classes: lines.append("### UI Components") lines.append("") for name, cls in ui_classes: lines.extend(generate_markdown_class(get_class_info(cls))) # Collections if collection_classes: lines.append("### Collections") lines.append("") for name, cls in collection_classes: lines.extend(generate_markdown_class(get_class_info(cls))) # System Classes if system_classes: lines.append("### System Types") lines.append("") for name, cls in system_classes: lines.extend(generate_markdown_class(get_class_info(cls))) # Other Classes if other_classes: lines.append("### Other Classes") lines.append("") for name, cls in other_classes: lines.extend(generate_markdown_class(get_class_info(cls))) # Document functions lines.append("## Functions") lines.append("") # Group functions by category scene_funcs = [] audio_funcs = [] ui_funcs = [] system_funcs = [] for name, func in functions: if 'scene' in name.lower() or name in ['createScene', 'setScene']: scene_funcs.append((name, func)) elif any(x in name.lower() for x in ['sound', 'music', 'volume']): audio_funcs.append((name, func)) elif name in ['find', 'findAll']: ui_funcs.append((name, func)) else: system_funcs.append((name, func)) # Scene Management if scene_funcs: lines.append("### Scene Management") lines.append("") for name, func in scene_funcs: lines.extend(generate_markdown_function(get_function_info(func, name))) # Audio if audio_funcs: lines.append("### Audio") lines.append("") for name, func in audio_funcs: lines.extend(generate_markdown_function(get_function_info(func, name))) # UI Utilities if ui_funcs: lines.append("### UI Utilities") lines.append("") for name, func in ui_funcs: lines.extend(generate_markdown_function(get_function_info(func, name))) # System if system_funcs: lines.append("### System") lines.append("") for name, func in system_funcs: lines.extend(generate_markdown_function(get_function_info(func, name))) # Automation module if hasattr(mcrfpy, 'automation'): lines.append("## Automation Module") lines.append("") lines.append("The `mcrfpy.automation` module provides testing and automation capabilities.") lines.append("") automation = mcrfpy.automation auto_funcs = [] for name in sorted(dir(automation)): if not name.startswith('_'): obj = getattr(automation, name) if callable(obj): auto_funcs.append((name, obj)) for name, func in auto_funcs: # Format as static method func_info = get_function_info(func, name) lines.append(f"### `automation.{func_info['signature']}`") lines.append("") if func_info['doc']: lines.append(func_info['doc']) lines.append("") lines.append("---") lines.append("") return '\n'.join(lines) def generate_html_docs(markdown_content: str) -> str: """Convert markdown to HTML.""" # Simple conversion - in production use a proper markdown parser html = [''] html.append('') html.append('') html.append('McRogueFace API Reference') html.append('') html.append('') # Very basic markdown to HTML conversion lines = markdown_content.split('\n') in_code_block = False in_list = False for line in lines: stripped = line.strip() if stripped.startswith('```'): if in_code_block: html.append('') in_code_block = False else: lang = stripped[3:] or 'python' html.append(f'
')
                in_code_block = True
            continue
        
        if in_code_block:
            html.append(line)
            continue
        
        # Headers
        if stripped.startswith('#'):
            level = len(stripped.split()[0])
            text = stripped[level:].strip()
            html.append(f'{text}')
        # Lists
        elif stripped.startswith('- '):
            if not in_list:
                html.append('')
                in_list = False
            html.append('
') # Emphasis elif stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2: html.append(f'{stripped[1:-1]}') # Bold elif stripped.startswith('**') and stripped.endswith('**'): html.append(f'{stripped[2:-2]}') # Regular paragraph elif stripped: if in_list: html.append('') in_list = False # Convert inline code text = stripped if '`' in text: import re text = re.sub(r'`([^`]+)`', r'\1', text) html.append(f'

{text}

') else: if in_list: html.append('') in_list = False # Empty line html.append('') if in_list: html.append('') if in_code_block: html.append('
') html.append('') return '\n'.join(html) def main(): """Generate API documentation in multiple formats.""" print("Generating McRogueFace API Documentation...") # Create docs directory docs_dir = Path("docs") docs_dir.mkdir(exist_ok=True) # Generate markdown documentation print("- Generating Markdown documentation...") markdown_content = generate_markdown_docs() # Write markdown md_path = docs_dir / "API_REFERENCE.md" with open(md_path, 'w') as f: f.write(markdown_content) print(f" ✓ Written to {md_path}") # Generate HTML print("- Generating HTML documentation...") html_content = generate_html_docs(markdown_content) # Write HTML html_path = docs_dir / "api_reference.html" with open(html_path, 'w') as f: f.write(html_content) print(f" ✓ Written to {html_path}") # Summary statistics lines = markdown_content.split('\n') class_count = markdown_content.count('### class') func_count = len([l for l in lines if l.strip().startswith('### `') and 'class' not in l]) print("\nDocumentation Statistics:") print(f"- Classes documented: {class_count}") print(f"- Functions documented: {func_count}") print(f"- Total lines: {len(lines)}") print(f"- File size: {len(markdown_content):,} bytes") print("\nAPI documentation generated successfully!") if __name__ == '__main__': main()