482 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
#!/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 = ['<!DOCTYPE html>']
 | 
						|
    html.append('<html><head>')
 | 
						|
    html.append('<meta charset="UTF-8">')
 | 
						|
    html.append('<title>McRogueFace API Reference</title>')
 | 
						|
    html.append('<style>')
 | 
						|
    html.append('''
 | 
						|
        body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; 
 | 
						|
               line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
 | 
						|
        h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
 | 
						|
        h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
 | 
						|
        h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
 | 
						|
        code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
 | 
						|
        pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
 | 
						|
        pre code { background: none; padding: 0; }
 | 
						|
        blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
 | 
						|
        hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
 | 
						|
        a { color: #3498db; text-decoration: none; }
 | 
						|
        a:hover { text-decoration: underline; }
 | 
						|
        .property { color: #27ae60; }
 | 
						|
        .method { color: #2980b9; }
 | 
						|
        .class-name { color: #8e44ad; font-weight: bold; }
 | 
						|
        ul { padding-left: 24px; }
 | 
						|
        li { margin: 4px 0; }
 | 
						|
    ''')
 | 
						|
    html.append('</style>')
 | 
						|
    html.append('</head><body>')
 | 
						|
    
 | 
						|
    # 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('</code></pre>')
 | 
						|
                in_code_block = False
 | 
						|
            else:
 | 
						|
                lang = stripped[3:] or 'python'
 | 
						|
                html.append(f'<pre><code class="language-{lang}">')
 | 
						|
                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'<h{level}>{text}</h{level}>')
 | 
						|
        # Lists
 | 
						|
        elif stripped.startswith('- '):
 | 
						|
            if not in_list:
 | 
						|
                html.append('<ul>')
 | 
						|
                in_list = True
 | 
						|
            html.append(f'<li>{stripped[2:]}</li>')
 | 
						|
        # Horizontal rule
 | 
						|
        elif stripped == '---':
 | 
						|
            if in_list:
 | 
						|
                html.append('</ul>')
 | 
						|
                in_list = False
 | 
						|
            html.append('<hr>')
 | 
						|
        # Emphasis
 | 
						|
        elif stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2:
 | 
						|
            html.append(f'<em>{stripped[1:-1]}</em>')
 | 
						|
        # Bold
 | 
						|
        elif stripped.startswith('**') and stripped.endswith('**'):
 | 
						|
            html.append(f'<strong>{stripped[2:-2]}</strong>')
 | 
						|
        # Regular paragraph
 | 
						|
        elif stripped:
 | 
						|
            if in_list:
 | 
						|
                html.append('</ul>')
 | 
						|
                in_list = False
 | 
						|
            # Convert inline code
 | 
						|
            text = stripped
 | 
						|
            if '`' in text:
 | 
						|
                import re
 | 
						|
                text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
 | 
						|
            html.append(f'<p>{text}</p>')
 | 
						|
        else:
 | 
						|
            if in_list:
 | 
						|
                html.append('</ul>')
 | 
						|
                in_list = False
 | 
						|
            # Empty line
 | 
						|
            html.append('')
 | 
						|
    
 | 
						|
    if in_list:
 | 
						|
        html.append('</ul>')
 | 
						|
    if in_code_block:
 | 
						|
        html.append('</code></pre>')
 | 
						|
    
 | 
						|
    html.append('</body></html>')
 | 
						|
    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() |