From 1f6175bfa5e14af657ebf2affd292730c1e4f67f Mon Sep 17 00:00:00 2001 From: John McCardle Date: Thu, 30 Oct 2025 11:51:21 -0400 Subject: [PATCH] refactor: remove obsolete documentation generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes ~3,979 lines of hardcoded Python documentation dictionaries. Documentation is now generated from C++ docstrings via macros. Deleted: - generate_complete_api_docs.py (959 lines) - generate_complete_markdown_docs.py (820 lines) - generate_api_docs.py (481 lines) - generate_api_docs_simple.py (118 lines) - generate_api_docs_html.py (1,601 lines) Addresses issue #97. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tools/generate_api_docs.py | 482 ------- tools/generate_api_docs_html.py | 1602 ---------------------- tools/generate_api_docs_simple.py | 119 -- tools/generate_complete_api_docs.py | 960 ------------- tools/generate_complete_markdown_docs.py | 821 ----------- 5 files changed, 3984 deletions(-) delete mode 100644 tools/generate_api_docs.py delete mode 100644 tools/generate_api_docs_html.py delete mode 100644 tools/generate_api_docs_simple.py delete mode 100644 tools/generate_complete_api_docs.py delete mode 100644 tools/generate_complete_markdown_docs.py diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py deleted file mode 100644 index d1e100f..0000000 --- a/tools/generate_api_docs.py +++ /dev/null @@ -1,482 +0,0 @@ -#!/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 = True - html.append(f'
  • {stripped[2:]}
  • ') - # Horizontal rule - elif stripped == '---': - if 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() \ No newline at end of file diff --git a/tools/generate_api_docs_html.py b/tools/generate_api_docs_html.py deleted file mode 100644 index fe3cf08..0000000 --- a/tools/generate_api_docs_html.py +++ /dev/null @@ -1,1602 +0,0 @@ -#!/usr/bin/env python3 -"""Generate high-quality HTML API reference documentation for McRogueFace.""" - -import os -import sys -import datetime -import html -from pathlib import Path -import mcrfpy - -def escape_html(text: str) -> str: - """Escape HTML special characters.""" - return html.escape(text) if text else "" - -def format_docstring_as_html(docstring: str) -> str: - """Convert docstring to properly formatted HTML.""" - if not docstring: - return "" - - # Split and process lines - lines = docstring.strip().split('\n') - result = [] - in_code_block = False - - for line in lines: - # Convert \n to actual newlines - line = line.replace('\\n', '\n') - - # Handle code blocks - if line.strip().startswith('```'): - if in_code_block: - result.append('') - in_code_block = False - else: - result.append('
')
-                in_code_block = True
-            continue
-            
-        # Convert markdown-style code to HTML
-        if '`' in line and not in_code_block:
-            import re
-            line = re.sub(r'`([^`]+)`', r'\1', line)
-        
-        if in_code_block:
-            result.append(escape_html(line))
-        else:
-            result.append(escape_html(line) + '
') - - if in_code_block: - result.append('
') - - return '\n'.join(result) - -def get_class_details(cls): - """Get detailed information about a class.""" - info = { - 'name': cls.__name__, - 'doc': cls.__doc__ or "", - 'methods': {}, - 'properties': {}, - 'bases': [] - } - - # Get real base classes (excluding object) - for base in cls.__bases__: - if base.__name__ != 'object': - info['bases'].append(base.__name__) - - # Special handling for Entity which doesn't inherit from Drawable - if cls.__name__ == 'Entity' and 'Drawable' in info['bases']: - info['bases'].remove('Drawable') - - # Get methods and properties - for attr_name in dir(cls): - if attr_name.startswith('__') and attr_name != '__init__': - continue - - try: - attr = getattr(cls, attr_name) - - if isinstance(attr, property): - info['properties'][attr_name] = { - 'doc': (attr.fget.__doc__ if attr.fget else "") or "", - 'readonly': attr.fset is None - } - elif callable(attr) and not attr_name.startswith('_'): - info['methods'][attr_name] = attr.__doc__ or "" - except: - pass - - return info - -def generate_class_init_docs(class_name): - """Generate initialization documentation for specific classes.""" - init_docs = { - 'Entity': { - 'signature': 'Entity(x=0, y=0, sprite_id=0)', - 'description': 'Game entity that can be placed in a Grid.', - 'args': [ - ('x', 'int', 'Grid x coordinate. Default: 0'), - ('y', 'int', 'Grid y coordinate. Default: 0'), - ('sprite_id', 'int', 'Sprite index for rendering. Default: 0') - ], - 'example': '''entity = mcrfpy.Entity(5, 10, 42) -entity.move(1, 0) # Move right one tile''' - }, - 'Color': { - 'signature': 'Color(r=255, g=255, b=255, a=255)', - 'description': 'RGBA color representation.', - 'args': [ - ('r', 'int', 'Red component (0-255). Default: 255'), - ('g', 'int', 'Green component (0-255). Default: 255'), - ('b', 'int', 'Blue component (0-255). Default: 255'), - ('a', 'int', 'Alpha component (0-255). Default: 255') - ], - 'example': 'red = mcrfpy.Color(255, 0, 0)' - }, - 'Font': { - 'signature': 'Font(filename)', - 'description': 'Load a font from file.', - 'args': [ - ('filename', 'str', 'Path to font file (TTF/OTF)') - ] - }, - 'Texture': { - 'signature': 'Texture(filename)', - 'description': 'Load a texture from file.', - 'args': [ - ('filename', 'str', 'Path to image file (PNG/JPG/BMP)') - ] - }, - 'Vector': { - 'signature': 'Vector(x=0.0, y=0.0)', - 'description': '2D vector for positions and directions.', - 'args': [ - ('x', 'float', 'X component. Default: 0.0'), - ('y', 'float', 'Y component. Default: 0.0') - ] - }, - 'Animation': { - 'signature': 'Animation(property_name, start_value, end_value, duration, transition="linear", loop=False)', - 'description': 'Animate UI element properties over time.', - 'args': [ - ('property_name', 'str', 'Property to animate (e.g., "x", "y", "scale")'), - ('start_value', 'float', 'Starting value'), - ('end_value', 'float', 'Ending value'), - ('duration', 'float', 'Duration in seconds'), - ('transition', 'str', 'Easing function. Default: "linear"'), - ('loop', 'bool', 'Whether to loop. Default: False') - ], - 'properties': ['current_value', 'elapsed_time', 'is_running', 'is_finished'] - }, - 'GridPoint': { - 'description': 'Represents a single tile in a Grid.', - 'properties': ['x', 'y', 'texture_index', 'solid', 'transparent', 'color'] - }, - 'GridPointState': { - 'description': 'State information for a GridPoint.', - 'properties': ['visible', 'discovered', 'custom_flags'] - }, - 'Timer': { - 'signature': 'Timer(name, callback, interval_ms)', - 'description': 'Create a recurring timer.', - 'args': [ - ('name', 'str', 'Unique timer identifier'), - ('callback', 'callable', 'Function to call'), - ('interval_ms', 'int', 'Interval in milliseconds') - ] - } - } - - return init_docs.get(class_name, {}) - -def generate_method_docs(method_name, class_name): - """Generate documentation for specific methods.""" - method_docs = { - # Base Drawable methods (inherited by all UI elements) - 'Drawable': { - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of this drawable element.', - 'returns': 'tuple: (x, y, width, height) representing the element\'s bounds', - 'note': 'The bounds are in screen coordinates and account for current position and size.' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the element by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ], - 'note': 'This modifies the x and y position properties by the given amounts.' - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Resize the element to new dimensions.', - 'args': [ - ('width', 'float', 'New width in pixels'), - ('height', 'float', 'New height in pixels') - ], - 'note': 'Behavior varies by element type. Some elements may ignore or constrain dimensions.' - } - }, - - # Caption-specific methods - 'Caption': { - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of the text.', - 'returns': 'tuple: (x, y, width, height) based on text content and font size', - 'note': 'Bounds are automatically calculated from the rendered text dimensions.' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the caption by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ] - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Set text wrapping bounds (limited support).', - 'args': [ - ('width', 'float', 'Maximum width for text wrapping'), - ('height', 'float', 'Currently unused') - ], - 'note': 'Full text wrapping is not yet implemented. This prepares for future multiline support.' - } - }, - - # Entity-specific methods - 'Entity': { - 'at': { - 'signature': 'at(x, y)', - 'description': 'Get the GridPointState at the specified grid coordinates relative to this entity.', - 'args': [ - ('x', 'int', 'Grid x offset from entity position'), - ('y', 'int', 'Grid y offset from entity position') - ], - 'returns': 'GridPointState: State of the grid point at the specified position', - 'note': 'Requires entity to be associated with a grid. Raises ValueError if not.' - }, - 'die': { - 'signature': 'die()', - 'description': 'Remove this entity from its parent grid.', - 'returns': 'None', - 'note': 'The entity object remains valid but is no longer rendered or updated.' - }, - 'index': { - 'signature': 'index()', - 'description': 'Get the index of this entity in its grid\'s entity collection.', - 'returns': 'int: Zero-based index in the parent grid\'s entity list', - 'note': 'Raises RuntimeError if not associated with a grid, ValueError if not found.' - }, - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of the entity\'s sprite.', - 'returns': 'tuple: (x, y, width, height) of the sprite bounds', - 'note': 'Delegates to the internal sprite\'s get_bounds method.' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the entity by a relative offset in pixels.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ], - 'note': 'Updates both sprite position and entity grid position.' - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Entities do not support direct resizing.', - 'args': [ - ('width', 'float', 'Ignored'), - ('height', 'float', 'Ignored') - ], - 'note': 'This method exists for interface compatibility but has no effect.' - } - }, - - # Frame-specific methods - 'Frame': { - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of the frame.', - 'returns': 'tuple: (x, y, width, height) representing the frame bounds' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the frame and all its children by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ], - 'note': 'Child elements maintain their relative positions within the frame.' - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Resize the frame to new dimensions.', - 'args': [ - ('width', 'float', 'New width in pixels'), - ('height', 'float', 'New height in pixels') - ], - 'note': 'Does not automatically resize children. Set clip_children=True to clip overflow.' - } - }, - - # Grid-specific methods - 'Grid': { - 'at': { - 'signature': 'at(x, y) or at((x, y))', - 'description': 'Get the GridPoint at the specified grid coordinates.', - 'args': [ - ('x', 'int', 'Grid x coordinate (0-based)'), - ('y', 'int', 'Grid y coordinate (0-based)') - ], - 'returns': 'GridPoint: The grid point at (x, y)', - 'note': 'Raises IndexError if coordinates are out of range. Accepts either two arguments or a tuple.', - 'example': 'point = grid.at(5, 3) # or grid.at((5, 3))' - }, - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of the entire grid.', - 'returns': 'tuple: (x, y, width, height) of the grid\'s display area' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the grid display by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ], - 'note': 'Moves the entire grid viewport. Use center property to pan within the grid.' - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Resize the grid\'s display viewport.', - 'args': [ - ('width', 'float', 'New viewport width in pixels'), - ('height', 'float', 'New viewport height in pixels') - ], - 'note': 'Changes the visible area, not the grid dimensions. Use zoom to scale content.' - } - }, - - # Sprite-specific methods - 'Sprite': { - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of the sprite.', - 'returns': 'tuple: (x, y, width, height) based on texture size and scale', - 'note': 'Bounds account for current scale. Returns (x, y, 0, 0) if no texture.' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the sprite by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ] - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Resize the sprite by adjusting its scale.', - 'args': [ - ('width', 'float', 'Target width in pixels'), - ('height', 'float', 'Target height in pixels') - ], - 'note': 'Calculates and applies uniform scale to best fit the target dimensions.' - } - }, - - 'Animation': { - 'get_current_value': { - 'signature': 'get_current_value()', - 'description': 'Get the current interpolated value.', - 'returns': 'float: Current animation value' - }, - 'start': { - 'signature': 'start(target)', - 'description': 'Start the animation on a target UI element.', - 'args': [('target', 'UIDrawable', 'The element to animate')] - } - }, - - # Collection methods (shared by EntityCollection and UICollection) - 'EntityCollection': { - 'append': { - 'signature': 'append(entity)', - 'description': 'Add an entity to the end of the collection.', - 'args': [ - ('entity', 'Entity', 'The entity to add') - ] - }, - 'remove': { - 'signature': 'remove(entity)', - 'description': 'Remove the first occurrence of an entity from the collection.', - 'args': [ - ('entity', 'Entity', 'The entity to remove') - ], - 'note': 'Raises ValueError if entity is not found.' - }, - 'extend': { - 'signature': 'extend(iterable)', - 'description': 'Add multiple entities from an iterable.', - 'args': [ - ('iterable', 'iterable', 'An iterable of Entity objects') - ] - }, - 'count': { - 'signature': 'count(entity)', - 'description': 'Count occurrences of an entity in the collection.', - 'args': [ - ('entity', 'Entity', 'The entity to count') - ], - 'returns': 'int: Number of times the entity appears' - }, - 'index': { - 'signature': 'index(entity)', - 'description': 'Find the index of the first occurrence of an entity.', - 'args': [ - ('entity', 'Entity', 'The entity to find') - ], - 'returns': 'int: Zero-based index of the entity', - 'note': 'Raises ValueError if entity is not found.' - } - }, - - 'UICollection': { - 'append': { - 'signature': 'append(drawable)', - 'description': 'Add a drawable element to the end of the collection.', - 'args': [ - ('drawable', 'Drawable', 'Any UI element (Frame, Caption, Sprite, Grid)') - ] - }, - 'remove': { - 'signature': 'remove(drawable)', - 'description': 'Remove the first occurrence of a drawable from the collection.', - 'args': [ - ('drawable', 'Drawable', 'The drawable to remove') - ], - 'note': 'Raises ValueError if drawable is not found.' - }, - 'extend': { - 'signature': 'extend(iterable)', - 'description': 'Add multiple drawables from an iterable.', - 'args': [ - ('iterable', 'iterable', 'An iterable of Drawable objects') - ] - }, - 'count': { - 'signature': 'count(drawable)', - 'description': 'Count occurrences of a drawable in the collection.', - 'args': [ - ('drawable', 'Drawable', 'The drawable to count') - ], - 'returns': 'int: Number of times the drawable appears' - }, - 'index': { - 'signature': 'index(drawable)', - 'description': 'Find the index of the first occurrence of a drawable.', - 'args': [ - ('drawable', 'Drawable', 'The drawable to find') - ], - 'returns': 'int: Zero-based index of the drawable', - 'note': 'Raises ValueError if drawable is not found.' - } - } - } - - return method_docs.get(class_name, {}).get(method_name, {}) - -def generate_function_docs(): - """Generate documentation for all mcrfpy module functions.""" - function_docs = { - # Scene Management - 'createScene': { - 'signature': 'createScene(name: str) -> None', - 'description': 'Create a new empty scene.', - 'args': [ - ('name', 'str', 'Unique name for the new scene') - ], - 'returns': 'None', - 'exceptions': [ - ('ValueError', 'If a scene with this name already exists') - ], - 'note': 'The scene is created but not made active. Use setScene() to switch to it.', - 'example': '''mcrfpy.createScene("game") -mcrfpy.createScene("menu") -mcrfpy.setScene("game")''' - }, - - 'setScene': { - 'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None', - 'description': 'Switch to a different scene with optional transition effect.', - 'args': [ - ('scene', 'str', 'Name of the scene to switch to'), - ('transition', 'str', 'Transition type ("fade", "slide_left", "slide_right", "slide_up", "slide_down"). Default: None'), - ('duration', 'float', 'Transition duration in seconds. Default: 0.0 for instant') - ], - 'returns': 'None', - 'exceptions': [ - ('KeyError', 'If the scene doesn\'t exist'), - ('ValueError', 'If the transition type is invalid') - ], - 'example': '''mcrfpy.setScene("menu") -mcrfpy.setScene("game", "fade", 0.5) -mcrfpy.setScene("credits", "slide_left", 1.0)''' - }, - - 'currentScene': { - 'signature': 'currentScene() -> str', - 'description': 'Get the name of the currently active scene.', - 'args': [], - 'returns': 'str: Name of the current scene', - 'example': '''scene = mcrfpy.currentScene() -print(f"Currently in scene: {scene}")''' - }, - - 'sceneUI': { - 'signature': 'sceneUI(scene: str = None) -> list', - 'description': 'Get all UI elements for a scene.', - 'args': [ - ('scene', 'str', 'Scene name. If None, uses current scene. Default: None') - ], - 'returns': 'list: All UI elements (Frame, Caption, Sprite, Grid) in the scene', - 'exceptions': [ - ('KeyError', 'If the specified scene doesn\'t exist') - ], - 'example': '''# Get UI for current scene -ui_elements = mcrfpy.sceneUI() - -# Get UI for specific scene -menu_ui = mcrfpy.sceneUI("menu") -for element in menu_ui: - print(f"{element.name}: {type(element).__name__}")''' - }, - - 'keypressScene': { - 'signature': 'keypressScene(handler: callable) -> None', - 'description': 'Set the keyboard event handler for the current scene.', - 'args': [ - ('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)') - ], - 'returns': 'None', - 'note': 'The handler is called for every key press and release event. Key names are single characters (e.g., "A", "1") or special keys (e.g., "Space", "Enter", "Escape").', - 'example': '''def on_key(key, pressed): - if pressed: - if key == "Space": - player.jump() - elif key == "Escape": - mcrfpy.setScene("pause_menu") - else: - # Handle key release - if key in ["A", "D"]: - player.stop_moving() - -mcrfpy.keypressScene(on_key)''' - }, - - # Audio Functions - 'createSoundBuffer': { - 'signature': 'createSoundBuffer(filename: str) -> int', - 'description': 'Load a sound effect from a file and return its buffer ID.', - 'args': [ - ('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)') - ], - 'returns': 'int: Buffer ID for use with playSound()', - 'exceptions': [ - ('RuntimeError', 'If the file cannot be loaded') - ], - 'note': 'Sound buffers are stored in memory for fast playback. Load sound effects once and reuse the buffer ID.', - 'example': '''# Load sound effects -jump_sound = mcrfpy.createSoundBuffer("assets/sounds/jump.wav") -coin_sound = mcrfpy.createSoundBuffer("assets/sounds/coin.ogg") - -# Play later -mcrfpy.playSound(jump_sound)''' - }, - - 'loadMusic': { - 'signature': 'loadMusic(filename: str, loop: bool = True) -> None', - 'description': 'Load and immediately play background music from a file.', - 'args': [ - ('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'), - ('loop', 'bool', 'Whether to loop the music. Default: True') - ], - 'returns': 'None', - 'note': 'Only one music track can play at a time. Loading new music stops the current track.', - 'example': '''# Play looping background music -mcrfpy.loadMusic("assets/music/theme.ogg") - -# Play music once without looping -mcrfpy.loadMusic("assets/music/victory.ogg", loop=False)''' - }, - - 'playSound': { - 'signature': 'playSound(buffer_id: int) -> None', - 'description': 'Play a sound effect using a previously loaded buffer.', - 'args': [ - ('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()') - ], - 'returns': 'None', - 'exceptions': [ - ('RuntimeError', 'If the buffer ID is invalid') - ], - 'note': 'Multiple sounds can play simultaneously. Each call creates a new sound instance.', - 'example': '''# Load once -explosion_sound = mcrfpy.createSoundBuffer("explosion.wav") - -# Play multiple times -for enemy in destroyed_enemies: - mcrfpy.playSound(explosion_sound)''' - }, - - 'getMusicVolume': { - 'signature': 'getMusicVolume() -> int', - 'description': 'Get the current music volume level.', - 'args': [], - 'returns': 'int: Current volume (0-100)', - 'example': '''volume = mcrfpy.getMusicVolume() -print(f"Music volume: {volume}%")''' - }, - - 'getSoundVolume': { - 'signature': 'getSoundVolume() -> int', - 'description': 'Get the current sound effects volume level.', - 'args': [], - 'returns': 'int: Current volume (0-100)', - 'example': '''volume = mcrfpy.getSoundVolume() -print(f"Sound effects volume: {volume}%")''' - }, - - 'setMusicVolume': { - 'signature': 'setMusicVolume(volume: int) -> None', - 'description': 'Set the global music volume.', - 'args': [ - ('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)') - ], - 'returns': 'None', - 'example': '''# Mute music -mcrfpy.setMusicVolume(0) - -# Half volume -mcrfpy.setMusicVolume(50) - -# Full volume -mcrfpy.setMusicVolume(100)''' - }, - - 'setSoundVolume': { - 'signature': 'setSoundVolume(volume: int) -> None', - 'description': 'Set the global sound effects volume.', - 'args': [ - ('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)') - ], - 'returns': 'None', - 'example': '''# Audio settings from options menu -mcrfpy.setSoundVolume(sound_slider.value) -mcrfpy.setMusicVolume(music_slider.value)''' - }, - - # UI Utilities - 'find': { - 'signature': 'find(name: str, scene: str = None) -> UIDrawable | None', - 'description': 'Find the first UI element with the specified name.', - 'args': [ - ('name', 'str', 'Exact name to search for'), - ('scene', 'str', 'Scene to search in. Default: current scene') - ], - 'returns': 'Frame, Caption, Sprite, Grid, or Entity if found; None otherwise', - 'note': 'Searches scene UI elements and entities within grids. Returns the first match found.', - 'example': '''# Find in current scene -player = mcrfpy.find("player") -if player: - player.x = 100 - -# Find in specific scene -menu_button = mcrfpy.find("start_button", "main_menu")''' - }, - - 'findAll': { - 'signature': 'findAll(pattern: str, scene: str = None) -> list', - 'description': 'Find all UI elements matching a name pattern.', - 'args': [ - ('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'), - ('scene', 'str', 'Scene to search in. Default: current scene') - ], - 'returns': 'list: All matching UI elements and entities', - 'note': 'Supports wildcard patterns for flexible searching.', - 'example': '''# Find all enemies -enemies = mcrfpy.findAll("enemy*") -for enemy in enemies: - enemy.sprite_id = 0 # Reset sprite - -# Find all buttons -buttons = mcrfpy.findAll("*_button") -for btn in buttons: - btn.visible = True - -# Find exact matches -health_bars = mcrfpy.findAll("health_bar") # No wildcards = exact match''' - }, - - # System Functions - 'exit': { - 'signature': 'exit() -> None', - 'description': 'Cleanly shut down the game engine and exit the application.', - 'args': [], - 'returns': 'None', - 'note': 'This immediately closes the window and terminates the program. Ensure any necessary cleanup is done before calling.', - 'example': '''def quit_game(): - # Save game state - save_progress() - - # Exit - mcrfpy.exit()''' - }, - - 'getMetrics': { - 'signature': 'getMetrics() -> dict', - 'description': 'Get current performance metrics.', - 'args': [], - 'returns': '''dict: Performance data with keys: - - frame_time: Last frame duration in seconds - - avg_frame_time: Average frame time - - fps: Frames per second - - draw_calls: Number of draw calls - - ui_elements: Total UI element count - - visible_elements: Visible element count - - current_frame: Frame counter - - runtime: Total runtime in seconds''', - 'example': '''metrics = mcrfpy.getMetrics() -print(f"FPS: {metrics['fps']}") -print(f"Frame time: {metrics['frame_time']*1000:.1f}ms") -print(f"Draw calls: {metrics['draw_calls']}") -print(f"Runtime: {metrics['runtime']:.1f}s") - -# Performance monitoring -if metrics['fps'] < 30: - print("Performance warning: FPS below 30")''' - }, - - 'setTimer': { - 'signature': 'setTimer(name: str, handler: callable, interval: int) -> None', - 'description': 'Create or update a recurring timer.', - 'args': [ - ('name', 'str', 'Unique identifier for the timer'), - ('handler', 'callable', 'Function called with (runtime: float) parameter'), - ('interval', 'int', 'Time between calls in milliseconds') - ], - 'returns': 'None', - 'note': 'If a timer with this name exists, it will be replaced. The handler receives the total runtime in seconds as its argument.', - 'example': '''# Simple repeating timer -def spawn_enemy(runtime): - enemy = mcrfpy.Entity() - enemy.x = random.randint(0, 800) - grid.entities.append(enemy) - -mcrfpy.setTimer("enemy_spawner", spawn_enemy, 2000) # Every 2 seconds - -# Timer with runtime check -def update_timer(runtime): - time_left = 60 - runtime - timer_text.text = f"Time: {int(time_left)}" - if time_left <= 0: - mcrfpy.delTimer("game_timer") - game_over() - -mcrfpy.setTimer("game_timer", update_timer, 100) # Update every 100ms''' - }, - - 'delTimer': { - 'signature': 'delTimer(name: str) -> None', - 'description': 'Stop and remove a timer.', - 'args': [ - ('name', 'str', 'Timer identifier to remove') - ], - 'returns': 'None', - 'note': 'No error is raised if the timer doesn\'t exist.', - 'example': '''# Stop spawning enemies -mcrfpy.delTimer("enemy_spawner") - -# Clean up all game timers -for timer_name in ["enemy_spawner", "powerup_timer", "score_updater"]: - mcrfpy.delTimer(timer_name)''' - }, - - 'setScale': { - 'signature': 'setScale(multiplier: float) -> None', - 'description': 'Scale the game window size.', - 'args': [ - ('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)') - ], - 'returns': 'None', - 'exceptions': [ - ('ValueError', 'If multiplier is not between 0.2 and 4.0') - ], - 'note': 'The internal resolution remains 1024x768, but the window is scaled. This is deprecated - use Window.resolution instead.', - 'example': '''# Double the window size -mcrfpy.setScale(2.0) - -# Half size window -mcrfpy.setScale(0.5) - -# Better approach (not deprecated): -mcrfpy.Window.resolution = (1920, 1080)''' - } - } - - return function_docs - -def generate_collection_docs(class_name): - """Generate documentation for collection classes.""" - collection_docs = { - 'EntityCollection': { - 'description': 'Container for Entity objects in a Grid. Supports iteration and indexing.', - 'methods': { - 'append': 'Add an entity to the collection', - 'remove': 'Remove an entity from the collection', - 'extend': 'Add multiple entities from an iterable', - 'count': 'Count occurrences of an entity', - 'index': 'Find the index of an entity' - } - }, - 'UICollection': { - 'description': 'Container for UI drawable elements. Supports iteration and indexing.', - 'methods': { - 'append': 'Add a UI element to the collection', - 'remove': 'Remove a UI element from the collection', - 'extend': 'Add multiple UI elements from an iterable', - 'count': 'Count occurrences of a UI element', - 'index': 'Find the index of a UI element' - } - }, - 'UICollectionIter': { - 'description': 'Iterator for UICollection. Automatically created when iterating over a UICollection.' - }, - 'UIEntityCollectionIter': { - 'description': 'Iterator for EntityCollection. Automatically created when iterating over an EntityCollection.' - } - } - - return collection_docs.get(class_name, {}) - -def format_class_html(cls_info, class_name): - """Format a class as HTML with proper structure.""" - html_parts = [] - - # Class header - html_parts.append(f'
') - html_parts.append(f'

class {class_name}

') - - # Inheritance - if cls_info['bases']: - html_parts.append(f'

Inherits from: {", ".join(cls_info["bases"])}

') - - # Get additional documentation - init_info = generate_class_init_docs(class_name) - collection_info = generate_collection_docs(class_name) - - # Constructor signature for classes with __init__ - if init_info.get('signature'): - html_parts.append('
') - html_parts.append('
')
-        html_parts.append(escape_html(init_info['signature']))
-        html_parts.append('
') - html_parts.append('
') - - # Description - description = "" - if collection_info.get('description'): - description = collection_info['description'] - elif init_info.get('description'): - description = init_info['description'] - elif cls_info['doc']: - # Parse description from docstring - doc_lines = cls_info['doc'].strip().split('\n') - # Skip constructor line if present - start_idx = 1 if doc_lines and '(' in doc_lines[0] else 0 - if start_idx < len(doc_lines): - description = '\n'.join(doc_lines[start_idx:]).strip() - - if description: - html_parts.append('
') - html_parts.append(f'

{format_docstring_as_html(description)}

') - html_parts.append('
') - - # Constructor arguments - if init_info.get('args'): - html_parts.append('
') - html_parts.append('

Arguments:

') - html_parts.append('
') - for arg_name, arg_type, arg_desc in init_info['args']: - html_parts.append(f'
{arg_name} ({arg_type})
') - html_parts.append(f'
{escape_html(arg_desc)}
') - html_parts.append('
') - html_parts.append('
') - - # Properties/Attributes - props = cls_info.get('properties', {}) - if props or init_info.get('properties'): - html_parts.append('
') - html_parts.append('

Attributes:

') - html_parts.append('
') - - # Add documented properties from init_info - if init_info.get('properties'): - for prop_name in init_info['properties']: - html_parts.append(f'
{prop_name}
') - html_parts.append(f'
Property of {class_name}
') - - # Add actual properties - for prop_name, prop_info in props.items(): - readonly = ' (read-only)' if prop_info.get('readonly') else '' - html_parts.append(f'
{prop_name}{readonly}
') - if prop_info.get('doc'): - html_parts.append(f'
{escape_html(prop_info["doc"])}
') - - html_parts.append('
') - html_parts.append('
') - - # Methods - methods = cls_info.get('methods', {}) - collection_methods = collection_info.get('methods', {}) - - if methods or collection_methods: - html_parts.append('
') - html_parts.append('

Methods:

') - - for method_name, method_doc in {**collection_methods, **methods}.items(): - if method_name == '__init__': - continue - - html_parts.append('
') - - # Get specific method documentation - method_info = generate_method_docs(method_name, class_name) - - if method_info: - # Use detailed documentation - html_parts.append(f'
{method_info["signature"]}
') - html_parts.append(f'

{escape_html(method_info["description"])}

') - - if method_info.get('args'): - html_parts.append('

Arguments:

') - html_parts.append('
    ') - for arg in method_info['args']: - if len(arg) == 3: - html_parts.append(f'
  • {arg[0]} ({arg[1]}): {arg[2]}
  • ') - else: - html_parts.append(f'
  • {arg[0]} ({arg[1]})
  • ') - html_parts.append('
') - - if method_info.get('returns'): - html_parts.append(f'

Returns: {escape_html(method_info["returns"])}

') - - if method_info.get('note'): - html_parts.append(f'

Note: {escape_html(method_info["note"])}

') - else: - # Use docstring - html_parts.append(f'
{method_name}(...)
') - if isinstance(method_doc, str) and method_doc: - html_parts.append(f'

{escape_html(method_doc)}

') - - html_parts.append('
') - - html_parts.append('
') - - # Example - if init_info.get('example'): - html_parts.append('
') - html_parts.append('

Example:

') - html_parts.append('
')
-        html_parts.append(escape_html(init_info['example']))
-        html_parts.append('
') - html_parts.append('
') - - html_parts.append('
') - html_parts.append('
') - - return '\n'.join(html_parts) - -def generate_html_documentation(): - """Generate complete HTML API documentation.""" - html_parts = [] - - # HTML header - html_parts.append(''' - - - - - McRogueFace API Reference - - - -
-''') - - # Title and timestamp - html_parts.append('

McRogueFace API Reference

') - html_parts.append(f'

Generated on {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}

') - - # Overview - if mcrfpy.__doc__: - html_parts.append('
') - html_parts.append('

Overview

') - # Process the docstring properly - doc_lines = mcrfpy.__doc__.strip().split('\\n') - for line in doc_lines: - if line.strip().startswith('Example:'): - html_parts.append('

Example:

') - html_parts.append('
')
-            elif line.strip() and not line.startswith(' '):
-                html_parts.append(f'

{escape_html(line)}

') - elif line.strip(): - # Code line - html_parts.append(escape_html(line)) - html_parts.append('
') - html_parts.append('
') - - # Table of Contents - html_parts.append('
') - html_parts.append('

Table of Contents

') - html_parts.append('') - html_parts.append('
') - - # Collect all components - classes = {} - functions = {} - - for name in sorted(dir(mcrfpy)): - if name.startswith('_'): - continue - - obj = getattr(mcrfpy, name) - - if isinstance(obj, type): - classes[name] = obj - elif callable(obj) and not isinstance(obj, type): - # Include built-in functions and other callables (but not classes) - functions[name] = obj - - - # Classes section - html_parts.append('

Classes

') - - # Group classes - ui_classes = ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity'] - collection_classes = ['EntityCollection', 'UICollection', 'UICollectionIter', 'UIEntityCollectionIter'] - system_classes = ['Color', 'Vector', 'Texture', 'Font'] - other_classes = [name for name in classes if name not in ui_classes + collection_classes + system_classes] - - # UI Components - html_parts.append('

UI Components

') - for class_name in ui_classes: - if class_name in classes: - cls_info = get_class_details(classes[class_name]) - html_parts.append(format_class_html(cls_info, class_name)) - - # Collections - html_parts.append('

Collections

') - for class_name in collection_classes: - if class_name in classes: - cls_info = get_class_details(classes[class_name]) - html_parts.append(format_class_html(cls_info, class_name)) - - # System Types - html_parts.append('

System Types

') - for class_name in system_classes: - if class_name in classes: - cls_info = get_class_details(classes[class_name]) - html_parts.append(format_class_html(cls_info, class_name)) - - # Other Classes - html_parts.append('

Other Classes

') - for class_name in other_classes: - if class_name in classes: - cls_info = get_class_details(classes[class_name]) - html_parts.append(format_class_html(cls_info, class_name)) - - # Functions section - html_parts.append('

Functions

') - - # Group functions by category - scene_funcs = ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'] - audio_funcs = ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', - 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'] - ui_funcs = ['find', 'findAll'] - system_funcs = ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale'] - - # Scene Management - html_parts.append('

Scene Management

') - for func_name in scene_funcs: - if func_name in functions: - html_parts.append(format_function_html(func_name, functions[func_name])) - - # Audio - html_parts.append('

Audio

') - for func_name in audio_funcs: - if func_name in functions: - html_parts.append(format_function_html(func_name, functions[func_name])) - - # UI Utilities - html_parts.append('

UI Utilities

') - for func_name in ui_funcs: - if func_name in functions: - html_parts.append(format_function_html(func_name, functions[func_name])) - - # System - html_parts.append('

System

') - for func_name in system_funcs: - if func_name in functions: - html_parts.append(format_function_html(func_name, functions[func_name])) - - # Automation Module - if hasattr(mcrfpy, 'automation'): - html_parts.append('
') - html_parts.append('

Automation Module

') - html_parts.append('

The mcrfpy.automation module provides testing and automation capabilities for simulating user input and capturing screenshots.

') - - 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: - html_parts.append('
') - html_parts.append(f'

automation.{name}

') - if func.__doc__: - # Extract just the description, not the repeated signature - doc_lines = func.__doc__.strip().split(' - ') - if len(doc_lines) > 1: - description = doc_lines[1] - else: - description = func.__doc__.strip() - html_parts.append(f'

{escape_html(description)}

') - html_parts.append('
') - - html_parts.append('
') - - # Close HTML - html_parts.append(''' -
- -''') - - return '\n'.join(html_parts) - -def format_function_html(func_name, func): - """Format a function as HTML using enhanced documentation.""" - html_parts = [] - - html_parts.append('
') - - # Get enhanced documentation - func_docs = generate_function_docs() - - if func_name in func_docs: - doc_info = func_docs[func_name] - - # Signature - signature = doc_info.get('signature', f'{func_name}(...)') - html_parts.append(f'

{escape_html(signature)}

') - - # Description - if 'description' in doc_info: - html_parts.append(f'

{escape_html(doc_info["description"])}

') - - # Arguments - if 'args' in doc_info and doc_info['args']: - html_parts.append('
') - html_parts.append('
Arguments:
') - html_parts.append('
') - for arg_name, arg_type, arg_desc in doc_info['args']: - html_parts.append(f'
{escape_html(arg_name)} : {escape_html(arg_type)}
') - html_parts.append(f'
{escape_html(arg_desc)}
') - html_parts.append('
') - html_parts.append('
') - - # Returns - if 'returns' in doc_info and doc_info['returns']: - html_parts.append('
') - html_parts.append('
Returns:
') - html_parts.append(f'

{escape_html(doc_info["returns"])}

') - html_parts.append('
') - - # Exceptions - if 'exceptions' in doc_info and doc_info['exceptions']: - html_parts.append('
') - html_parts.append('
Raises:
') - html_parts.append('
') - for exc_type, exc_desc in doc_info['exceptions']: - html_parts.append(f'
{escape_html(exc_type)}
') - html_parts.append(f'
{escape_html(exc_desc)}
') - html_parts.append('
') - html_parts.append('
') - - # Note - if 'note' in doc_info: - html_parts.append('
') - html_parts.append(f'

Note: {escape_html(doc_info["note"])}

') - html_parts.append('
') - - # Example - if 'example' in doc_info: - html_parts.append('
') - html_parts.append('
Example:
') - html_parts.append('
')
-            html_parts.append(escape_html(doc_info['example']))
-            html_parts.append('
') - html_parts.append('
') - else: - # Fallback to parsing docstring if not in enhanced docs - doc = func.__doc__ or "" - lines = doc.strip().split('\n') if doc else [] - - # Extract signature - signature = func_name + '(...)' - if lines and '(' in lines[0]: - signature = lines[0].strip() - - html_parts.append(f'

{escape_html(signature)}

') - - # Process rest of docstring - if len(lines) > 1: - in_section = None - for line in lines[1:]: - stripped = line.strip() - - if stripped in ['Args:', 'Returns:', 'Raises:', 'Note:', 'Example:']: - in_section = stripped[:-1] - html_parts.append(f'

{in_section}:

') - elif in_section == 'Example': - if not stripped: - continue - if stripped.startswith('>>>') or (len(lines) > lines.index(line) + 1 and - lines[lines.index(line) + 1].strip().startswith('>>>')): - html_parts.append('
')
-                        html_parts.append(escape_html(stripped))
-                        # Get rest of example
-                        idx = lines.index(line) + 1
-                        while idx < len(lines) and lines[idx].strip():
-                            html_parts.append(escape_html(lines[idx]))
-                            idx += 1
-                        html_parts.append('
') - break - elif in_section and stripped: - if in_section == 'Args': - # Format arguments nicely - if ':' in stripped: - param, desc = stripped.split(':', 1) - html_parts.append(f'

{escape_html(param.strip())}: {escape_html(desc.strip())}

') - else: - html_parts.append(f'

{escape_html(stripped)}

') - else: - html_parts.append(f'

{escape_html(stripped)}

') - elif stripped and not in_section: - html_parts.append(f'

{escape_html(stripped)}

') - - html_parts.append('
') - html_parts.append('
') - - return '\n'.join(html_parts) - -def main(): - """Generate improved HTML API documentation.""" - print("Generating improved HTML API documentation...") - - # Generate HTML - html_content = generate_html_documentation() - - # Write to file - output_path = Path("docs/api_reference_improved.html") - output_path.parent.mkdir(exist_ok=True) - - with open(output_path, 'w', encoding='utf-8') as f: - f.write(html_content) - - print(f"✓ Generated {output_path}") - print(f" File size: {len(html_content):,} bytes") - - # Also generate a test to verify the HTML - test_content = '''#!/usr/bin/env python3 -"""Test the improved HTML API documentation.""" - -import os -import sys -from pathlib import Path - -def test_html_quality(): - """Test that the HTML documentation meets quality standards.""" - html_path = Path("docs/api_reference_improved.html") - - if not html_path.exists(): - print("ERROR: HTML documentation not found") - return False - - with open(html_path, 'r') as f: - content = f.read() - - # Check for common issues - issues = [] - - # Check that \\n is not present literally - if '\\\\n' in content: - issues.append("Found literal \\\\n in HTML content") - - # Check that markdown links are converted - if '[' in content and '](#' in content: - issues.append("Found unconverted markdown links") - - # Check for proper HTML structure - if '

Args:

' in content: - issues.append("Args: should not be an H4 heading") - - if '

Attributes:

' not in content: - issues.append("Missing proper Attributes: headings") - - # Check for duplicate method descriptions - if content.count('Get bounding box as (x, y, width, height)') > 20: - issues.append("Too many duplicate method descriptions") - - # Check specific improvements - if 'Entity' in content and 'Inherits from: Drawable' in content: - issues.append("Entity incorrectly shown as inheriting from Drawable") - - if not issues: - print("✓ HTML documentation passes all quality checks") - return True - else: - print("Issues found:") - for issue in issues: - print(f" - {issue}") - return False - -if __name__ == '__main__': - if test_html_quality(): - print("PASS") - sys.exit(0) - else: - print("FAIL") - sys.exit(1) -''' - - test_path = Path("tests/test_html_quality.py") - with open(test_path, 'w') as f: - f.write(test_content) - - print(f"✓ Generated test at {test_path}") - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/tools/generate_api_docs_simple.py b/tools/generate_api_docs_simple.py deleted file mode 100644 index 2bb405f..0000000 --- a/tools/generate_api_docs_simple.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 -"""Generate API reference documentation for McRogueFace - Simple version.""" - -import os -import sys -import datetime -from pathlib import Path - -import mcrfpy - -def generate_markdown_docs(): - """Generate markdown API documentation.""" - lines = [] - - # Header - lines.append("# McRogueFace API Reference") - lines.append("") - lines.append("*Generated on {}*".format(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("") - - # Collect all components - classes = [] - functions = [] - - 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)) - - # Document classes - lines.append("## Classes") - lines.append("") - - for name, cls in classes: - lines.append("### class {}".format(name)) - if cls.__doc__: - doc_lines = cls.__doc__.strip().split('\n') - for line in doc_lines[:5]: # First 5 lines - lines.append(line) - lines.append("") - lines.append("---") - lines.append("") - - # Document functions - lines.append("## Functions") - lines.append("") - - for name, func in functions: - lines.append("### {}".format(name)) - if func.__doc__: - doc_lines = func.__doc__.strip().split('\n') - for line in doc_lines[:5]: # First 5 lines - lines.append(line) - lines.append("") - lines.append("---") - lines.append("") - - # Automation module - if hasattr(mcrfpy, 'automation'): - lines.append("## Automation Module") - lines.append("") - - automation = mcrfpy.automation - for name in sorted(dir(automation)): - if not name.startswith('_'): - obj = getattr(automation, name) - if callable(obj): - lines.append("### automation.{}".format(name)) - if obj.__doc__: - lines.append(obj.__doc__.strip().split('\n')[0]) - lines.append("") - - return '\n'.join(lines) - -def main(): - """Generate API documentation.""" - print("Generating McRogueFace API Documentation...") - - # Create docs directory - docs_dir = Path("docs") - docs_dir.mkdir(exist_ok=True) - - # Generate markdown - 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("Written to {}".format(md_path)) - - # Summary - lines = markdown_content.split('\n') - class_count = markdown_content.count('### class') - func_count = markdown_content.count('### ') - class_count - markdown_content.count('### automation.') - - print("\nDocumentation Statistics:") - print("- Classes documented: {}".format(class_count)) - print("- Functions documented: {}".format(func_count)) - print("- Total lines: {}".format(len(lines))) - - print("\nAPI documentation generated successfully!") - sys.exit(0) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/tools/generate_complete_api_docs.py b/tools/generate_complete_api_docs.py deleted file mode 100644 index 8b41446..0000000 --- a/tools/generate_complete_api_docs.py +++ /dev/null @@ -1,960 +0,0 @@ -#!/usr/bin/env python3 -"""Generate COMPLETE HTML API reference documentation for McRogueFace with NO missing methods.""" - -import os -import sys -import datetime -import html -from pathlib import Path -import mcrfpy - -def escape_html(text: str) -> str: - """Escape HTML special characters.""" - return html.escape(text) if text else "" - -def get_complete_method_documentation(): - """Return complete documentation for ALL methods across all classes.""" - return { - # Base Drawable methods (inherited by all UI elements) - 'Drawable': { - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of this drawable element.', - 'returns': 'tuple: (x, y, width, height) representing the element\'s bounds', - 'note': 'The bounds are in screen coordinates and account for current position and size.' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the element by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ], - 'note': 'This modifies the x and y position properties by the given amounts.' - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Resize the element to new dimensions.', - 'args': [ - ('width', 'float', 'New width in pixels'), - ('height', 'float', 'New height in pixels') - ], - 'note': 'For Caption and Sprite, this may not change actual size if determined by content.' - } - }, - - # Entity-specific methods - 'Entity': { - 'at': { - 'signature': 'at(x, y)', - 'description': 'Check if this entity is at the specified grid coordinates.', - 'args': [ - ('x', 'int', 'Grid x coordinate to check'), - ('y', 'int', 'Grid y coordinate to check') - ], - 'returns': 'bool: True if entity is at position (x, y), False otherwise' - }, - 'die': { - 'signature': 'die()', - 'description': 'Remove this entity from its parent grid.', - 'note': 'The entity object remains valid but is no longer rendered or updated.' - }, - 'index': { - 'signature': 'index()', - 'description': 'Get the index of this entity in its parent grid\'s entity list.', - 'returns': 'int: Index position, or -1 if not in a grid' - } - }, - - # Grid-specific methods - 'Grid': { - 'at': { - 'signature': 'at(x, y)', - 'description': 'Get the GridPoint at the specified grid coordinates.', - 'args': [ - ('x', 'int', 'Grid x coordinate'), - ('y', 'int', 'Grid y coordinate') - ], - 'returns': 'GridPoint or None: The grid point at (x, y), or None if out of bounds' - } - }, - - # Collection methods - 'EntityCollection': { - 'append': { - 'signature': 'append(entity)', - 'description': 'Add an entity to the end of the collection.', - 'args': [('entity', 'Entity', 'The entity to add')] - }, - 'remove': { - 'signature': 'remove(entity)', - 'description': 'Remove the first occurrence of an entity from the collection.', - 'args': [('entity', 'Entity', 'The entity to remove')], - 'raises': 'ValueError: If entity is not in collection' - }, - 'extend': { - 'signature': 'extend(iterable)', - 'description': 'Add all entities from an iterable to the collection.', - 'args': [('iterable', 'Iterable[Entity]', 'Entities to add')] - }, - 'count': { - 'signature': 'count(entity)', - 'description': 'Count the number of occurrences of an entity in the collection.', - 'args': [('entity', 'Entity', 'The entity to count')], - 'returns': 'int: Number of times entity appears in collection' - }, - 'index': { - 'signature': 'index(entity)', - 'description': 'Find the index of the first occurrence of an entity.', - 'args': [('entity', 'Entity', 'The entity to find')], - 'returns': 'int: Index of entity in collection', - 'raises': 'ValueError: If entity is not in collection' - } - }, - - 'UICollection': { - 'append': { - 'signature': 'append(drawable)', - 'description': 'Add a drawable element to the end of the collection.', - 'args': [('drawable', 'UIDrawable', 'The drawable element to add')] - }, - 'remove': { - 'signature': 'remove(drawable)', - 'description': 'Remove the first occurrence of a drawable from the collection.', - 'args': [('drawable', 'UIDrawable', 'The drawable to remove')], - 'raises': 'ValueError: If drawable is not in collection' - }, - 'extend': { - 'signature': 'extend(iterable)', - 'description': 'Add all drawables from an iterable to the collection.', - 'args': [('iterable', 'Iterable[UIDrawable]', 'Drawables to add')] - }, - 'count': { - 'signature': 'count(drawable)', - 'description': 'Count the number of occurrences of a drawable in the collection.', - 'args': [('drawable', 'UIDrawable', 'The drawable to count')], - 'returns': 'int: Number of times drawable appears in collection' - }, - 'index': { - 'signature': 'index(drawable)', - 'description': 'Find the index of the first occurrence of a drawable.', - 'args': [('drawable', 'UIDrawable', 'The drawable to find')], - 'returns': 'int: Index of drawable in collection', - 'raises': 'ValueError: If drawable is not in collection' - } - }, - - # Animation methods - 'Animation': { - 'get_current_value': { - 'signature': 'get_current_value()', - 'description': 'Get the current interpolated value of the animation.', - 'returns': 'float: Current animation value between start and end' - }, - 'start': { - 'signature': 'start(target)', - 'description': 'Start the animation on a target UI element.', - 'args': [('target', 'UIDrawable', 'The UI element to animate')], - 'note': 'The target must have the property specified in the animation constructor.' - }, - 'update': { - 'signature': 'update(delta_time)', - 'description': 'Update the animation by the given time delta.', - 'args': [('delta_time', 'float', 'Time elapsed since last update in seconds')], - 'returns': 'bool: True if animation is still running, False if finished' - } - }, - - # Color methods - 'Color': { - 'from_hex': { - 'signature': 'from_hex(hex_string)', - 'description': 'Create a Color from a hexadecimal color string.', - 'args': [('hex_string', 'str', 'Hex color string (e.g., "#FF0000" or "FF0000")')], - 'returns': 'Color: New Color object from hex string', - 'example': 'red = Color.from_hex("#FF0000")' - }, - 'to_hex': { - 'signature': 'to_hex()', - 'description': 'Convert this Color to a hexadecimal string.', - 'returns': 'str: Hex color string in format "#RRGGBB"', - 'example': 'hex_str = color.to_hex() # Returns "#FF0000"' - }, - 'lerp': { - 'signature': 'lerp(other, t)', - 'description': 'Linearly interpolate between this color and another.', - 'args': [ - ('other', 'Color', 'The color to interpolate towards'), - ('t', 'float', 'Interpolation factor from 0.0 to 1.0') - ], - 'returns': 'Color: New interpolated Color object', - 'example': 'mixed = red.lerp(blue, 0.5) # 50% between red and blue' - } - }, - - # Vector methods - 'Vector': { - 'magnitude': { - 'signature': 'magnitude()', - 'description': 'Calculate the length/magnitude of this vector.', - 'returns': 'float: The magnitude of the vector', - 'example': 'length = vector.magnitude()' - }, - 'magnitude_squared': { - 'signature': 'magnitude_squared()', - 'description': 'Calculate the squared magnitude of this vector.', - 'returns': 'float: The squared magnitude (faster than magnitude())', - 'note': 'Use this for comparisons to avoid expensive square root calculation.' - }, - 'normalize': { - 'signature': 'normalize()', - 'description': 'Return a unit vector in the same direction.', - 'returns': 'Vector: New normalized vector with magnitude 1.0', - 'raises': 'ValueError: If vector has zero magnitude' - }, - 'dot': { - 'signature': 'dot(other)', - 'description': 'Calculate the dot product with another vector.', - 'args': [('other', 'Vector', 'The other vector')], - 'returns': 'float: Dot product of the two vectors' - }, - 'distance_to': { - 'signature': 'distance_to(other)', - 'description': 'Calculate the distance to another vector.', - 'args': [('other', 'Vector', 'The other vector')], - 'returns': 'float: Distance between the two vectors' - }, - 'angle': { - 'signature': 'angle()', - 'description': 'Get the angle of this vector in radians.', - 'returns': 'float: Angle in radians from positive x-axis' - }, - 'copy': { - 'signature': 'copy()', - 'description': 'Create a copy of this vector.', - 'returns': 'Vector: New Vector object with same x and y values' - } - }, - - # Scene methods - 'Scene': { - 'activate': { - 'signature': 'activate()', - 'description': 'Make this scene the active scene.', - 'note': 'Equivalent to calling setScene() with this scene\'s name.' - }, - 'get_ui': { - 'signature': 'get_ui()', - 'description': 'Get the UI element collection for this scene.', - 'returns': 'UICollection: Collection of all UI elements in this scene' - }, - 'keypress': { - 'signature': 'keypress(handler)', - 'description': 'Register a keyboard handler function for this scene.', - 'args': [('handler', 'callable', 'Function that takes (key_name: str, is_pressed: bool)')], - 'note': 'Alternative to overriding the on_keypress method.' - }, - 'register_keyboard': { - 'signature': 'register_keyboard(callable)', - 'description': 'Register a keyboard event handler function for the scene.', - 'args': [('callable', 'callable', 'Function that takes (key: str, action: str) parameters')], - 'note': 'Alternative to overriding the on_keypress method when subclassing Scene objects.', - 'example': '''def handle_keyboard(key, action): - print(f"Key '{key}' was {action}") - if key == "q" and action == "press": - # Handle quit - pass -scene.register_keyboard(handle_keyboard)''' - } - }, - - # Timer methods - 'Timer': { - 'pause': { - 'signature': 'pause()', - 'description': 'Pause the timer, stopping its callback execution.', - 'note': 'Use resume() to continue the timer from where it was paused.' - }, - 'resume': { - 'signature': 'resume()', - 'description': 'Resume a paused timer.', - 'note': 'Has no effect if timer is not paused.' - }, - 'cancel': { - 'signature': 'cancel()', - 'description': 'Cancel the timer and remove it from the system.', - 'note': 'After cancelling, the timer object cannot be reused.' - }, - 'restart': { - 'signature': 'restart()', - 'description': 'Restart the timer from the beginning.', - 'note': 'Resets the timer\'s internal clock to zero.' - } - }, - - # Window methods - 'Window': { - 'get': { - 'signature': 'get()', - 'description': 'Get the Window singleton instance.', - 'returns': 'Window: The singleton window object', - 'note': 'This is a static method that returns the same instance every time.' - }, - 'center': { - 'signature': 'center()', - 'description': 'Center the window on the screen.', - 'note': 'Only works if the window is not fullscreen.' - }, - 'screenshot': { - 'signature': 'screenshot(filename)', - 'description': 'Take a screenshot and save it to a file.', - 'args': [('filename', 'str', 'Path where to save the screenshot')], - 'note': 'Supports PNG, JPG, and BMP formats based on file extension.' - } - } - } - -def get_complete_function_documentation(): - """Return complete documentation for ALL module functions.""" - return { - # Scene Management - 'createScene': { - 'signature': 'createScene(name: str) -> None', - 'description': 'Create a new empty scene with the given name.', - 'args': [('name', 'str', 'Unique name for the new scene')], - 'raises': 'ValueError: If a scene with this name already exists', - 'note': 'The scene is created but not made active. Use setScene() to switch to it.', - 'example': 'mcrfpy.createScene("game_over")' - }, - 'setScene': { - 'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None', - 'description': 'Switch to a different scene with optional transition effect.', - 'args': [ - ('scene', 'str', 'Name of the scene to switch to'), - ('transition', 'str', 'Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down"'), - ('duration', 'float', 'Transition duration in seconds (default: 0.0 for instant)') - ], - 'raises': 'KeyError: If the scene doesn\'t exist', - 'example': 'mcrfpy.setScene("game", "fade", 0.5)' - }, - 'currentScene': { - 'signature': 'currentScene() -> str', - 'description': 'Get the name of the currently active scene.', - 'returns': 'str: Name of the current scene', - 'example': 'scene_name = mcrfpy.currentScene()' - }, - 'sceneUI': { - 'signature': 'sceneUI(scene: str = None) -> UICollection', - 'description': 'Get all UI elements for a scene.', - 'args': [('scene', 'str', 'Scene name. If None, uses current scene')], - 'returns': 'UICollection: All UI elements in the scene', - 'raises': 'KeyError: If the specified scene doesn\'t exist', - 'example': 'ui_elements = mcrfpy.sceneUI("game")' - }, - 'keypressScene': { - 'signature': 'keypressScene(handler: callable) -> None', - 'description': 'Set the keyboard event handler for the current scene.', - 'args': [('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)')], - 'example': '''def on_key(key, pressed): - if key == "SPACE" and pressed: - player.jump() -mcrfpy.keypressScene(on_key)''' - }, - - # Audio Functions - 'createSoundBuffer': { - 'signature': 'createSoundBuffer(filename: str) -> int', - 'description': 'Load a sound effect from a file and return its buffer ID.', - 'args': [('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)')], - 'returns': 'int: Buffer ID for use with playSound()', - 'raises': 'RuntimeError: If the file cannot be loaded', - 'example': 'jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav")' - }, - 'loadMusic': { - 'signature': 'loadMusic(filename: str, loop: bool = True) -> None', - 'description': 'Load and immediately play background music from a file.', - 'args': [ - ('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'), - ('loop', 'bool', 'Whether to loop the music (default: True)') - ], - 'note': 'Only one music track can play at a time. Loading new music stops the current track.', - 'example': 'mcrfpy.loadMusic("assets/background.ogg", True)' - }, - 'playSound': { - 'signature': 'playSound(buffer_id: int) -> None', - 'description': 'Play a sound effect using a previously loaded buffer.', - 'args': [('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()')], - 'raises': 'RuntimeError: If the buffer ID is invalid', - 'example': 'mcrfpy.playSound(jump_sound)' - }, - 'getMusicVolume': { - 'signature': 'getMusicVolume() -> int', - 'description': 'Get the current music volume level.', - 'returns': 'int: Current volume (0-100)', - 'example': 'current_volume = mcrfpy.getMusicVolume()' - }, - 'getSoundVolume': { - 'signature': 'getSoundVolume() -> int', - 'description': 'Get the current sound effects volume level.', - 'returns': 'int: Current volume (0-100)', - 'example': 'current_volume = mcrfpy.getSoundVolume()' - }, - 'setMusicVolume': { - 'signature': 'setMusicVolume(volume: int) -> None', - 'description': 'Set the global music volume.', - 'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')], - 'example': 'mcrfpy.setMusicVolume(50) # Set to 50% volume' - }, - 'setSoundVolume': { - 'signature': 'setSoundVolume(volume: int) -> None', - 'description': 'Set the global sound effects volume.', - 'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')], - 'example': 'mcrfpy.setSoundVolume(75) # Set to 75% volume' - }, - - # UI Utilities - 'find': { - 'signature': 'find(name: str, scene: str = None) -> UIDrawable | None', - 'description': 'Find the first UI element with the specified name.', - 'args': [ - ('name', 'str', 'Exact name to search for'), - ('scene', 'str', 'Scene to search in (default: current scene)') - ], - 'returns': 'UIDrawable or None: The found element, or None if not found', - 'note': 'Searches scene UI elements and entities within grids.', - 'example': 'button = mcrfpy.find("start_button")' - }, - 'findAll': { - 'signature': 'findAll(pattern: str, scene: str = None) -> list', - 'description': 'Find all UI elements matching a name pattern.', - 'args': [ - ('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'), - ('scene', 'str', 'Scene to search in (default: current scene)') - ], - 'returns': 'list: All matching UI elements and entities', - 'example': 'enemies = mcrfpy.findAll("enemy_*")' - }, - - # System Functions - 'exit': { - 'signature': 'exit() -> None', - 'description': 'Cleanly shut down the game engine and exit the application.', - 'note': 'This immediately closes the window and terminates the program.', - 'example': 'mcrfpy.exit()' - }, - 'getMetrics': { - 'signature': 'getMetrics() -> dict', - 'description': 'Get current performance metrics.', - 'returns': '''dict: Performance data with keys: -- frame_time: Last frame duration in seconds -- avg_frame_time: Average frame time -- fps: Frames per second -- draw_calls: Number of draw calls -- ui_elements: Total UI element count -- visible_elements: Visible element count -- current_frame: Frame counter -- runtime: Total runtime in seconds''', - 'example': 'metrics = mcrfpy.getMetrics()' - }, - 'setTimer': { - 'signature': 'setTimer(name: str, handler: callable, interval: int) -> None', - 'description': 'Create or update a recurring timer.', - 'args': [ - ('name', 'str', 'Unique identifier for the timer'), - ('handler', 'callable', 'Function called with (runtime: float) parameter'), - ('interval', 'int', 'Time between calls in milliseconds') - ], - 'note': 'If a timer with this name exists, it will be replaced.', - 'example': '''def update_score(runtime): - score += 1 -mcrfpy.setTimer("score_update", update_score, 1000)''' - }, - 'delTimer': { - 'signature': 'delTimer(name: str) -> None', - 'description': 'Stop and remove a timer.', - 'args': [('name', 'str', 'Timer identifier to remove')], - 'note': 'No error is raised if the timer doesn\'t exist.', - 'example': 'mcrfpy.delTimer("score_update")' - }, - 'setScale': { - 'signature': 'setScale(multiplier: float) -> None', - 'description': 'Scale the game window size.', - 'args': [('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)')], - 'note': 'The internal resolution remains 1024x768, but the window is scaled.', - 'example': 'mcrfpy.setScale(2.0) # Double the window size' - } - } - -def get_complete_property_documentation(): - """Return complete documentation for ALL properties.""" - return { - 'Animation': { - 'property': 'str: Name of the property being animated (e.g., "x", "y", "scale")', - 'duration': 'float: Total duration of the animation in seconds', - 'elapsed_time': 'float: Time elapsed since animation started (read-only)', - 'current_value': 'float: Current interpolated value of the animation (read-only)', - 'is_running': 'bool: True if animation is currently running (read-only)', - 'is_finished': 'bool: True if animation has completed (read-only)' - }, - 'GridPoint': { - 'x': 'int: Grid x coordinate of this point', - 'y': 'int: Grid y coordinate of this point', - 'texture_index': 'int: Index of the texture/sprite to display at this point', - 'solid': 'bool: Whether this point blocks movement', - 'transparent': 'bool: Whether this point allows light/vision through', - 'color': 'Color: Color tint applied to the texture at this point' - }, - 'GridPointState': { - 'visible': 'bool: Whether this point is currently visible to the player', - 'discovered': 'bool: Whether this point has been discovered/explored', - 'custom_flags': 'int: Bitfield for custom game-specific flags' - } - } - -def generate_complete_html_documentation(): - """Generate complete HTML documentation with NO missing methods.""" - - # Get all documentation data - method_docs = get_complete_method_documentation() - function_docs = get_complete_function_documentation() - property_docs = get_complete_property_documentation() - - html_parts = [] - - # HTML header with enhanced styling - html_parts.append(''' - - - - - McRogueFace API Reference - Complete Documentation - - - -
-''') - - # Title and overview - html_parts.append('

McRogueFace API Reference - Complete Documentation

') - html_parts.append(f'

Generated on {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}

') - - # Table of contents - html_parts.append('
') - html_parts.append('

Table of Contents

') - html_parts.append('') - html_parts.append('
') - - # Functions section - html_parts.append('

Functions

') - - # Group functions by category - categories = { - 'Scene Management': ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'], - 'Audio': ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'], - 'UI Utilities': ['find', 'findAll'], - 'System': ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale'] - } - - for category, functions in categories.items(): - html_parts.append(f'

{category}

') - for func_name in functions: - if func_name in function_docs: - html_parts.append(format_function_html(func_name, function_docs[func_name])) - - # Classes section - html_parts.append('

Classes

') - - # Get all classes from mcrfpy - classes = [] - for name in sorted(dir(mcrfpy)): - if not name.startswith('_'): - obj = getattr(mcrfpy, name) - if isinstance(obj, type): - classes.append((name, obj)) - - # Generate class documentation - for class_name, cls in classes: - html_parts.append(format_class_html_complete(class_name, cls, method_docs, property_docs)) - - # Automation section - if hasattr(mcrfpy, 'automation'): - html_parts.append('

Automation Module

') - html_parts.append('

The mcrfpy.automation module provides testing and automation capabilities.

') - - automation = mcrfpy.automation - for name in sorted(dir(automation)): - if not name.startswith('_'): - obj = getattr(automation, name) - if callable(obj): - html_parts.append(f'
') - html_parts.append(f'

automation.{name}

') - if obj.__doc__: - doc_parts = obj.__doc__.split(' - ') - if len(doc_parts) > 1: - html_parts.append(f'

{escape_html(doc_parts[1])}

') - else: - html_parts.append(f'

{escape_html(obj.__doc__)}

') - html_parts.append('
') - - html_parts.append('
') - html_parts.append('') - html_parts.append('') - - return '\n'.join(html_parts) - -def format_function_html(func_name, func_doc): - """Format a function with complete documentation.""" - html_parts = [] - - html_parts.append('
') - html_parts.append(f'

{func_doc["signature"]}

') - html_parts.append(f'

{escape_html(func_doc["description"])}

') - - # Arguments - if 'args' in func_doc: - html_parts.append('
') - html_parts.append('
Arguments:
') - for arg in func_doc['args']: - html_parts.append('
') - html_parts.append(f'{arg[0]} ') - html_parts.append(f'({arg[1]}): ') - html_parts.append(f'{escape_html(arg[2])}') - html_parts.append('
') - html_parts.append('
') - - # Returns - if 'returns' in func_doc: - html_parts.append('
') - html_parts.append(f'Returns: {escape_html(func_doc["returns"])}') - html_parts.append('
') - - # Raises - if 'raises' in func_doc: - html_parts.append('
') - html_parts.append(f'Raises: {escape_html(func_doc["raises"])}') - html_parts.append('
') - - # Note - if 'note' in func_doc: - html_parts.append('
') - html_parts.append(f'Note: {escape_html(func_doc["note"])}') - html_parts.append('
') - - # Example - if 'example' in func_doc: - html_parts.append('
') - html_parts.append('
Example:
') - html_parts.append('
')
-        html_parts.append(escape_html(func_doc['example']))
-        html_parts.append('
') - html_parts.append('
') - - html_parts.append('
') - - return '\n'.join(html_parts) - -def format_class_html_complete(class_name, cls, method_docs, property_docs): - """Format a class with complete documentation.""" - html_parts = [] - - html_parts.append('
') - html_parts.append(f'

{class_name}

') - - # Class description - if cls.__doc__: - html_parts.append(f'

{escape_html(cls.__doc__)}

') - - # Properties - if class_name in property_docs: - html_parts.append('

Properties:

') - for prop_name, prop_desc in property_docs[class_name].items(): - html_parts.append(f'
') - html_parts.append(f'{prop_name}: {escape_html(prop_desc)}') - html_parts.append('
') - - # Methods - methods_to_document = [] - - # Add inherited methods for UI classes - if any(base.__name__ == 'Drawable' for base in cls.__bases__ if hasattr(base, '__name__')): - methods_to_document.extend(['get_bounds', 'move', 'resize']) - - # Add class-specific methods - if class_name in method_docs: - methods_to_document.extend(method_docs[class_name].keys()) - - # Add methods from introspection - for attr_name in dir(cls): - if not attr_name.startswith('_') and callable(getattr(cls, attr_name)): - if attr_name not in methods_to_document: - methods_to_document.append(attr_name) - - if methods_to_document: - html_parts.append('

Methods:

') - for method_name in set(methods_to_document): - # Get method documentation - method_doc = None - if class_name in method_docs and method_name in method_docs[class_name]: - method_doc = method_docs[class_name][method_name] - elif method_name in method_docs.get('Drawable', {}): - method_doc = method_docs['Drawable'][method_name] - - if method_doc: - html_parts.append(format_method_html(method_name, method_doc)) - else: - # Basic method with no documentation - html_parts.append(f'
') - html_parts.append(f'{method_name}(...)') - html_parts.append('
') - - html_parts.append('
') - - return '\n'.join(html_parts) - -def format_method_html(method_name, method_doc): - """Format a method with complete documentation.""" - html_parts = [] - - html_parts.append('
') - html_parts.append(f'
{method_doc["signature"]}
') - html_parts.append(f'

{escape_html(method_doc["description"])}

') - - # Arguments - if 'args' in method_doc: - for arg in method_doc['args']: - html_parts.append(f'
') - html_parts.append(f'{arg[0]} ') - html_parts.append(f'({arg[1]}): ') - html_parts.append(f'{escape_html(arg[2])}') - html_parts.append('
') - - # Returns - if 'returns' in method_doc: - html_parts.append(f'
') - html_parts.append(f'Returns: {escape_html(method_doc["returns"])}') - html_parts.append('
') - - # Note - if 'note' in method_doc: - html_parts.append(f'
') - html_parts.append(f'Note: {escape_html(method_doc["note"])}') - html_parts.append('
') - - # Example - if 'example' in method_doc: - html_parts.append(f'
') - html_parts.append('Example:') - html_parts.append('
')
-        html_parts.append(escape_html(method_doc['example']))
-        html_parts.append('
') - html_parts.append('
') - - html_parts.append('
') - - return '\n'.join(html_parts) - -def main(): - """Generate complete HTML documentation with zero missing methods.""" - print("Generating COMPLETE HTML API documentation...") - - # Generate HTML - html_content = generate_complete_html_documentation() - - # Write to file - output_path = Path("docs/api_reference_complete.html") - output_path.parent.mkdir(exist_ok=True) - - with open(output_path, 'w', encoding='utf-8') as f: - f.write(html_content) - - print(f"✓ Generated {output_path}") - print(f" File size: {len(html_content):,} bytes") - - # Count "..." instances - ellipsis_count = html_content.count('...') - print(f" Ellipsis instances: {ellipsis_count}") - - if ellipsis_count == 0: - print("✅ SUCCESS: No missing documentation found!") - else: - print(f"❌ WARNING: {ellipsis_count} methods still need documentation") - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/tools/generate_complete_markdown_docs.py b/tools/generate_complete_markdown_docs.py deleted file mode 100644 index 89fab79..0000000 --- a/tools/generate_complete_markdown_docs.py +++ /dev/null @@ -1,821 +0,0 @@ -#!/usr/bin/env python3 -"""Generate COMPLETE Markdown API reference documentation for McRogueFace with NO missing methods.""" - -import os -import sys -import datetime -from pathlib import Path -import mcrfpy - -def get_complete_method_documentation(): - """Return complete documentation for ALL methods across all classes.""" - return { - # Base Drawable methods (inherited by all UI elements) - 'Drawable': { - 'get_bounds': { - 'signature': 'get_bounds()', - 'description': 'Get the bounding rectangle of this drawable element.', - 'returns': 'tuple: (x, y, width, height) representing the element\'s bounds', - 'note': 'The bounds are in screen coordinates and account for current position and size.' - }, - 'move': { - 'signature': 'move(dx, dy)', - 'description': 'Move the element by a relative offset.', - 'args': [ - ('dx', 'float', 'Horizontal offset in pixels'), - ('dy', 'float', 'Vertical offset in pixels') - ], - 'note': 'This modifies the x and y position properties by the given amounts.' - }, - 'resize': { - 'signature': 'resize(width, height)', - 'description': 'Resize the element to new dimensions.', - 'args': [ - ('width', 'float', 'New width in pixels'), - ('height', 'float', 'New height in pixels') - ], - 'note': 'For Caption and Sprite, this may not change actual size if determined by content.' - } - }, - - # Entity-specific methods - 'Entity': { - 'at': { - 'signature': 'at(x, y)', - 'description': 'Check if this entity is at the specified grid coordinates.', - 'args': [ - ('x', 'int', 'Grid x coordinate to check'), - ('y', 'int', 'Grid y coordinate to check') - ], - 'returns': 'bool: True if entity is at position (x, y), False otherwise' - }, - 'die': { - 'signature': 'die()', - 'description': 'Remove this entity from its parent grid.', - 'note': 'The entity object remains valid but is no longer rendered or updated.' - }, - 'index': { - 'signature': 'index()', - 'description': 'Get the index of this entity in its parent grid\'s entity list.', - 'returns': 'int: Index position, or -1 if not in a grid' - } - }, - - # Grid-specific methods - 'Grid': { - 'at': { - 'signature': 'at(x, y)', - 'description': 'Get the GridPoint at the specified grid coordinates.', - 'args': [ - ('x', 'int', 'Grid x coordinate'), - ('y', 'int', 'Grid y coordinate') - ], - 'returns': 'GridPoint or None: The grid point at (x, y), or None if out of bounds' - } - }, - - # Collection methods - 'EntityCollection': { - 'append': { - 'signature': 'append(entity)', - 'description': 'Add an entity to the end of the collection.', - 'args': [('entity', 'Entity', 'The entity to add')] - }, - 'remove': { - 'signature': 'remove(entity)', - 'description': 'Remove the first occurrence of an entity from the collection.', - 'args': [('entity', 'Entity', 'The entity to remove')], - 'raises': 'ValueError: If entity is not in collection' - }, - 'extend': { - 'signature': 'extend(iterable)', - 'description': 'Add all entities from an iterable to the collection.', - 'args': [('iterable', 'Iterable[Entity]', 'Entities to add')] - }, - 'count': { - 'signature': 'count(entity)', - 'description': 'Count the number of occurrences of an entity in the collection.', - 'args': [('entity', 'Entity', 'The entity to count')], - 'returns': 'int: Number of times entity appears in collection' - }, - 'index': { - 'signature': 'index(entity)', - 'description': 'Find the index of the first occurrence of an entity.', - 'args': [('entity', 'Entity', 'The entity to find')], - 'returns': 'int: Index of entity in collection', - 'raises': 'ValueError: If entity is not in collection' - } - }, - - 'UICollection': { - 'append': { - 'signature': 'append(drawable)', - 'description': 'Add a drawable element to the end of the collection.', - 'args': [('drawable', 'UIDrawable', 'The drawable element to add')] - }, - 'remove': { - 'signature': 'remove(drawable)', - 'description': 'Remove the first occurrence of a drawable from the collection.', - 'args': [('drawable', 'UIDrawable', 'The drawable to remove')], - 'raises': 'ValueError: If drawable is not in collection' - }, - 'extend': { - 'signature': 'extend(iterable)', - 'description': 'Add all drawables from an iterable to the collection.', - 'args': [('iterable', 'Iterable[UIDrawable]', 'Drawables to add')] - }, - 'count': { - 'signature': 'count(drawable)', - 'description': 'Count the number of occurrences of a drawable in the collection.', - 'args': [('drawable', 'UIDrawable', 'The drawable to count')], - 'returns': 'int: Number of times drawable appears in collection' - }, - 'index': { - 'signature': 'index(drawable)', - 'description': 'Find the index of the first occurrence of a drawable.', - 'args': [('drawable', 'UIDrawable', 'The drawable to find')], - 'returns': 'int: Index of drawable in collection', - 'raises': 'ValueError: If drawable is not in collection' - } - }, - - # Animation methods - 'Animation': { - 'get_current_value': { - 'signature': 'get_current_value()', - 'description': 'Get the current interpolated value of the animation.', - 'returns': 'float: Current animation value between start and end' - }, - 'start': { - 'signature': 'start(target)', - 'description': 'Start the animation on a target UI element.', - 'args': [('target', 'UIDrawable', 'The UI element to animate')], - 'note': 'The target must have the property specified in the animation constructor.' - }, - 'update': { - 'signature': 'update(delta_time)', - 'description': 'Update the animation by the given time delta.', - 'args': [('delta_time', 'float', 'Time elapsed since last update in seconds')], - 'returns': 'bool: True if animation is still running, False if finished' - } - }, - - # Color methods - 'Color': { - 'from_hex': { - 'signature': 'from_hex(hex_string)', - 'description': 'Create a Color from a hexadecimal color string.', - 'args': [('hex_string', 'str', 'Hex color string (e.g., "#FF0000" or "FF0000")')], - 'returns': 'Color: New Color object from hex string', - 'example': 'red = Color.from_hex("#FF0000")' - }, - 'to_hex': { - 'signature': 'to_hex()', - 'description': 'Convert this Color to a hexadecimal string.', - 'returns': 'str: Hex color string in format "#RRGGBB"', - 'example': 'hex_str = color.to_hex() # Returns "#FF0000"' - }, - 'lerp': { - 'signature': 'lerp(other, t)', - 'description': 'Linearly interpolate between this color and another.', - 'args': [ - ('other', 'Color', 'The color to interpolate towards'), - ('t', 'float', 'Interpolation factor from 0.0 to 1.0') - ], - 'returns': 'Color: New interpolated Color object', - 'example': 'mixed = red.lerp(blue, 0.5) # 50% between red and blue' - } - }, - - # Vector methods - 'Vector': { - 'magnitude': { - 'signature': 'magnitude()', - 'description': 'Calculate the length/magnitude of this vector.', - 'returns': 'float: The magnitude of the vector' - }, - 'magnitude_squared': { - 'signature': 'magnitude_squared()', - 'description': 'Calculate the squared magnitude of this vector.', - 'returns': 'float: The squared magnitude (faster than magnitude())', - 'note': 'Use this for comparisons to avoid expensive square root calculation.' - }, - 'normalize': { - 'signature': 'normalize()', - 'description': 'Return a unit vector in the same direction.', - 'returns': 'Vector: New normalized vector with magnitude 1.0', - 'raises': 'ValueError: If vector has zero magnitude' - }, - 'dot': { - 'signature': 'dot(other)', - 'description': 'Calculate the dot product with another vector.', - 'args': [('other', 'Vector', 'The other vector')], - 'returns': 'float: Dot product of the two vectors' - }, - 'distance_to': { - 'signature': 'distance_to(other)', - 'description': 'Calculate the distance to another vector.', - 'args': [('other', 'Vector', 'The other vector')], - 'returns': 'float: Distance between the two vectors' - }, - 'angle': { - 'signature': 'angle()', - 'description': 'Get the angle of this vector in radians.', - 'returns': 'float: Angle in radians from positive x-axis' - }, - 'copy': { - 'signature': 'copy()', - 'description': 'Create a copy of this vector.', - 'returns': 'Vector: New Vector object with same x and y values' - } - }, - - # Scene methods - 'Scene': { - 'activate': { - 'signature': 'activate()', - 'description': 'Make this scene the active scene.', - 'note': 'Equivalent to calling setScene() with this scene\'s name.' - }, - 'get_ui': { - 'signature': 'get_ui()', - 'description': 'Get the UI element collection for this scene.', - 'returns': 'UICollection: Collection of all UI elements in this scene' - }, - 'keypress': { - 'signature': 'keypress(handler)', - 'description': 'Register a keyboard handler function for this scene.', - 'args': [('handler', 'callable', 'Function that takes (key_name: str, is_pressed: bool)')], - 'note': 'Alternative to overriding the on_keypress method.' - }, - 'register_keyboard': { - 'signature': 'register_keyboard(callable)', - 'description': 'Register a keyboard event handler function for the scene.', - 'args': [('callable', 'callable', 'Function that takes (key: str, action: str) parameters')], - 'note': 'Alternative to overriding the on_keypress method when subclassing Scene objects.', - 'example': '''def handle_keyboard(key, action): - print(f"Key '{key}' was {action}") -scene.register_keyboard(handle_keyboard)''' - } - }, - - # Timer methods - 'Timer': { - 'pause': { - 'signature': 'pause()', - 'description': 'Pause the timer, stopping its callback execution.', - 'note': 'Use resume() to continue the timer from where it was paused.' - }, - 'resume': { - 'signature': 'resume()', - 'description': 'Resume a paused timer.', - 'note': 'Has no effect if timer is not paused.' - }, - 'cancel': { - 'signature': 'cancel()', - 'description': 'Cancel the timer and remove it from the system.', - 'note': 'After cancelling, the timer object cannot be reused.' - }, - 'restart': { - 'signature': 'restart()', - 'description': 'Restart the timer from the beginning.', - 'note': 'Resets the timer\'s internal clock to zero.' - } - }, - - # Window methods - 'Window': { - 'get': { - 'signature': 'get()', - 'description': 'Get the Window singleton instance.', - 'returns': 'Window: The singleton window object', - 'note': 'This is a static method that returns the same instance every time.' - }, - 'center': { - 'signature': 'center()', - 'description': 'Center the window on the screen.', - 'note': 'Only works if the window is not fullscreen.' - }, - 'screenshot': { - 'signature': 'screenshot(filename)', - 'description': 'Take a screenshot and save it to a file.', - 'args': [('filename', 'str', 'Path where to save the screenshot')], - 'note': 'Supports PNG, JPG, and BMP formats based on file extension.' - } - } - } - -def get_complete_function_documentation(): - """Return complete documentation for ALL module functions.""" - return { - # Scene Management - 'createScene': { - 'signature': 'createScene(name: str) -> None', - 'description': 'Create a new empty scene with the given name.', - 'args': [('name', 'str', 'Unique name for the new scene')], - 'raises': 'ValueError: If a scene with this name already exists', - 'note': 'The scene is created but not made active. Use setScene() to switch to it.', - 'example': 'mcrfpy.createScene("game_over")' - }, - 'setScene': { - 'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None', - 'description': 'Switch to a different scene with optional transition effect.', - 'args': [ - ('scene', 'str', 'Name of the scene to switch to'), - ('transition', 'str', 'Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down"'), - ('duration', 'float', 'Transition duration in seconds (default: 0.0 for instant)') - ], - 'raises': 'KeyError: If the scene doesn\'t exist', - 'example': 'mcrfpy.setScene("game", "fade", 0.5)' - }, - 'currentScene': { - 'signature': 'currentScene() -> str', - 'description': 'Get the name of the currently active scene.', - 'returns': 'str: Name of the current scene', - 'example': 'scene_name = mcrfpy.currentScene()' - }, - 'sceneUI': { - 'signature': 'sceneUI(scene: str = None) -> UICollection', - 'description': 'Get all UI elements for a scene.', - 'args': [('scene', 'str', 'Scene name. If None, uses current scene')], - 'returns': 'UICollection: All UI elements in the scene', - 'raises': 'KeyError: If the specified scene doesn\'t exist', - 'example': 'ui_elements = mcrfpy.sceneUI("game")' - }, - 'keypressScene': { - 'signature': 'keypressScene(handler: callable) -> None', - 'description': 'Set the keyboard event handler for the current scene.', - 'args': [('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)')], - 'example': '''def on_key(key, pressed): - if key == "SPACE" and pressed: - player.jump() -mcrfpy.keypressScene(on_key)''' - }, - - # Audio Functions - 'createSoundBuffer': { - 'signature': 'createSoundBuffer(filename: str) -> int', - 'description': 'Load a sound effect from a file and return its buffer ID.', - 'args': [('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)')], - 'returns': 'int: Buffer ID for use with playSound()', - 'raises': 'RuntimeError: If the file cannot be loaded', - 'example': 'jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav")' - }, - 'loadMusic': { - 'signature': 'loadMusic(filename: str, loop: bool = True) -> None', - 'description': 'Load and immediately play background music from a file.', - 'args': [ - ('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'), - ('loop', 'bool', 'Whether to loop the music (default: True)') - ], - 'note': 'Only one music track can play at a time. Loading new music stops the current track.', - 'example': 'mcrfpy.loadMusic("assets/background.ogg", True)' - }, - 'playSound': { - 'signature': 'playSound(buffer_id: int) -> None', - 'description': 'Play a sound effect using a previously loaded buffer.', - 'args': [('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()')], - 'raises': 'RuntimeError: If the buffer ID is invalid', - 'example': 'mcrfpy.playSound(jump_sound)' - }, - 'getMusicVolume': { - 'signature': 'getMusicVolume() -> int', - 'description': 'Get the current music volume level.', - 'returns': 'int: Current volume (0-100)', - 'example': 'current_volume = mcrfpy.getMusicVolume()' - }, - 'getSoundVolume': { - 'signature': 'getSoundVolume() -> int', - 'description': 'Get the current sound effects volume level.', - 'returns': 'int: Current volume (0-100)', - 'example': 'current_volume = mcrfpy.getSoundVolume()' - }, - 'setMusicVolume': { - 'signature': 'setMusicVolume(volume: int) -> None', - 'description': 'Set the global music volume.', - 'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')], - 'example': 'mcrfpy.setMusicVolume(50) # Set to 50% volume' - }, - 'setSoundVolume': { - 'signature': 'setSoundVolume(volume: int) -> None', - 'description': 'Set the global sound effects volume.', - 'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')], - 'example': 'mcrfpy.setSoundVolume(75) # Set to 75% volume' - }, - - # UI Utilities - 'find': { - 'signature': 'find(name: str, scene: str = None) -> UIDrawable | None', - 'description': 'Find the first UI element with the specified name.', - 'args': [ - ('name', 'str', 'Exact name to search for'), - ('scene', 'str', 'Scene to search in (default: current scene)') - ], - 'returns': 'UIDrawable or None: The found element, or None if not found', - 'note': 'Searches scene UI elements and entities within grids.', - 'example': 'button = mcrfpy.find("start_button")' - }, - 'findAll': { - 'signature': 'findAll(pattern: str, scene: str = None) -> list', - 'description': 'Find all UI elements matching a name pattern.', - 'args': [ - ('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'), - ('scene', 'str', 'Scene to search in (default: current scene)') - ], - 'returns': 'list: All matching UI elements and entities', - 'example': 'enemies = mcrfpy.findAll("enemy_*")' - }, - - # System Functions - 'exit': { - 'signature': 'exit() -> None', - 'description': 'Cleanly shut down the game engine and exit the application.', - 'note': 'This immediately closes the window and terminates the program.', - 'example': 'mcrfpy.exit()' - }, - 'getMetrics': { - 'signature': 'getMetrics() -> dict', - 'description': 'Get current performance metrics.', - 'returns': '''dict: Performance data with keys: -- frame_time: Last frame duration in seconds -- avg_frame_time: Average frame time -- fps: Frames per second -- draw_calls: Number of draw calls -- ui_elements: Total UI element count -- visible_elements: Visible element count -- current_frame: Frame counter -- runtime: Total runtime in seconds''', - 'example': 'metrics = mcrfpy.getMetrics()' - }, - 'setTimer': { - 'signature': 'setTimer(name: str, handler: callable, interval: int) -> None', - 'description': 'Create or update a recurring timer.', - 'args': [ - ('name', 'str', 'Unique identifier for the timer'), - ('handler', 'callable', 'Function called with (runtime: float) parameter'), - ('interval', 'int', 'Time between calls in milliseconds') - ], - 'note': 'If a timer with this name exists, it will be replaced.', - 'example': '''def update_score(runtime): - score += 1 -mcrfpy.setTimer("score_update", update_score, 1000)''' - }, - 'delTimer': { - 'signature': 'delTimer(name: str) -> None', - 'description': 'Stop and remove a timer.', - 'args': [('name', 'str', 'Timer identifier to remove')], - 'note': 'No error is raised if the timer doesn\'t exist.', - 'example': 'mcrfpy.delTimer("score_update")' - }, - 'setScale': { - 'signature': 'setScale(multiplier: float) -> None', - 'description': 'Scale the game window size.', - 'args': [('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)')], - 'note': 'The internal resolution remains 1024x768, but the window is scaled.', - 'example': 'mcrfpy.setScale(2.0) # Double the window size' - } - } - -def get_complete_property_documentation(): - """Return complete documentation for ALL properties.""" - return { - 'Animation': { - 'property': 'str: Name of the property being animated (e.g., "x", "y", "scale")', - 'duration': 'float: Total duration of the animation in seconds', - 'elapsed_time': 'float: Time elapsed since animation started (read-only)', - 'current_value': 'float: Current interpolated value of the animation (read-only)', - 'is_running': 'bool: True if animation is currently running (read-only)', - 'is_finished': 'bool: True if animation has completed (read-only)' - }, - 'GridPoint': { - 'x': 'int: Grid x coordinate of this point', - 'y': 'int: Grid y coordinate of this point', - 'texture_index': 'int: Index of the texture/sprite to display at this point', - 'solid': 'bool: Whether this point blocks movement', - 'transparent': 'bool: Whether this point allows light/vision through', - 'color': 'Color: Color tint applied to the texture at this point' - }, - 'GridPointState': { - 'visible': 'bool: Whether this point is currently visible to the player', - 'discovered': 'bool: Whether this point has been discovered/explored', - 'custom_flags': 'int: Bitfield for custom game-specific flags' - } - } - -def format_method_markdown(method_name, method_doc): - """Format a method as markdown.""" - lines = [] - - lines.append(f"#### `{method_doc['signature']}`") - lines.append("") - lines.append(method_doc['description']) - lines.append("") - - # Arguments - if 'args' in method_doc: - lines.append("**Arguments:**") - for arg in method_doc['args']: - lines.append(f"- `{arg[0]}` (*{arg[1]}*): {arg[2]}") - lines.append("") - - # Returns - if 'returns' in method_doc: - lines.append(f"**Returns:** {method_doc['returns']}") - lines.append("") - - # Raises - if 'raises' in method_doc: - lines.append(f"**Raises:** {method_doc['raises']}") - lines.append("") - - # Note - if 'note' in method_doc: - lines.append(f"**Note:** {method_doc['note']}") - lines.append("") - - # Example - if 'example' in method_doc: - lines.append("**Example:**") - lines.append("```python") - lines.append(method_doc['example']) - lines.append("```") - lines.append("") - - return lines - -def format_function_markdown(func_name, func_doc): - """Format a function as markdown.""" - lines = [] - - lines.append(f"### `{func_doc['signature']}`") - lines.append("") - lines.append(func_doc['description']) - lines.append("") - - # Arguments - if 'args' in func_doc: - lines.append("**Arguments:**") - for arg in func_doc['args']: - lines.append(f"- `{arg[0]}` (*{arg[1]}*): {arg[2]}") - lines.append("") - - # Returns - if 'returns' in func_doc: - lines.append(f"**Returns:** {func_doc['returns']}") - lines.append("") - - # Raises - if 'raises' in func_doc: - lines.append(f"**Raises:** {func_doc['raises']}") - lines.append("") - - # Note - if 'note' in func_doc: - lines.append(f"**Note:** {func_doc['note']}") - lines.append("") - - # Example - if 'example' in func_doc: - lines.append("**Example:**") - lines.append("```python") - lines.append(func_doc['example']) - lines.append("```") - lines.append("") - - lines.append("---") - lines.append("") - - return lines - -def generate_complete_markdown_documentation(): - """Generate complete markdown documentation with NO missing methods.""" - - # Get all documentation data - method_docs = get_complete_method_documentation() - function_docs = get_complete_function_documentation() - property_docs = get_complete_property_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("") - - # Overview - if mcrfpy.__doc__: - lines.append("## Overview") - lines.append("") - # Process the docstring properly - doc_text = mcrfpy.__doc__.replace('\\n', '\n') - lines.append(doc_text) - lines.append("") - - # Table of Contents - lines.append("## Table of Contents") - lines.append("") - lines.append("- [Functions](#functions)") - lines.append(" - [Scene Management](#scene-management)") - lines.append(" - [Audio](#audio)") - lines.append(" - [UI Utilities](#ui-utilities)") - lines.append(" - [System](#system)") - lines.append("- [Classes](#classes)") - lines.append(" - [UI Components](#ui-components)") - lines.append(" - [Collections](#collections)") - lines.append(" - [System Types](#system-types)") - lines.append(" - [Other Classes](#other-classes)") - lines.append("- [Automation Module](#automation-module)") - lines.append("") - - # Functions section - lines.append("## Functions") - lines.append("") - - # Group functions by category - categories = { - 'Scene Management': ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'], - 'Audio': ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'], - 'UI Utilities': ['find', 'findAll'], - 'System': ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale'] - } - - for category, functions in categories.items(): - lines.append(f"### {category}") - lines.append("") - for func_name in functions: - if func_name in function_docs: - lines.extend(format_function_markdown(func_name, function_docs[func_name])) - - # Classes section - lines.append("## Classes") - lines.append("") - - # Get all classes from mcrfpy - classes = [] - for name in sorted(dir(mcrfpy)): - if not name.startswith('_'): - obj = getattr(mcrfpy, name) - if isinstance(obj, type): - classes.append((name, obj)) - - # Group classes - ui_classes = ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity'] - collection_classes = ['EntityCollection', 'UICollection', 'UICollectionIter', 'UIEntityCollectionIter'] - system_classes = ['Color', 'Vector', 'Texture', 'Font'] - other_classes = [name for name, _ in classes if name not in ui_classes + collection_classes + system_classes] - - # UI Components - lines.append("### UI Components") - lines.append("") - for class_name in ui_classes: - if any(name == class_name for name, _ in classes): - lines.extend(format_class_markdown(class_name, method_docs, property_docs)) - - # Collections - lines.append("### Collections") - lines.append("") - for class_name in collection_classes: - if any(name == class_name for name, _ in classes): - lines.extend(format_class_markdown(class_name, method_docs, property_docs)) - - # System Types - lines.append("### System Types") - lines.append("") - for class_name in system_classes: - if any(name == class_name for name, _ in classes): - lines.extend(format_class_markdown(class_name, method_docs, property_docs)) - - # Other Classes - lines.append("### Other Classes") - lines.append("") - for class_name in other_classes: - lines.extend(format_class_markdown(class_name, method_docs, property_docs)) - - # Automation section - 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 - for name in sorted(dir(automation)): - if not name.startswith('_'): - obj = getattr(automation, name) - if callable(obj): - lines.append(f"### `automation.{name}`") - lines.append("") - if obj.__doc__: - doc_parts = obj.__doc__.split(' - ') - if len(doc_parts) > 1: - lines.append(doc_parts[1]) - else: - lines.append(obj.__doc__) - lines.append("") - lines.append("---") - lines.append("") - - return '\n'.join(lines) - -def format_class_markdown(class_name, method_docs, property_docs): - """Format a class as markdown.""" - lines = [] - - lines.append(f"### class `{class_name}`") - lines.append("") - - # Class description from known info - class_descriptions = { - 'Frame': 'A rectangular frame UI element that can contain other drawable elements.', - 'Caption': 'A text display UI element with customizable font and styling.', - 'Sprite': 'A sprite UI element that displays a texture or portion of a texture atlas.', - 'Grid': 'A grid-based tilemap UI element for rendering tile-based levels and game worlds.', - 'Entity': 'Game entity that can be placed in a Grid.', - 'EntityCollection': 'Container for Entity objects in a Grid. Supports iteration and indexing.', - 'UICollection': 'Container for UI drawable elements. Supports iteration and indexing.', - 'UICollectionIter': 'Iterator for UICollection. Automatically created when iterating over a UICollection.', - 'UIEntityCollectionIter': 'Iterator for EntityCollection. Automatically created when iterating over an EntityCollection.', - 'Color': 'RGBA color representation.', - 'Vector': '2D vector for positions and directions.', - 'Font': 'Font object for text rendering.', - 'Texture': 'Texture object for image data.', - 'Animation': 'Animate UI element properties over time.', - 'GridPoint': 'Represents a single tile in a Grid.', - 'GridPointState': 'State information for a GridPoint.', - 'Scene': 'Base class for object-oriented scenes.', - 'Timer': 'Timer object for scheduled callbacks.', - 'Window': 'Window singleton for accessing and modifying the game window properties.', - 'Drawable': 'Base class for all drawable UI elements.' - } - - if class_name in class_descriptions: - lines.append(class_descriptions[class_name]) - lines.append("") - - # Properties - if class_name in property_docs: - lines.append("#### Properties") - lines.append("") - for prop_name, prop_desc in property_docs[class_name].items(): - lines.append(f"- **`{prop_name}`**: {prop_desc}") - lines.append("") - - # Methods - methods_to_document = [] - - # Add inherited methods for UI classes - if class_name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']: - methods_to_document.extend(['get_bounds', 'move', 'resize']) - - # Add class-specific methods - if class_name in method_docs: - methods_to_document.extend(method_docs[class_name].keys()) - - if methods_to_document: - lines.append("#### Methods") - lines.append("") - for method_name in set(methods_to_document): - # Get method documentation - method_doc = None - if class_name in method_docs and method_name in method_docs[class_name]: - method_doc = method_docs[class_name][method_name] - elif method_name in method_docs.get('Drawable', {}): - method_doc = method_docs['Drawable'][method_name] - - if method_doc: - lines.extend(format_method_markdown(method_name, method_doc)) - - lines.append("---") - lines.append("") - - return lines - -def main(): - """Generate complete markdown documentation with zero missing methods.""" - print("Generating COMPLETE Markdown API documentation...") - - # Generate markdown - markdown_content = generate_complete_markdown_documentation() - - # Write to file - output_path = Path("docs/API_REFERENCE_COMPLETE.md") - output_path.parent.mkdir(exist_ok=True) - - with open(output_path, 'w', encoding='utf-8') as f: - f.write(markdown_content) - - print(f"✓ Generated {output_path}") - print(f" File size: {len(markdown_content):,} bytes") - - # Count "..." instances - ellipsis_count = markdown_content.count('...') - print(f" Ellipsis instances: {ellipsis_count}") - - if ellipsis_count == 0: - print("✅ SUCCESS: No missing documentation found!") - else: - print(f"❌ WARNING: {ellipsis_count} methods still need documentation") - -if __name__ == '__main__': - main() \ No newline at end of file