#!/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('
')
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 = {
'Entity': {
'at': {
'signature': 'at(x, y)',
'description': 'Check if entity is at given grid coordinates.',
'args': [('x', 'int'), ('y', 'int')],
'returns': 'bool: True if entity is at (x, y)'
},
'die': {
'signature': 'die()',
'description': 'Remove this entity from its parent grid.',
'note': 'The entity object remains valid but is no longer rendered.'
}
},
'Grid': {
'at': {
'signature': 'at(x, y)',
'description': 'Get the GridPoint at the specified coordinates.',
'args': [('x', 'int'), ('y', 'int')],
'returns': 'GridPoint: The tile at (x, y), or None if out of bounds'
}
},
'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')]
}
}
}
return method_docs.get(class_name, {}).get(method_name, {})
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'')
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('
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(escape_html(init_info['signature']))
html_parts.append('
')
html_parts.append('{format_docstring_as_html(description)}
') html_parts.append('{arg_name}
({arg_type}){prop_name}
{prop_name}
{readonly}{method_info["signature"]}
{escape_html(method_info["description"])}
') if method_info.get('args'): html_parts.append('Arguments:
') html_parts.append('{arg[0]}
({arg[1]}): {arg[2]}{arg[0]}
({arg[1]})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}(...)
{escape_html(method_doc)}
') html_parts.append('')
html_parts.append(escape_html(init_info['example']))
html_parts.append('
')
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('The mcrfpy.automation
module provides testing and automation capabilities for simulating user input and capturing screenshots.
automation.{name}
{escape_html(description)}
') html_parts.append('{escape_html(signature)}
{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())}
{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('