McRogueFace/generate_complete_api_docs.py

960 lines
38 KiB
Python

#!/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('''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>McRogueFace API Reference - Complete Documentation</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #2c3e50;
border-bottom: 3px solid #3498db;
padding-bottom: 15px;
margin-bottom: 30px;
}
h2 {
color: #34495e;
border-bottom: 2px solid #ecf0f1;
padding-bottom: 10px;
margin-top: 40px;
}
h3 {
color: #2c3e50;
margin-top: 30px;
}
h4 {
color: #34495e;
margin-top: 20px;
font-size: 1.1em;
}
h5 {
color: #555;
margin-top: 15px;
font-size: 1em;
}
code {
background: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace;
font-size: 0.9em;
}
pre {
background: #f8f8f8;
border: 1px solid #e1e4e8;
border-radius: 6px;
padding: 16px;
overflow-x: auto;
margin: 15px 0;
}
pre code {
background: none;
padding: 0;
font-size: 0.875em;
line-height: 1.45;
}
.class-name {
color: #8e44ad;
font-weight: bold;
}
.property {
color: #27ae60;
font-weight: 600;
}
.method {
color: #2980b9;
font-weight: 600;
}
.function-signature {
color: #d73a49;
font-weight: 600;
}
.method-section {
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
border-left: 4px solid #3498db;
}
.arg-list {
margin: 10px 0;
}
.arg-item {
margin: 8px 0;
padding: 8px;
background: #fff;
border-radius: 4px;
border: 1px solid #e1e4e8;
}
.arg-name {
color: #d73a49;
font-weight: 600;
}
.arg-type {
color: #6f42c1;
font-style: italic;
}
.returns {
background: #e8f5e8;
padding: 10px;
border-radius: 4px;
border-left: 4px solid #28a745;
margin: 10px 0;
}
.note {
background: #fff3cd;
padding: 10px;
border-radius: 4px;
border-left: 4px solid #ffc107;
margin: 10px 0;
}
.example {
background: #e7f3ff;
padding: 15px;
border-radius: 4px;
border-left: 4px solid #0366d6;
margin: 15px 0;
}
.toc {
background: #f8f9fa;
border: 1px solid #e1e4e8;
border-radius: 6px;
padding: 20px;
margin: 20px 0;
}
.toc ul {
list-style: none;
padding-left: 0;
}
.toc li {
margin: 8px 0;
}
.toc a {
color: #3498db;
text-decoration: none;
font-weight: 500;
}
.toc a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
''')
# Title and overview
html_parts.append('<h1>McRogueFace API Reference - Complete Documentation</h1>')
html_parts.append(f'<p><em>Generated on {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</em></p>')
# Table of contents
html_parts.append('<div class="toc">')
html_parts.append('<h2>Table of Contents</h2>')
html_parts.append('<ul>')
html_parts.append('<li><a href="#functions">Functions</a></li>')
html_parts.append('<li><a href="#classes">Classes</a></li>')
html_parts.append('<li><a href="#automation">Automation Module</a></li>')
html_parts.append('</ul>')
html_parts.append('</div>')
# Functions section
html_parts.append('<h2 id="functions">Functions</h2>')
# 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'<h3>{category}</h3>')
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('<h2 id="classes">Classes</h2>')
# 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('<h2 id="automation">Automation Module</h2>')
html_parts.append('<p>The <code>mcrfpy.automation</code> module provides testing and automation capabilities.</p>')
automation = mcrfpy.automation
for name in sorted(dir(automation)):
if not name.startswith('_'):
obj = getattr(automation, name)
if callable(obj):
html_parts.append(f'<div class="method-section">')
html_parts.append(f'<h4><code class="function-signature">automation.{name}</code></h4>')
if obj.__doc__:
doc_parts = obj.__doc__.split(' - ')
if len(doc_parts) > 1:
html_parts.append(f'<p>{escape_html(doc_parts[1])}</p>')
else:
html_parts.append(f'<p>{escape_html(obj.__doc__)}</p>')
html_parts.append('</div>')
html_parts.append('</div>')
html_parts.append('</body>')
html_parts.append('</html>')
return '\n'.join(html_parts)
def format_function_html(func_name, func_doc):
"""Format a function with complete documentation."""
html_parts = []
html_parts.append('<div class="method-section">')
html_parts.append(f'<h4><code class="function-signature">{func_doc["signature"]}</code></h4>')
html_parts.append(f'<p>{escape_html(func_doc["description"])}</p>')
# Arguments
if 'args' in func_doc:
html_parts.append('<div class="arg-list">')
html_parts.append('<h5>Arguments:</h5>')
for arg in func_doc['args']:
html_parts.append('<div class="arg-item">')
html_parts.append(f'<span class="arg-name">{arg[0]}</span> ')
html_parts.append(f'<span class="arg-type">({arg[1]})</span>: ')
html_parts.append(f'{escape_html(arg[2])}')
html_parts.append('</div>')
html_parts.append('</div>')
# Returns
if 'returns' in func_doc:
html_parts.append('<div class="returns">')
html_parts.append(f'<strong>Returns:</strong> {escape_html(func_doc["returns"])}')
html_parts.append('</div>')
# Raises
if 'raises' in func_doc:
html_parts.append('<div class="note">')
html_parts.append(f'<strong>Raises:</strong> {escape_html(func_doc["raises"])}')
html_parts.append('</div>')
# Note
if 'note' in func_doc:
html_parts.append('<div class="note">')
html_parts.append(f'<strong>Note:</strong> {escape_html(func_doc["note"])}')
html_parts.append('</div>')
# Example
if 'example' in func_doc:
html_parts.append('<div class="example">')
html_parts.append('<h5>Example:</h5>')
html_parts.append('<pre><code>')
html_parts.append(escape_html(func_doc['example']))
html_parts.append('</code></pre>')
html_parts.append('</div>')
html_parts.append('</div>')
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('<div class="method-section">')
html_parts.append(f'<h3><span class="class-name">{class_name}</span></h3>')
# Class description
if cls.__doc__:
html_parts.append(f'<p>{escape_html(cls.__doc__)}</p>')
# Properties
if class_name in property_docs:
html_parts.append('<h4>Properties:</h4>')
for prop_name, prop_desc in property_docs[class_name].items():
html_parts.append(f'<div class="arg-item">')
html_parts.append(f'<span class="property">{prop_name}</span>: {escape_html(prop_desc)}')
html_parts.append('</div>')
# 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('<h4>Methods:</h4>')
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'<div class="arg-item">')
html_parts.append(f'<span class="method">{method_name}(...)</span>')
html_parts.append('</div>')
html_parts.append('</div>')
return '\n'.join(html_parts)
def format_method_html(method_name, method_doc):
"""Format a method with complete documentation."""
html_parts = []
html_parts.append('<div style="margin-left: 20px; margin-bottom: 15px;">')
html_parts.append(f'<h5><code class="method">{method_doc["signature"]}</code></h5>')
html_parts.append(f'<p>{escape_html(method_doc["description"])}</p>')
# Arguments
if 'args' in method_doc:
for arg in method_doc['args']:
html_parts.append(f'<div style="margin-left: 20px;">')
html_parts.append(f'<span class="arg-name">{arg[0]}</span> ')
html_parts.append(f'<span class="arg-type">({arg[1]})</span>: ')
html_parts.append(f'{escape_html(arg[2])}')
html_parts.append('</div>')
# Returns
if 'returns' in method_doc:
html_parts.append(f'<div style="margin-left: 20px; color: #28a745;">')
html_parts.append(f'<strong>Returns:</strong> {escape_html(method_doc["returns"])}')
html_parts.append('</div>')
# Note
if 'note' in method_doc:
html_parts.append(f'<div style="margin-left: 20px; color: #856404;">')
html_parts.append(f'<strong>Note:</strong> {escape_html(method_doc["note"])}')
html_parts.append('</div>')
# Example
if 'example' in method_doc:
html_parts.append(f'<div style="margin-left: 20px;">')
html_parts.append('<strong>Example:</strong>')
html_parts.append('<pre><code>')
html_parts.append(escape_html(method_doc['example']))
html_parts.append('</code></pre>')
html_parts.append('</div>')
html_parts.append('</div>')
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()