Squashed commit of the following: [alpha_presentable]
Author: John McCardle <mccardle.john@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
commit dc47f2474c7b2642d368f9772894aed857527807
the UIEntity rant
commit 673ca8e1b089ea670257fc04ae1a676ed95a40ed
I forget when these tests were written, but I want them in the squash merge
commit 70c71565c684fa96e222179271ecb13a156d80ad
Fix UI object segfault by switching from managed to manual weakref management
The UI types (Frame, Caption, Sprite, Grid, Entity) were using
Py_TPFLAGS_MANAGED_WEAKREF while also trying to manually create weakrefs
for the PythonObjectCache. This is fundamentally incompatible - when
Python manages weakrefs internally, PyWeakref_NewRef() cannot access the
weakref list properly, causing segfaults.
Changed all UI types to use manual weakref management (like PyTimer):
- Restored weakreflist field in all UI type structures
- Removed Py_TPFLAGS_MANAGED_WEAKREF from all UI type flags
- Added tp_weaklistoffset for all UI types in module initialization
- Initialize weakreflist=NULL in tp_new and init methods
- Call PyObject_ClearWeakRefs() in dealloc functions
This allows the PythonObjectCache to continue working correctly,
maintaining Python object identity for C++ objects across the boundary.
Fixes segfault when creating UI objects (e.g., Caption, Grid) that was
preventing tutorial scripts from running.
This is the bulk of the required behavior for Issue #126.
that issure isn't ready for closure yet; several other sub-issues left.
closes #110
mention issue #109 - resolves some __init__ related nuisances
commit 3dce3ec539ae99e32d869007bf3f49d03e4e2f89
Refactor timer system for cleaner architecture and enhanced functionality
Major improvements to the timer system:
- Unified all timer logic in the Timer class (C++)
- Removed PyTimerCallable subclass, now using PyCallable directly
- Timer objects are now passed to callbacks as first argument
- Added 'once' parameter for one-shot timers that auto-stop
- Implemented proper PythonObjectCache integration with weakref support
API enhancements:
- New callback signature: callback(timer, runtime) instead of just (runtime)
- Timer objects expose: name, interval, remaining, paused, active, once properties
- Methods: pause(), resume(), cancel(), restart()
- Comprehensive documentation with examples
- Enhanced repr showing timer state (active/paused/once/remaining time)
This cleanup follows the UIEntity/PyUIEntity pattern and makes the timer
system more Pythonic while maintaining backward compatibility through
the legacy setTimer/delTimer API.
closes #121
commit 145834cfc31b8dabc4cb3591b9cb4ed99fc8b964
Implement Python object cache to preserve derived types in collections
Add a global cache system that maintains weak references to Python objects,
ensuring that derived Python classes maintain their identity when stored in
and retrieved from C++ collections.
Key changes:
- Add PythonObjectCache singleton with serial number system
- Each cacheable object (UIDrawable, UIEntity, Timer, Animation) gets unique ID
- Cache stores weak references to prevent circular reference memory leaks
- Update all UI type definitions to support weak references (Py_TPFLAGS_MANAGED_WEAKREF)
- Enable subclassing for all UI types (Py_TPFLAGS_BASETYPE)
- Collections check cache before creating new Python wrappers
- Register objects in cache during __init__ methods
- Clean up cache entries in C++ destructors
This ensures that Python code like:
```python
class MyFrame(mcrfpy.Frame):
def __init__(self):
super().__init__()
self.custom_data = "preserved"
frame = MyFrame()
scene.ui.append(frame)
retrieved = scene.ui[0] # Same MyFrame instance with custom_data intact
```
Works correctly, with retrieved maintaining the derived type and custom attributes.
Closes #112
commit c5e7e8e298
Update test demos for new Python API and entity system
- Update all text input demos to use new Entity constructor signature
- Fix pathfinding showcase to work with new entity position handling
- Remove entity_waypoints tracking in favor of simplified movement
- Delete obsolete exhaustive_api_demo.py (superseded by newer demos)
- Adjust entity creation calls to match Entity((x, y), texture, sprite_index) pattern
commit 6d29652ae7
Update animation demo suite with crash fixes and improvements
- Add warnings about AnimationManager segfault bug in sizzle_reel_final.py
- Create sizzle_reel_final_fixed.py that works around the crash by hiding objects instead of removing them
- Increase font sizes for better visibility in demos
- Extend demo durations for better showcase of animations
- Remove debug prints from animation_sizzle_reel_working.py
- Minor cleanup and improvements to all animation demos
commit a010e5fa96
Update game scripts for new Python API
- Convert entity position access from tuple to x/y properties
- Update caption size property to font_size
- Fix grid boundary checks to use grid_size instead of exceptions
- Clean up demo timer on menu exit to prevent callbacks
These changes adapt the game scripts to work with the new standardized
Python API constructors and property names.
commit 9c8d6c4591
Fix click event z-order handling in PyScene
Changed click detection to properly respect z-index by:
- Sorting ui_elements in-place when needed (same as render order)
- Using reverse iterators to check highest z-index elements first
- This ensures top-most elements receive clicks before lower ones
commit dcd1b0ca33
Add roguelike tutorial implementation files
Implement Parts 0-2 of the classic roguelike tutorial adapted for McRogueFace:
- Part 0: Basic grid setup and tile rendering
- Part 1: Drawing '@' symbol and basic movement
- Part 1b: Variant with sprite-based player
- Part 2: Entity system and NPC implementation with three movement variants:
- part_2.py: Standard implementation
- part_2-naive.py: Naive movement approach
- part_2-onemovequeued.py: Queued movement system
Includes tutorial assets:
- tutorial2.png: Tileset for dungeon tiles
- tutorial_hero.png: Player sprite sheet
commit 6813fb5129
Standardize Python API constructors and remove PyArgHelpers
- Remove PyArgHelpers.h and all macro-based argument parsing
- Convert all UI class constructors to use PyArg_ParseTupleAndKeywords
- Standardize constructor signatures across UICaption, UIEntity, UIFrame, UIGrid, and UISprite
- Replace PYARGHELPER_SINGLE/MULTI macros with explicit argument parsing
- Improve error messages and argument validation
- Maintain backward compatibility with existing Python code
This change improves code maintainability and consistency across the Python API.
commit 6f67fbb51e
Fix animation callback crashes from iterator invalidation (#119)
Resolved segfaults caused by creating new animations from within
animation callbacks. The issue was iterator invalidation in
AnimationManager::update() when callbacks modified the active
animations vector.
Changes:
- Add deferred animation queue to AnimationManager
- New animations created during update are queued and added after
- Set isUpdating flag to track when in update loop
- Properly handle Animation destructor during callback execution
- Add clearCallback() method for safe cleanup scenarios
This fixes the "free(): invalid pointer" and "malloc(): unaligned
fastbin chunk detected" errors that occurred with rapid animation
creation in callbacks.
commit eb88c7b3aa
Add animation completion callbacks (#119)
Implement callbacks that fire when animations complete, enabling direct
causality between animation end and game state changes. This eliminates
race conditions from parallel timer workarounds.
- Add optional callback parameter to Animation constructor
- Callbacks execute synchronously when animation completes
- Proper Python reference counting with GIL safety
- Callbacks receive (anim, target) parameters (currently None)
- Exception handling prevents crashes from Python errors
Example usage:
```python
def on_complete(anim, target):
player_moving = False
anim = mcrfpy.Animation("x", 300.0, 1.0, "easeOut", callback=on_complete)
anim.start(player)
```
closes #119
commit 9fb428dd01
Update ROADMAP with GitHub issue numbers (#111-#125)
Added issue numbers from GitHub tracker to roadmap items:
- #111: Grid Click Events Broken in Headless
- #112: Object Splitting Bug (Python type preservation)
- #113: Batch Operations for Grid
- #114: CellView API
- #115: SpatialHash Implementation
- #116: Dirty Flag System
- #117: Memory Pool for Entities
- #118: Scene as Drawable
- #119: Animation Completion Callbacks
- #120: Animation Property Locking
- #121: Timer Object System
- #122: Parent-Child UI System
- #123: Grid Subgrid System
- #124: Grid Point Animation
- #125: GitHub Issues Automation
Also updated existing references:
- #101/#110: Constructor standardization
- #109: Vector class indexing
Note: Tutorial-specific items and Python-implementable features
(input queue, collision reservation) are not tracked as engine issues.
commit 062e4dadc4
Fix animation segfaults with RAII weak_ptr implementation
Resolved two critical segmentation faults in AnimationManager:
1. Race condition when creating multiple animations in timer callbacks
2. Exit crash when animations outlive their target objects
Changes:
- Replace raw pointers with std::weak_ptr for automatic target invalidation
- Add Animation::complete() to jump animations to final value
- Add Animation::hasValidTarget() to check if target still exists
- Update AnimationManager to auto-remove invalid animations
- Add AnimationManager::clear() call to GameEngine::cleanup()
- Update Python bindings to pass shared_ptr instead of raw pointers
This ensures animations can never reference destroyed objects, following
proper RAII principles. Tested with sizzle_reel_final.py and stress
tests creating/destroying hundreds of animated objects.
commit 98fc49a978
Directory structure cleanup and organization overhaul
This commit is contained in:
parent
1a143982e1
commit
f4343e1e82
163 changed files with 12812 additions and 5441 deletions
482
tools/generate_api_docs.py
Normal file
482
tools/generate_api_docs.py
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate API reference documentation for McRogueFace.
|
||||
|
||||
This script generates comprehensive API documentation in multiple formats:
|
||||
- Markdown for GitHub/documentation sites
|
||||
- HTML for local browsing
|
||||
- RST for Sphinx integration (future)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
from pathlib import Path
|
||||
|
||||
# We need to run this with McRogueFace as the interpreter
|
||||
# so mcrfpy is available
|
||||
import mcrfpy
|
||||
|
||||
def escape_markdown(text: str) -> str:
|
||||
"""Escape special markdown characters."""
|
||||
if not text:
|
||||
return ""
|
||||
# Escape backticks in inline code
|
||||
return text.replace("`", "\\`")
|
||||
|
||||
def format_signature(name: str, doc: str) -> str:
|
||||
"""Extract and format function signature from docstring."""
|
||||
if not doc:
|
||||
return f"{name}(...)"
|
||||
|
||||
lines = doc.strip().split('\n')
|
||||
if lines and '(' in lines[0]:
|
||||
# First line contains signature
|
||||
return lines[0].split('->')[0].strip()
|
||||
|
||||
return f"{name}(...)"
|
||||
|
||||
def get_class_info(cls: type) -> Dict[str, Any]:
|
||||
"""Extract comprehensive information about a class."""
|
||||
info = {
|
||||
'name': cls.__name__,
|
||||
'doc': cls.__doc__ or "",
|
||||
'methods': [],
|
||||
'properties': [],
|
||||
'bases': [base.__name__ for base in cls.__bases__ if base.__name__ != 'object'],
|
||||
}
|
||||
|
||||
# Get all attributes
|
||||
for attr_name in sorted(dir(cls)):
|
||||
if attr_name.startswith('_') and not attr_name.startswith('__'):
|
||||
continue
|
||||
|
||||
try:
|
||||
attr = getattr(cls, attr_name)
|
||||
|
||||
if isinstance(attr, property):
|
||||
prop_info = {
|
||||
'name': attr_name,
|
||||
'doc': (attr.fget.__doc__ if attr.fget else "") or "",
|
||||
'readonly': attr.fset is None
|
||||
}
|
||||
info['properties'].append(prop_info)
|
||||
elif callable(attr) and not attr_name.startswith('__'):
|
||||
method_info = {
|
||||
'name': attr_name,
|
||||
'doc': attr.__doc__ or "",
|
||||
'signature': format_signature(attr_name, attr.__doc__)
|
||||
}
|
||||
info['methods'].append(method_info)
|
||||
except:
|
||||
pass
|
||||
|
||||
return info
|
||||
|
||||
def get_function_info(func: Any, name: str) -> Dict[str, Any]:
|
||||
"""Extract information about a function."""
|
||||
return {
|
||||
'name': name,
|
||||
'doc': func.__doc__ or "",
|
||||
'signature': format_signature(name, func.__doc__)
|
||||
}
|
||||
|
||||
def generate_markdown_class(cls_info: Dict[str, Any]) -> List[str]:
|
||||
"""Generate markdown documentation for a class."""
|
||||
lines = []
|
||||
|
||||
# Class header
|
||||
lines.append(f"### class `{cls_info['name']}`")
|
||||
if cls_info['bases']:
|
||||
lines.append(f"*Inherits from: {', '.join(cls_info['bases'])}*")
|
||||
lines.append("")
|
||||
|
||||
# Class description
|
||||
if cls_info['doc']:
|
||||
doc_lines = cls_info['doc'].strip().split('\n')
|
||||
# First line is usually the constructor signature
|
||||
if doc_lines and '(' in doc_lines[0]:
|
||||
lines.append(f"```python")
|
||||
lines.append(doc_lines[0])
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
# Rest is description
|
||||
if len(doc_lines) > 2:
|
||||
lines.extend(doc_lines[2:])
|
||||
lines.append("")
|
||||
else:
|
||||
lines.extend(doc_lines)
|
||||
lines.append("")
|
||||
|
||||
# Properties
|
||||
if cls_info['properties']:
|
||||
lines.append("#### Properties")
|
||||
lines.append("")
|
||||
for prop in cls_info['properties']:
|
||||
readonly = " *(readonly)*" if prop['readonly'] else ""
|
||||
lines.append(f"- **`{prop['name']}`**{readonly}")
|
||||
if prop['doc']:
|
||||
lines.append(f" - {prop['doc'].strip()}")
|
||||
lines.append("")
|
||||
|
||||
# Methods
|
||||
if cls_info['methods']:
|
||||
lines.append("#### Methods")
|
||||
lines.append("")
|
||||
for method in cls_info['methods']:
|
||||
lines.append(f"##### `{method['signature']}`")
|
||||
if method['doc']:
|
||||
# Parse docstring for better formatting
|
||||
doc_lines = method['doc'].strip().split('\n')
|
||||
# Skip the signature line if it's repeated
|
||||
start = 1 if doc_lines and method['name'] in doc_lines[0] else 0
|
||||
for line in doc_lines[start:]:
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
def generate_markdown_function(func_info: Dict[str, Any]) -> List[str]:
|
||||
"""Generate markdown documentation for a function."""
|
||||
lines = []
|
||||
|
||||
lines.append(f"### `{func_info['signature']}`")
|
||||
lines.append("")
|
||||
|
||||
if func_info['doc']:
|
||||
doc_lines = func_info['doc'].strip().split('\n')
|
||||
# Skip signature line if present
|
||||
start = 1 if doc_lines and func_info['name'] in doc_lines[0] else 0
|
||||
|
||||
# Process documentation sections
|
||||
in_section = None
|
||||
for line in doc_lines[start:]:
|
||||
if line.strip() in ['Args:', 'Returns:', 'Raises:', 'Note:', 'Example:']:
|
||||
in_section = line.strip()
|
||||
lines.append(f"**{in_section}**")
|
||||
elif in_section and line.strip():
|
||||
# Indent content under sections
|
||||
lines.append(f"{line}")
|
||||
else:
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
def generate_markdown_docs() -> str:
|
||||
"""Generate complete markdown API documentation."""
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append("# McRogueFace API Reference")
|
||||
lines.append("")
|
||||
lines.append(f"*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
|
||||
lines.append("")
|
||||
|
||||
# Module description
|
||||
if mcrfpy.__doc__:
|
||||
lines.append("## Overview")
|
||||
lines.append("")
|
||||
lines.extend(mcrfpy.__doc__.strip().split('\n'))
|
||||
lines.append("")
|
||||
|
||||
# Table of contents
|
||||
lines.append("## Table of Contents")
|
||||
lines.append("")
|
||||
lines.append("- [Classes](#classes)")
|
||||
lines.append("- [Functions](#functions)")
|
||||
lines.append("- [Automation Module](#automation-module)")
|
||||
lines.append("")
|
||||
|
||||
# Collect all components
|
||||
classes = []
|
||||
functions = []
|
||||
constants = []
|
||||
|
||||
for name in sorted(dir(mcrfpy)):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(mcrfpy, name)
|
||||
|
||||
if isinstance(obj, type):
|
||||
classes.append((name, obj))
|
||||
elif callable(obj):
|
||||
functions.append((name, obj))
|
||||
elif not inspect.ismodule(obj):
|
||||
constants.append((name, obj))
|
||||
|
||||
# Document classes
|
||||
lines.append("## Classes")
|
||||
lines.append("")
|
||||
|
||||
# Group classes by category
|
||||
ui_classes = []
|
||||
collection_classes = []
|
||||
system_classes = []
|
||||
other_classes = []
|
||||
|
||||
for name, cls in classes:
|
||||
if name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']:
|
||||
ui_classes.append((name, cls))
|
||||
elif 'Collection' in name:
|
||||
collection_classes.append((name, cls))
|
||||
elif name in ['Color', 'Vector', 'Texture', 'Font']:
|
||||
system_classes.append((name, cls))
|
||||
else:
|
||||
other_classes.append((name, cls))
|
||||
|
||||
# UI Classes
|
||||
if ui_classes:
|
||||
lines.append("### UI Components")
|
||||
lines.append("")
|
||||
for name, cls in ui_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# Collections
|
||||
if collection_classes:
|
||||
lines.append("### Collections")
|
||||
lines.append("")
|
||||
for name, cls in collection_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# System Classes
|
||||
if system_classes:
|
||||
lines.append("### System Types")
|
||||
lines.append("")
|
||||
for name, cls in system_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# Other Classes
|
||||
if other_classes:
|
||||
lines.append("### Other Classes")
|
||||
lines.append("")
|
||||
for name, cls in other_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# Document functions
|
||||
lines.append("## Functions")
|
||||
lines.append("")
|
||||
|
||||
# Group functions by category
|
||||
scene_funcs = []
|
||||
audio_funcs = []
|
||||
ui_funcs = []
|
||||
system_funcs = []
|
||||
|
||||
for name, func in functions:
|
||||
if 'scene' in name.lower() or name in ['createScene', 'setScene']:
|
||||
scene_funcs.append((name, func))
|
||||
elif any(x in name.lower() for x in ['sound', 'music', 'volume']):
|
||||
audio_funcs.append((name, func))
|
||||
elif name in ['find', 'findAll']:
|
||||
ui_funcs.append((name, func))
|
||||
else:
|
||||
system_funcs.append((name, func))
|
||||
|
||||
# Scene Management
|
||||
if scene_funcs:
|
||||
lines.append("### Scene Management")
|
||||
lines.append("")
|
||||
for name, func in scene_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# Audio
|
||||
if audio_funcs:
|
||||
lines.append("### Audio")
|
||||
lines.append("")
|
||||
for name, func in audio_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# UI Utilities
|
||||
if ui_funcs:
|
||||
lines.append("### UI Utilities")
|
||||
lines.append("")
|
||||
for name, func in ui_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# System
|
||||
if system_funcs:
|
||||
lines.append("### System")
|
||||
lines.append("")
|
||||
for name, func in system_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# Automation module
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
lines.append("## Automation Module")
|
||||
lines.append("")
|
||||
lines.append("The `mcrfpy.automation` module provides testing and automation capabilities.")
|
||||
lines.append("")
|
||||
|
||||
automation = mcrfpy.automation
|
||||
auto_funcs = []
|
||||
|
||||
for name in sorted(dir(automation)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
auto_funcs.append((name, obj))
|
||||
|
||||
for name, func in auto_funcs:
|
||||
# Format as static method
|
||||
func_info = get_function_info(func, name)
|
||||
lines.append(f"### `automation.{func_info['signature']}`")
|
||||
lines.append("")
|
||||
if func_info['doc']:
|
||||
lines.append(func_info['doc'])
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def generate_html_docs(markdown_content: str) -> str:
|
||||
"""Convert markdown to HTML."""
|
||||
# Simple conversion - in production use a proper markdown parser
|
||||
html = ['<!DOCTYPE html>']
|
||||
html.append('<html><head>')
|
||||
html.append('<meta charset="UTF-8">')
|
||||
html.append('<title>McRogueFace API Reference</title>')
|
||||
html.append('<style>')
|
||||
html.append('''
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
|
||||
h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
|
||||
h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
||||
h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
|
||||
code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
|
||||
pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
|
||||
pre code { background: none; padding: 0; }
|
||||
blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
|
||||
hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
|
||||
a { color: #3498db; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.property { color: #27ae60; }
|
||||
.method { color: #2980b9; }
|
||||
.class-name { color: #8e44ad; font-weight: bold; }
|
||||
ul { padding-left: 24px; }
|
||||
li { margin: 4px 0; }
|
||||
''')
|
||||
html.append('</style>')
|
||||
html.append('</head><body>')
|
||||
|
||||
# Very basic markdown to HTML conversion
|
||||
lines = markdown_content.split('\n')
|
||||
in_code_block = False
|
||||
in_list = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
if stripped.startswith('```'):
|
||||
if in_code_block:
|
||||
html.append('</code></pre>')
|
||||
in_code_block = False
|
||||
else:
|
||||
lang = stripped[3:] or 'python'
|
||||
html.append(f'<pre><code class="language-{lang}">')
|
||||
in_code_block = True
|
||||
continue
|
||||
|
||||
if in_code_block:
|
||||
html.append(line)
|
||||
continue
|
||||
|
||||
# Headers
|
||||
if stripped.startswith('#'):
|
||||
level = len(stripped.split()[0])
|
||||
text = stripped[level:].strip()
|
||||
html.append(f'<h{level}>{text}</h{level}>')
|
||||
# Lists
|
||||
elif stripped.startswith('- '):
|
||||
if not in_list:
|
||||
html.append('<ul>')
|
||||
in_list = True
|
||||
html.append(f'<li>{stripped[2:]}</li>')
|
||||
# Horizontal rule
|
||||
elif stripped == '---':
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
in_list = False
|
||||
html.append('<hr>')
|
||||
# Emphasis
|
||||
elif stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2:
|
||||
html.append(f'<em>{stripped[1:-1]}</em>')
|
||||
# Bold
|
||||
elif stripped.startswith('**') and stripped.endswith('**'):
|
||||
html.append(f'<strong>{stripped[2:-2]}</strong>')
|
||||
# Regular paragraph
|
||||
elif stripped:
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
in_list = False
|
||||
# Convert inline code
|
||||
text = stripped
|
||||
if '`' in text:
|
||||
import re
|
||||
text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
|
||||
html.append(f'<p>{text}</p>')
|
||||
else:
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
in_list = False
|
||||
# Empty line
|
||||
html.append('')
|
||||
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
if in_code_block:
|
||||
html.append('</code></pre>')
|
||||
|
||||
html.append('</body></html>')
|
||||
return '\n'.join(html)
|
||||
|
||||
def main():
|
||||
"""Generate API documentation in multiple formats."""
|
||||
print("Generating McRogueFace API Documentation...")
|
||||
|
||||
# Create docs directory
|
||||
docs_dir = Path("docs")
|
||||
docs_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Generate markdown documentation
|
||||
print("- Generating Markdown documentation...")
|
||||
markdown_content = generate_markdown_docs()
|
||||
|
||||
# Write markdown
|
||||
md_path = docs_dir / "API_REFERENCE.md"
|
||||
with open(md_path, 'w') as f:
|
||||
f.write(markdown_content)
|
||||
print(f" ✓ Written to {md_path}")
|
||||
|
||||
# Generate HTML
|
||||
print("- Generating HTML documentation...")
|
||||
html_content = generate_html_docs(markdown_content)
|
||||
|
||||
# Write HTML
|
||||
html_path = docs_dir / "api_reference.html"
|
||||
with open(html_path, 'w') as f:
|
||||
f.write(html_content)
|
||||
print(f" ✓ Written to {html_path}")
|
||||
|
||||
# Summary statistics
|
||||
lines = markdown_content.split('\n')
|
||||
class_count = markdown_content.count('### class')
|
||||
func_count = len([l for l in lines if l.strip().startswith('### `') and 'class' not in l])
|
||||
|
||||
print("\nDocumentation Statistics:")
|
||||
print(f"- Classes documented: {class_count}")
|
||||
print(f"- Functions documented: {func_count}")
|
||||
print(f"- Total lines: {len(lines)}")
|
||||
print(f"- File size: {len(markdown_content):,} bytes")
|
||||
|
||||
print("\nAPI documentation generated successfully!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1602
tools/generate_api_docs_html.py
Normal file
1602
tools/generate_api_docs_html.py
Normal file
File diff suppressed because it is too large
Load diff
119
tools/generate_api_docs_simple.py
Normal file
119
tools/generate_api_docs_simple.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#!/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()
|
||||
111
tools/generate_color_table.py
Normal file
111
tools/generate_color_table.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# data sources: CSS docs, jennyscrayoncollection 2017 article on Crayola colors, XKCD color survey
|
||||
|
||||
# target: Single C++ header file to provide a struct of color RGB codes and names.
|
||||
# This file pre-computes the nearest neighbor of every color.
|
||||
# if an RGB code being searched for is closer than the nearest neighbor, it's the closest color name.
|
||||
|
||||
def hex_to_rgb(txt):
|
||||
if '#' in txt: txt = txt.replace('#', '')
|
||||
r = txt[0:2]
|
||||
g = txt[2:4]
|
||||
b = txt[4:6]
|
||||
return tuple([int(s, 16) for s in (r,g,b)])
|
||||
|
||||
class palette:
|
||||
def __init__(self, name, filename, priority):
|
||||
self.name = name
|
||||
self.priority = priority
|
||||
with open(filename, "r") as f:
|
||||
print(f"scanning {filename}")
|
||||
self.colors = {}
|
||||
for line in f.read().split('\n'):
|
||||
if len(line.split('\t')) < 2: continue
|
||||
name, code = line.split('\t')
|
||||
#print(name, code)
|
||||
self.colors[name] = hex_to_rgb(code)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Palette '{self.name}' - {len(self.colors)} colors, priority = {self.priority}>"
|
||||
|
||||
palettes = [
|
||||
#palette("jenny", "jenny_colors.txt", 3), # I should probably use wikipedia as a source for copyright reasons
|
||||
palette("crayon", "wikicrayons_colors.txt", 2),
|
||||
palette("xkcd", "xkcd_colors.txt", 1),
|
||||
palette("css", "css_colors.txt", 0),
|
||||
#palette("matplotlib", "matplotlib_colors.txt", 2) # there's like 10 colors total, I think we'll survive without them
|
||||
]
|
||||
|
||||
all_colors = []
|
||||
|
||||
from math import sqrt
|
||||
def rgbdist(c1, c2):
|
||||
return sqrt((c1.r - c2.r)**2 + (c1.g - c2.g)**2 + (c1.b - c2.b)**2)
|
||||
|
||||
class Color:
|
||||
def __init__(self, r, g, b, name, prefix, priority):
|
||||
self.r = r
|
||||
self.g = g
|
||||
self.b = b
|
||||
self.name = name
|
||||
self.prefix = prefix
|
||||
self.priority = priority
|
||||
self.nearest_neighbor = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Color ({self.r}, {self.g}, {self.b}) - '{self.prefix}:{self.name}', priority = {self.priority}, nearest_neighbor={self.nearest_neighbor.name if self.nearest_neighbor is not None else None}>"
|
||||
|
||||
def nn(self, colors):
|
||||
nearest = None
|
||||
nearest_dist = 999999
|
||||
for c in colors:
|
||||
dist = rgbdist(self, c)
|
||||
if dist == 0: continue
|
||||
if dist < nearest_dist:
|
||||
nearest = c
|
||||
nearest_dist = dist
|
||||
self.nearest_neighbor = nearest
|
||||
|
||||
for p in palettes:
|
||||
prefix = p.name
|
||||
priority = p.priority
|
||||
for name, rgb in p.colors.items():
|
||||
all_colors.append(Color(*rgb, name, prefix, priority))
|
||||
print(f"{prefix}->{len(all_colors)}")
|
||||
|
||||
for c in all_colors:
|
||||
c.nn(all_colors)
|
||||
|
||||
smallest_dist = 9999999999999
|
||||
largest_dist = 0
|
||||
for c in all_colors:
|
||||
dist = rgbdist(c, c.nearest_neighbor)
|
||||
if dist > largest_dist: largest_dist = dist
|
||||
if dist < smallest_dist: smallest_dist = dist
|
||||
#print(f"{c.prefix}:{c.name} -> {c.nearest_neighbor.prefix}:{c.nearest_neighbor.name}\t{rgbdist(c, c.nearest_neighbor):.2f}")
|
||||
# questions -
|
||||
|
||||
# are there any colors where their nearest neighbor's nearest neighbor isn't them? (There should be)
|
||||
nonnear_pairs = 0
|
||||
for c in all_colors:
|
||||
neighbor = c.nearest_neighbor
|
||||
their_neighbor = neighbor.nearest_neighbor
|
||||
if c is not their_neighbor:
|
||||
#print(f"{c.prefix}:{c.name} -> {neighbor.prefix}:{neighbor.name} -> {their_neighbor.prefix}:{their_neighbor.name}")
|
||||
nonnear_pairs += 1
|
||||
print("Non-near pairs:", nonnear_pairs)
|
||||
#print(f"{c.prefix}:{c.name} -> {c.nearest_neighbor.prefix}:{c.nearest_neighbor.name}\t{rgbdist(c, c.nearest_neighbor):.2f}")
|
||||
|
||||
# Are there duplicates? They should be removed from the palette that won't be used
|
||||
dupes = 0
|
||||
for c in all_colors:
|
||||
for c2 in all_colors:
|
||||
if c is c2: continue
|
||||
if c.r == c2.r and c.g == c2.g and c.b == c2.b:
|
||||
dupes += 1
|
||||
print("dupes:", dupes, "this many will need to be removed:", dupes / 2)
|
||||
|
||||
# What order to put them in? Do we want large radiuses first, or some sort of "common color" table?
|
||||
|
||||
# does manhattan distance change any answers over the 16.7M RGB values?
|
||||
|
||||
# What's the worst case lookup? (Checking all 1200 colors to find the name?)
|
||||
960
tools/generate_complete_api_docs.py
Normal file
960
tools/generate_complete_api_docs.py
Normal file
|
|
@ -0,0 +1,960 @@
|
|||
#!/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()
|
||||
821
tools/generate_complete_markdown_docs.py
Normal file
821
tools/generate_complete_markdown_docs.py
Normal file
|
|
@ -0,0 +1,821 @@
|
|||
#!/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()
|
||||
510
tools/generate_dynamic_docs.py
Normal file
510
tools/generate_dynamic_docs.py
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dynamic documentation generator for McRogueFace.
|
||||
Extracts all documentation directly from the compiled module using introspection.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import datetime
|
||||
import html
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
# Must be run with McRogueFace as interpreter
|
||||
try:
|
||||
import mcrfpy
|
||||
except ImportError:
|
||||
print("Error: This script must be run with McRogueFace as the interpreter")
|
||||
print("Usage: ./build/mcrogueface --exec generate_dynamic_docs.py")
|
||||
sys.exit(1)
|
||||
|
||||
def parse_docstring(docstring):
|
||||
"""Parse a docstring to extract signature, description, args, and returns."""
|
||||
if not docstring:
|
||||
return {"signature": "", "description": "", "args": [], "returns": "", "example": ""}
|
||||
|
||||
lines = docstring.strip().split('\n')
|
||||
result = {
|
||||
"signature": "",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"returns": "",
|
||||
"example": ""
|
||||
}
|
||||
|
||||
# First line often contains the signature
|
||||
if lines and '(' in lines[0] and ')' in lines[0]:
|
||||
result["signature"] = lines[0].strip()
|
||||
lines = lines[1:] if len(lines) > 1 else []
|
||||
|
||||
# Parse the rest
|
||||
current_section = "description"
|
||||
description_lines = []
|
||||
example_lines = []
|
||||
in_example = False
|
||||
|
||||
for line in lines:
|
||||
line_lower = line.strip().lower()
|
||||
|
||||
if line_lower.startswith("args:") or line_lower.startswith("arguments:"):
|
||||
current_section = "args"
|
||||
continue
|
||||
elif line_lower.startswith("returns:") or line_lower.startswith("return:"):
|
||||
current_section = "returns"
|
||||
result["returns"] = line[line.find(':')+1:].strip()
|
||||
continue
|
||||
elif line_lower.startswith("example:") or line_lower.startswith("examples:"):
|
||||
in_example = True
|
||||
continue
|
||||
elif line_lower.startswith("note:"):
|
||||
if description_lines:
|
||||
description_lines.append("")
|
||||
description_lines.append(line)
|
||||
continue
|
||||
|
||||
if in_example:
|
||||
example_lines.append(line)
|
||||
elif current_section == "description" and not line.startswith(" "):
|
||||
description_lines.append(line)
|
||||
elif current_section == "args" and line.strip():
|
||||
# Parse argument lines like " x: X coordinate"
|
||||
match = re.match(r'\s+(\w+):\s*(.+)', line)
|
||||
if match:
|
||||
result["args"].append({
|
||||
"name": match.group(1),
|
||||
"description": match.group(2).strip()
|
||||
})
|
||||
elif current_section == "returns" and line.strip() and line.startswith(" "):
|
||||
result["returns"] += " " + line.strip()
|
||||
|
||||
result["description"] = '\n'.join(description_lines).strip()
|
||||
result["example"] = '\n'.join(example_lines).strip()
|
||||
|
||||
return result
|
||||
|
||||
def get_all_functions():
|
||||
"""Get all module-level functions."""
|
||||
functions = {}
|
||||
for name in dir(mcrfpy):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
obj = getattr(mcrfpy, name)
|
||||
if inspect.isbuiltin(obj) or inspect.isfunction(obj):
|
||||
doc_info = parse_docstring(obj.__doc__)
|
||||
functions[name] = {
|
||||
"name": name,
|
||||
"doc": obj.__doc__ or "",
|
||||
"parsed": doc_info
|
||||
}
|
||||
return functions
|
||||
|
||||
def get_all_classes():
|
||||
"""Get all classes and their methods/properties."""
|
||||
classes = {}
|
||||
for name in dir(mcrfpy):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
obj = getattr(mcrfpy, name)
|
||||
if inspect.isclass(obj):
|
||||
class_info = {
|
||||
"name": name,
|
||||
"doc": obj.__doc__ or "",
|
||||
"methods": {},
|
||||
"properties": {},
|
||||
"bases": [base.__name__ for base in obj.__bases__ if base.__name__ != 'object']
|
||||
}
|
||||
|
||||
# Get methods and properties
|
||||
for attr_name in dir(obj):
|
||||
if attr_name.startswith('__') and attr_name != '__init__':
|
||||
continue
|
||||
|
||||
try:
|
||||
attr = getattr(obj, attr_name)
|
||||
if callable(attr):
|
||||
method_doc = attr.__doc__ or ""
|
||||
class_info["methods"][attr_name] = {
|
||||
"doc": method_doc,
|
||||
"parsed": parse_docstring(method_doc)
|
||||
}
|
||||
elif isinstance(attr, property):
|
||||
prop_doc = (attr.fget.__doc__ if attr.fget else "") or ""
|
||||
class_info["properties"][attr_name] = {
|
||||
"doc": prop_doc,
|
||||
"readonly": attr.fset is None
|
||||
}
|
||||
except:
|
||||
pass
|
||||
|
||||
classes[name] = class_info
|
||||
return classes
|
||||
|
||||
def get_constants():
|
||||
"""Get module constants."""
|
||||
constants = {}
|
||||
for name in dir(mcrfpy):
|
||||
if name.startswith('_') or name[0].islower():
|
||||
continue
|
||||
obj = getattr(mcrfpy, name)
|
||||
if not (inspect.isclass(obj) or callable(obj)):
|
||||
constants[name] = {
|
||||
"name": name,
|
||||
"value": repr(obj) if not name.startswith('default_') else f"<{name}>",
|
||||
"type": type(obj).__name__
|
||||
}
|
||||
return constants
|
||||
|
||||
def generate_html_docs():
|
||||
"""Generate HTML documentation."""
|
||||
functions = get_all_functions()
|
||||
classes = get_all_classes()
|
||||
constants = get_constants()
|
||||
|
||||
html_content = f"""<!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</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}}
|
||||
.container {{
|
||||
background-color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}}
|
||||
h1, h2, h3, h4, h5 {{
|
||||
color: #2c3e50;
|
||||
}}
|
||||
.toc {{
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
.toc ul {{
|
||||
list-style-type: none;
|
||||
padding-left: 20px;
|
||||
}}
|
||||
.toc > ul {{
|
||||
padding-left: 0;
|
||||
}}
|
||||
.toc a {{
|
||||
text-decoration: none;
|
||||
color: #3498db;
|
||||
}}
|
||||
.toc a:hover {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
.method-section {{
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #3498db;
|
||||
}}
|
||||
.function-signature {{
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
background-color: #e9ecef;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}}
|
||||
.class-name {{
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.method-name {{
|
||||
color: #3498db;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}}
|
||||
.property-name {{
|
||||
color: #27ae60;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}}
|
||||
.arg-name {{
|
||||
color: #8b4513;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.arg-type {{
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}}
|
||||
code {{
|
||||
background-color: #f4f4f4;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}}
|
||||
pre {{
|
||||
background-color: #f4f4f4;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}}
|
||||
.deprecated {{
|
||||
text-decoration: line-through;
|
||||
opacity: 0.6;
|
||||
}}
|
||||
.note {{
|
||||
background-color: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
}}
|
||||
.returns {{
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>McRogueFace API Reference</h1>
|
||||
<p><em>Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</em></p>
|
||||
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Table of Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#functions">Functions</a></li>
|
||||
<li><a href="#classes">Classes</a>
|
||||
<ul>
|
||||
"""
|
||||
|
||||
# Add classes to TOC
|
||||
for class_name in sorted(classes.keys()):
|
||||
html_content += f' <li><a href="#{class_name}">{class_name}</a></li>\n'
|
||||
|
||||
html_content += """ </ul>
|
||||
</li>
|
||||
<li><a href="#constants">Constants</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="functions">Functions</h2>
|
||||
"""
|
||||
|
||||
# Generate function documentation
|
||||
for func_name in sorted(functions.keys()):
|
||||
func_info = functions[func_name]
|
||||
parsed = func_info["parsed"]
|
||||
|
||||
html_content += f"""
|
||||
<div class="method-section">
|
||||
<h3><code class="function-signature">{func_name}{parsed['signature'] if parsed['signature'] else '(...)'}</code></h3>
|
||||
<p>{html.escape(parsed['description'])}</p>
|
||||
"""
|
||||
|
||||
if parsed['args']:
|
||||
html_content += " <h4>Arguments:</h4>\n <ul>\n"
|
||||
for arg in parsed['args']:
|
||||
html_content += f" <li><span class='arg-name'>{arg['name']}</span>: {html.escape(arg['description'])}</li>\n"
|
||||
html_content += " </ul>\n"
|
||||
|
||||
if parsed['returns']:
|
||||
html_content += f" <p><span class='returns'>Returns:</span> {html.escape(parsed['returns'])}</p>\n"
|
||||
|
||||
if parsed['example']:
|
||||
html_content += f" <h4>Example:</h4>\n <pre><code>{html.escape(parsed['example'])}</code></pre>\n"
|
||||
|
||||
html_content += " </div>\n"
|
||||
|
||||
# Generate class documentation
|
||||
html_content += "\n <h2 id='classes'>Classes</h2>\n"
|
||||
|
||||
for class_name in sorted(classes.keys()):
|
||||
class_info = classes[class_name]
|
||||
|
||||
html_content += f"""
|
||||
<div class="method-section">
|
||||
<h3 id="{class_name}"><span class="class-name">{class_name}</span></h3>
|
||||
"""
|
||||
|
||||
if class_info['bases']:
|
||||
html_content += f" <p><em>Inherits from: {', '.join(class_info['bases'])}</em></p>\n"
|
||||
|
||||
if class_info['doc']:
|
||||
html_content += f" <p>{html.escape(class_info['doc'])}</p>\n"
|
||||
|
||||
# Properties
|
||||
if class_info['properties']:
|
||||
html_content += " <h4>Properties:</h4>\n <ul>\n"
|
||||
for prop_name, prop_info in sorted(class_info['properties'].items()):
|
||||
readonly = " (read-only)" if prop_info['readonly'] else ""
|
||||
html_content += f" <li><span class='property-name'>{prop_name}</span>{readonly}"
|
||||
if prop_info['doc']:
|
||||
html_content += f": {html.escape(prop_info['doc'])}"
|
||||
html_content += "</li>\n"
|
||||
html_content += " </ul>\n"
|
||||
|
||||
# Methods
|
||||
if class_info['methods']:
|
||||
html_content += " <h4>Methods:</h4>\n"
|
||||
for method_name, method_info in sorted(class_info['methods'].items()):
|
||||
if method_name == '__init__':
|
||||
continue
|
||||
parsed = method_info['parsed']
|
||||
|
||||
html_content += f"""
|
||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||
<h5><code class="method-name">{method_name}{parsed['signature'] if parsed['signature'] else '(...)'}</code></h5>
|
||||
"""
|
||||
|
||||
if parsed['description']:
|
||||
html_content += f" <p>{html.escape(parsed['description'])}</p>\n"
|
||||
|
||||
if parsed['args']:
|
||||
html_content += " <div style='margin-left: 20px;'>\n"
|
||||
for arg in parsed['args']:
|
||||
html_content += f" <div><span class='arg-name'>{arg['name']}</span>: {html.escape(arg['description'])}</div>\n"
|
||||
html_content += " </div>\n"
|
||||
|
||||
if parsed['returns']:
|
||||
html_content += f" <p style='margin-left: 20px;'><span class='returns'>Returns:</span> {html.escape(parsed['returns'])}</p>\n"
|
||||
|
||||
html_content += " </div>\n"
|
||||
|
||||
html_content += " </div>\n"
|
||||
|
||||
# Constants
|
||||
html_content += "\n <h2 id='constants'>Constants</h2>\n <ul>\n"
|
||||
for const_name, const_info in sorted(constants.items()):
|
||||
html_content += f" <li><code>{const_name}</code> ({const_info['type']}): {const_info['value']}</li>\n"
|
||||
html_content += " </ul>\n"
|
||||
|
||||
html_content += """
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Write the file
|
||||
output_path = Path("docs/api_reference_dynamic.html")
|
||||
output_path.parent.mkdir(exist_ok=True)
|
||||
output_path.write_text(html_content)
|
||||
print(f"Generated {output_path}")
|
||||
print(f"Found {len(functions)} functions, {len(classes)} classes, {len(constants)} constants")
|
||||
|
||||
def generate_markdown_docs():
|
||||
"""Generate Markdown documentation."""
|
||||
functions = get_all_functions()
|
||||
classes = get_all_classes()
|
||||
constants = get_constants()
|
||||
|
||||
md_content = f"""# McRogueFace API Reference
|
||||
|
||||
*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*
|
||||
|
||||
*This documentation was dynamically generated from the compiled module.*
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Functions](#functions)
|
||||
- [Classes](#classes)
|
||||
"""
|
||||
|
||||
# Add classes to TOC
|
||||
for class_name in sorted(classes.keys()):
|
||||
md_content += f" - [{class_name}](#{class_name.lower()})\n"
|
||||
|
||||
md_content += "- [Constants](#constants)\n\n"
|
||||
|
||||
# Functions
|
||||
md_content += "## Functions\n\n"
|
||||
|
||||
for func_name in sorted(functions.keys()):
|
||||
func_info = functions[func_name]
|
||||
parsed = func_info["parsed"]
|
||||
|
||||
md_content += f"### `{func_name}{parsed['signature'] if parsed['signature'] else '(...)'}`\n\n"
|
||||
|
||||
if parsed['description']:
|
||||
md_content += f"{parsed['description']}\n\n"
|
||||
|
||||
if parsed['args']:
|
||||
md_content += "**Arguments:**\n"
|
||||
for arg in parsed['args']:
|
||||
md_content += f"- `{arg['name']}`: {arg['description']}\n"
|
||||
md_content += "\n"
|
||||
|
||||
if parsed['returns']:
|
||||
md_content += f"**Returns:** {parsed['returns']}\n\n"
|
||||
|
||||
if parsed['example']:
|
||||
md_content += f"**Example:**\n```python\n{parsed['example']}\n```\n\n"
|
||||
|
||||
# Classes
|
||||
md_content += "## Classes\n\n"
|
||||
|
||||
for class_name in sorted(classes.keys()):
|
||||
class_info = classes[class_name]
|
||||
|
||||
md_content += f"### {class_name}\n\n"
|
||||
|
||||
if class_info['bases']:
|
||||
md_content += f"*Inherits from: {', '.join(class_info['bases'])}*\n\n"
|
||||
|
||||
if class_info['doc']:
|
||||
md_content += f"{class_info['doc']}\n\n"
|
||||
|
||||
# Properties
|
||||
if class_info['properties']:
|
||||
md_content += "**Properties:**\n"
|
||||
for prop_name, prop_info in sorted(class_info['properties'].items()):
|
||||
readonly = " *(read-only)*" if prop_info['readonly'] else ""
|
||||
md_content += f"- `{prop_name}`{readonly}"
|
||||
if prop_info['doc']:
|
||||
md_content += f": {prop_info['doc']}"
|
||||
md_content += "\n"
|
||||
md_content += "\n"
|
||||
|
||||
# Methods
|
||||
if class_info['methods']:
|
||||
md_content += "**Methods:**\n\n"
|
||||
for method_name, method_info in sorted(class_info['methods'].items()):
|
||||
if method_name == '__init__':
|
||||
continue
|
||||
parsed = method_info['parsed']
|
||||
|
||||
md_content += f"#### `{method_name}{parsed['signature'] if parsed['signature'] else '(...)'}`\n\n"
|
||||
|
||||
if parsed['description']:
|
||||
md_content += f"{parsed['description']}\n\n"
|
||||
|
||||
if parsed['args']:
|
||||
md_content += "**Arguments:**\n"
|
||||
for arg in parsed['args']:
|
||||
md_content += f"- `{arg['name']}`: {arg['description']}\n"
|
||||
md_content += "\n"
|
||||
|
||||
if parsed['returns']:
|
||||
md_content += f"**Returns:** {parsed['returns']}\n\n"
|
||||
|
||||
# Constants
|
||||
md_content += "## Constants\n\n"
|
||||
for const_name, const_info in sorted(constants.items()):
|
||||
md_content += f"- `{const_name}` ({const_info['type']}): {const_info['value']}\n"
|
||||
|
||||
# Write the file
|
||||
output_path = Path("docs/API_REFERENCE_DYNAMIC.md")
|
||||
output_path.parent.mkdir(exist_ok=True)
|
||||
output_path.write_text(md_content)
|
||||
print(f"Generated {output_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Generating dynamic documentation from mcrfpy module...")
|
||||
generate_html_docs()
|
||||
generate_markdown_docs()
|
||||
print("Documentation generation complete!")
|
||||
268
tools/generate_stubs.py
Normal file
268
tools/generate_stubs.py
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate .pyi type stub files for McRogueFace Python API.
|
||||
|
||||
This script introspects the mcrfpy module and generates type stubs
|
||||
for better IDE support and type checking.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import types
|
||||
from typing import Dict, List, Set, Any
|
||||
|
||||
# Add the build directory to path to import mcrfpy
|
||||
sys.path.insert(0, './build')
|
||||
|
||||
try:
|
||||
import mcrfpy
|
||||
except ImportError:
|
||||
print("Error: Could not import mcrfpy. Make sure to run this from the project root after building.")
|
||||
sys.exit(1)
|
||||
|
||||
def parse_docstring_signature(doc: str) -> tuple[str, str]:
|
||||
"""Extract signature and description from docstring."""
|
||||
if not doc:
|
||||
return "", ""
|
||||
|
||||
lines = doc.strip().split('\n')
|
||||
if lines:
|
||||
# First line often contains the signature
|
||||
first_line = lines[0]
|
||||
if '(' in first_line and ')' in first_line:
|
||||
# Extract just the part after the function name
|
||||
start = first_line.find('(')
|
||||
end = first_line.rfind(')') + 1
|
||||
if start != -1 and end != 0:
|
||||
sig = first_line[start:end]
|
||||
# Get return type if present
|
||||
if '->' in first_line:
|
||||
ret_start = first_line.find('->')
|
||||
ret_type = first_line[ret_start:].strip()
|
||||
return sig, ret_type
|
||||
return sig, ""
|
||||
return "", ""
|
||||
|
||||
def get_type_hint(obj_type: type) -> str:
|
||||
"""Convert Python type to type hint string."""
|
||||
if obj_type == int:
|
||||
return "int"
|
||||
elif obj_type == float:
|
||||
return "float"
|
||||
elif obj_type == str:
|
||||
return "str"
|
||||
elif obj_type == bool:
|
||||
return "bool"
|
||||
elif obj_type == list:
|
||||
return "List[Any]"
|
||||
elif obj_type == dict:
|
||||
return "Dict[Any, Any]"
|
||||
elif obj_type == tuple:
|
||||
return "Tuple[Any, ...]"
|
||||
elif obj_type == type(None):
|
||||
return "None"
|
||||
else:
|
||||
return "Any"
|
||||
|
||||
def generate_class_stub(class_name: str, cls: type) -> List[str]:
|
||||
"""Generate stub for a class."""
|
||||
lines = []
|
||||
|
||||
# Get class docstring
|
||||
if cls.__doc__:
|
||||
doc_lines = cls.__doc__.strip().split('\n')
|
||||
# Use only the first paragraph for the stub
|
||||
lines.append(f'class {class_name}:')
|
||||
lines.append(f' """{doc_lines[0]}"""')
|
||||
else:
|
||||
lines.append(f'class {class_name}:')
|
||||
|
||||
# Check for __init__ method
|
||||
if hasattr(cls, '__init__'):
|
||||
init_doc = cls.__init__.__doc__ or cls.__doc__
|
||||
if init_doc:
|
||||
sig, ret = parse_docstring_signature(init_doc)
|
||||
if sig:
|
||||
lines.append(f' def __init__(self{sig[1:-1]}) -> None: ...')
|
||||
else:
|
||||
lines.append(f' def __init__(self, *args, **kwargs) -> None: ...')
|
||||
else:
|
||||
lines.append(f' def __init__(self, *args, **kwargs) -> None: ...')
|
||||
|
||||
# Get properties and methods
|
||||
properties = []
|
||||
methods = []
|
||||
|
||||
for attr_name in dir(cls):
|
||||
if attr_name.startswith('_') and not attr_name.startswith('__'):
|
||||
continue
|
||||
|
||||
try:
|
||||
attr = getattr(cls, attr_name)
|
||||
|
||||
if isinstance(attr, property):
|
||||
properties.append((attr_name, attr))
|
||||
elif callable(attr) and not attr_name.startswith('__'):
|
||||
methods.append((attr_name, attr))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Add properties
|
||||
if properties:
|
||||
lines.append('')
|
||||
for prop_name, prop in properties:
|
||||
# Try to determine property type from docstring
|
||||
if prop.fget and prop.fget.__doc__:
|
||||
lines.append(f' @property')
|
||||
lines.append(f' def {prop_name}(self) -> Any: ...')
|
||||
if prop.fset:
|
||||
lines.append(f' @{prop_name}.setter')
|
||||
lines.append(f' def {prop_name}(self, value: Any) -> None: ...')
|
||||
else:
|
||||
lines.append(f' {prop_name}: Any')
|
||||
|
||||
# Add methods
|
||||
if methods:
|
||||
lines.append('')
|
||||
for method_name, method in methods:
|
||||
if method.__doc__:
|
||||
sig, ret = parse_docstring_signature(method.__doc__)
|
||||
if sig and ret:
|
||||
lines.append(f' def {method_name}(self{sig[1:-1]}) {ret}: ...')
|
||||
elif sig:
|
||||
lines.append(f' def {method_name}(self{sig[1:-1]}) -> Any: ...')
|
||||
else:
|
||||
lines.append(f' def {method_name}(self, *args, **kwargs) -> Any: ...')
|
||||
else:
|
||||
lines.append(f' def {method_name}(self, *args, **kwargs) -> Any: ...')
|
||||
|
||||
lines.append('')
|
||||
return lines
|
||||
|
||||
def generate_function_stub(func_name: str, func: Any) -> str:
|
||||
"""Generate stub for a function."""
|
||||
if func.__doc__:
|
||||
sig, ret = parse_docstring_signature(func.__doc__)
|
||||
if sig and ret:
|
||||
return f'def {func_name}{sig} {ret}: ...'
|
||||
elif sig:
|
||||
return f'def {func_name}{sig} -> Any: ...'
|
||||
|
||||
return f'def {func_name}(*args, **kwargs) -> Any: ...'
|
||||
|
||||
def generate_stubs():
|
||||
"""Generate the main mcrfpy.pyi file."""
|
||||
lines = [
|
||||
'"""Type stubs for McRogueFace Python API.',
|
||||
'',
|
||||
'Auto-generated - do not edit directly.',
|
||||
'"""',
|
||||
'',
|
||||
'from typing import Any, List, Dict, Tuple, Optional, Callable, Union',
|
||||
'',
|
||||
'# Module documentation',
|
||||
]
|
||||
|
||||
# Add module docstring as comment
|
||||
if mcrfpy.__doc__:
|
||||
for line in mcrfpy.__doc__.strip().split('\n')[:3]:
|
||||
lines.append(f'# {line}')
|
||||
|
||||
lines.extend(['', '# Classes', ''])
|
||||
|
||||
# Collect all classes
|
||||
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))
|
||||
|
||||
# Generate class stubs
|
||||
for class_name, cls in classes:
|
||||
lines.extend(generate_class_stub(class_name, cls))
|
||||
|
||||
# Generate function stubs
|
||||
if functions:
|
||||
lines.extend(['# Functions', ''])
|
||||
for func_name, func in functions:
|
||||
lines.append(generate_function_stub(func_name, func))
|
||||
lines.append('')
|
||||
|
||||
# Generate constants
|
||||
if constants:
|
||||
lines.extend(['# Constants', ''])
|
||||
for const_name, const in constants:
|
||||
const_type = get_type_hint(type(const))
|
||||
lines.append(f'{const_name}: {const_type}')
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def generate_automation_stubs():
|
||||
"""Generate stubs for the automation submodule."""
|
||||
if not hasattr(mcrfpy, 'automation'):
|
||||
return None
|
||||
|
||||
automation = mcrfpy.automation
|
||||
|
||||
lines = [
|
||||
'"""Type stubs for McRogueFace automation API."""',
|
||||
'',
|
||||
'from typing import Optional, Tuple',
|
||||
'',
|
||||
]
|
||||
|
||||
# Get all automation functions
|
||||
for name in sorted(dir(automation)):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
lines.append(generate_function_stub(name, obj))
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
print("Generating type stubs for McRogueFace...")
|
||||
|
||||
# Generate main module stubs
|
||||
stubs = generate_stubs()
|
||||
|
||||
# Create stubs directory
|
||||
os.makedirs('stubs', exist_ok=True)
|
||||
|
||||
# Write main module stubs
|
||||
with open('stubs/mcrfpy.pyi', 'w') as f:
|
||||
f.write(stubs)
|
||||
print("Generated stubs/mcrfpy.pyi")
|
||||
|
||||
# Generate automation module stubs if available
|
||||
automation_stubs = generate_automation_stubs()
|
||||
if automation_stubs:
|
||||
os.makedirs('stubs/mcrfpy', exist_ok=True)
|
||||
with open('stubs/mcrfpy/__init__.pyi', 'w') as f:
|
||||
f.write(stubs)
|
||||
with open('stubs/mcrfpy/automation.pyi', 'w') as f:
|
||||
f.write(automation_stubs)
|
||||
print("Generated stubs/mcrfpy/automation.pyi")
|
||||
|
||||
print("\nType stubs generated successfully!")
|
||||
print("\nTo use in your IDE:")
|
||||
print("1. Add the 'stubs' directory to your PYTHONPATH")
|
||||
print("2. Or configure your IDE to look for stubs in the 'stubs' directory")
|
||||
print("3. Most IDEs will automatically detect .pyi files")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
574
tools/generate_stubs_v2.py
Normal file
574
tools/generate_stubs_v2.py
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate .pyi type stub files for McRogueFace Python API - Version 2.
|
||||
|
||||
This script creates properly formatted type stubs by manually defining
|
||||
the API based on the documentation we've created.
|
||||
"""
|
||||
|
||||
import os
|
||||
import mcrfpy
|
||||
|
||||
def generate_mcrfpy_stub():
|
||||
"""Generate the main mcrfpy.pyi stub file."""
|
||||
return '''"""Type stubs for McRogueFace Python API.
|
||||
|
||||
Core game engine interface for creating roguelike games with Python.
|
||||
"""
|
||||
|
||||
from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload
|
||||
|
||||
# Type aliases
|
||||
UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid']
|
||||
Transition = Union[str, None]
|
||||
|
||||
# Classes
|
||||
|
||||
class Color:
|
||||
"""SFML Color Object for RGBA colors."""
|
||||
|
||||
r: int
|
||||
g: int
|
||||
b: int
|
||||
a: int
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ...
|
||||
|
||||
def from_hex(self, hex_string: str) -> 'Color':
|
||||
"""Create color from hex string (e.g., '#FF0000' or 'FF0000')."""
|
||||
...
|
||||
|
||||
def to_hex(self) -> str:
|
||||
"""Convert color to hex string format."""
|
||||
...
|
||||
|
||||
def lerp(self, other: 'Color', t: float) -> 'Color':
|
||||
"""Linear interpolation between two colors."""
|
||||
...
|
||||
|
||||
class Vector:
|
||||
"""SFML Vector Object for 2D coordinates."""
|
||||
|
||||
x: float
|
||||
y: float
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float, y: float) -> None: ...
|
||||
|
||||
def add(self, other: 'Vector') -> 'Vector': ...
|
||||
def subtract(self, other: 'Vector') -> 'Vector': ...
|
||||
def multiply(self, scalar: float) -> 'Vector': ...
|
||||
def divide(self, scalar: float) -> 'Vector': ...
|
||||
def distance(self, other: 'Vector') -> float: ...
|
||||
def normalize(self) -> 'Vector': ...
|
||||
def dot(self, other: 'Vector') -> float: ...
|
||||
|
||||
class Texture:
|
||||
"""SFML Texture Object for images."""
|
||||
|
||||
def __init__(self, filename: str) -> None: ...
|
||||
|
||||
filename: str
|
||||
width: int
|
||||
height: int
|
||||
sprite_count: int
|
||||
|
||||
class Font:
|
||||
"""SFML Font Object for text rendering."""
|
||||
|
||||
def __init__(self, filename: str) -> None: ...
|
||||
|
||||
filename: str
|
||||
family: str
|
||||
|
||||
class Drawable:
|
||||
"""Base class for all drawable UI elements."""
|
||||
|
||||
x: float
|
||||
y: float
|
||||
visible: bool
|
||||
z_index: int
|
||||
name: str
|
||||
pos: Vector
|
||||
|
||||
def get_bounds(self) -> Tuple[float, float, float, float]:
|
||||
"""Get bounding box as (x, y, width, height)."""
|
||||
...
|
||||
|
||||
def move(self, dx: float, dy: float) -> None:
|
||||
"""Move by relative offset (dx, dy)."""
|
||||
...
|
||||
|
||||
def resize(self, width: float, height: float) -> None:
|
||||
"""Resize to new dimensions (width, height)."""
|
||||
...
|
||||
|
||||
class Frame(Drawable):
|
||||
"""Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
|
||||
|
||||
A rectangular frame UI element that can contain other drawable elements.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0,
|
||||
fill_color: Optional[Color] = None, outline_color: Optional[Color] = None,
|
||||
outline: float = 0, click: Optional[Callable] = None,
|
||||
children: Optional[List[UIElement]] = None) -> None: ...
|
||||
|
||||
w: float
|
||||
h: float
|
||||
fill_color: Color
|
||||
outline_color: Color
|
||||
outline: float
|
||||
click: Optional[Callable[[float, float, int], None]]
|
||||
children: 'UICollection'
|
||||
clip_children: bool
|
||||
|
||||
class Caption(Drawable):
|
||||
"""Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
|
||||
|
||||
A text display UI element with customizable font and styling.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, text: str = '', x: float = 0, y: float = 0,
|
||||
font: Optional[Font] = None, fill_color: Optional[Color] = None,
|
||||
outline_color: Optional[Color] = None, outline: float = 0,
|
||||
click: Optional[Callable] = None) -> None: ...
|
||||
|
||||
text: str
|
||||
font: Font
|
||||
fill_color: Color
|
||||
outline_color: Color
|
||||
outline: float
|
||||
click: Optional[Callable[[float, float, int], None]]
|
||||
w: float # Read-only, computed from text
|
||||
h: float # Read-only, computed from text
|
||||
|
||||
class Sprite(Drawable):
|
||||
"""Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
|
||||
|
||||
A sprite UI element that displays a texture or portion of a texture atlas.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float = 0, y: float = 0, texture: Optional[Texture] = None,
|
||||
sprite_index: int = 0, scale: float = 1.0,
|
||||
click: Optional[Callable] = None) -> None: ...
|
||||
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
scale: float
|
||||
click: Optional[Callable[[float, float, int], None]]
|
||||
w: float # Read-only, computed from texture
|
||||
h: float # Read-only, computed from texture
|
||||
|
||||
class Grid(Drawable):
|
||||
"""Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
|
||||
|
||||
A grid-based tilemap UI element for rendering tile-based levels and game worlds.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float = 0, y: float = 0, grid_size: Tuple[int, int] = (20, 20),
|
||||
texture: Optional[Texture] = None, tile_width: int = 16, tile_height: int = 16,
|
||||
scale: float = 1.0, click: Optional[Callable] = None) -> None: ...
|
||||
|
||||
grid_size: Tuple[int, int]
|
||||
tile_width: int
|
||||
tile_height: int
|
||||
texture: Texture
|
||||
scale: float
|
||||
points: List[List['GridPoint']]
|
||||
entities: 'EntityCollection'
|
||||
background_color: Color
|
||||
click: Optional[Callable[[int, int, int], None]]
|
||||
|
||||
def at(self, x: int, y: int) -> 'GridPoint':
|
||||
"""Get grid point at tile coordinates."""
|
||||
...
|
||||
|
||||
class GridPoint:
|
||||
"""Grid point representing a single tile."""
|
||||
|
||||
texture_index: int
|
||||
solid: bool
|
||||
color: Color
|
||||
|
||||
class GridPointState:
|
||||
"""State information for a grid point."""
|
||||
|
||||
texture_index: int
|
||||
color: Color
|
||||
|
||||
class Entity(Drawable):
|
||||
"""Entity(grid_x=0, grid_y=0, texture=None, sprite_index=0, name='')
|
||||
|
||||
Game entity that lives within a Grid.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, grid_x: float = 0, grid_y: float = 0, texture: Optional[Texture] = None,
|
||||
sprite_index: int = 0, name: str = '') -> None: ...
|
||||
|
||||
grid_x: float
|
||||
grid_y: float
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
grid: Optional[Grid]
|
||||
|
||||
def at(self, grid_x: float, grid_y: float) -> None:
|
||||
"""Move entity to grid position."""
|
||||
...
|
||||
|
||||
def die(self) -> None:
|
||||
"""Remove entity from its grid."""
|
||||
...
|
||||
|
||||
def index(self) -> int:
|
||||
"""Get index in parent grid's entity collection."""
|
||||
...
|
||||
|
||||
class UICollection:
|
||||
"""Collection of UI drawable elements (Frame, Caption, Sprite, Grid)."""
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, index: int) -> UIElement: ...
|
||||
def __setitem__(self, index: int, value: UIElement) -> None: ...
|
||||
def __delitem__(self, index: int) -> None: ...
|
||||
def __contains__(self, item: UIElement) -> bool: ...
|
||||
def __iter__(self) -> Any: ...
|
||||
def __add__(self, other: 'UICollection') -> 'UICollection': ...
|
||||
def __iadd__(self, other: 'UICollection') -> 'UICollection': ...
|
||||
|
||||
def append(self, item: UIElement) -> None: ...
|
||||
def extend(self, items: List[UIElement]) -> None: ...
|
||||
def remove(self, item: UIElement) -> None: ...
|
||||
def index(self, item: UIElement) -> int: ...
|
||||
def count(self, item: UIElement) -> int: ...
|
||||
|
||||
class EntityCollection:
|
||||
"""Collection of Entity objects."""
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, index: int) -> Entity: ...
|
||||
def __setitem__(self, index: int, value: Entity) -> None: ...
|
||||
def __delitem__(self, index: int) -> None: ...
|
||||
def __contains__(self, item: Entity) -> bool: ...
|
||||
def __iter__(self) -> Any: ...
|
||||
def __add__(self, other: 'EntityCollection') -> 'EntityCollection': ...
|
||||
def __iadd__(self, other: 'EntityCollection') -> 'EntityCollection': ...
|
||||
|
||||
def append(self, item: Entity) -> None: ...
|
||||
def extend(self, items: List[Entity]) -> None: ...
|
||||
def remove(self, item: Entity) -> None: ...
|
||||
def index(self, item: Entity) -> int: ...
|
||||
def count(self, item: Entity) -> int: ...
|
||||
|
||||
class Scene:
|
||||
"""Base class for object-oriented scenes."""
|
||||
|
||||
name: str
|
||||
|
||||
def __init__(self, name: str) -> None: ...
|
||||
|
||||
def activate(self) -> None:
|
||||
"""Called when scene becomes active."""
|
||||
...
|
||||
|
||||
def deactivate(self) -> None:
|
||||
"""Called when scene becomes inactive."""
|
||||
...
|
||||
|
||||
def get_ui(self) -> UICollection:
|
||||
"""Get UI elements collection."""
|
||||
...
|
||||
|
||||
def on_keypress(self, key: str, pressed: bool) -> None:
|
||||
"""Handle keyboard events."""
|
||||
...
|
||||
|
||||
def on_click(self, x: float, y: float, button: int) -> None:
|
||||
"""Handle mouse clicks."""
|
||||
...
|
||||
|
||||
def on_enter(self) -> None:
|
||||
"""Called when entering the scene."""
|
||||
...
|
||||
|
||||
def on_exit(self) -> None:
|
||||
"""Called when leaving the scene."""
|
||||
...
|
||||
|
||||
def on_resize(self, width: int, height: int) -> None:
|
||||
"""Handle window resize events."""
|
||||
...
|
||||
|
||||
def update(self, dt: float) -> None:
|
||||
"""Update scene logic."""
|
||||
...
|
||||
|
||||
class Timer:
|
||||
"""Timer object for scheduled callbacks."""
|
||||
|
||||
name: str
|
||||
interval: int
|
||||
active: bool
|
||||
|
||||
def __init__(self, name: str, callback: Callable[[float], None], interval: int) -> None: ...
|
||||
|
||||
def pause(self) -> None:
|
||||
"""Pause the timer."""
|
||||
...
|
||||
|
||||
def resume(self) -> None:
|
||||
"""Resume the timer."""
|
||||
...
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""Cancel and remove the timer."""
|
||||
...
|
||||
|
||||
class Window:
|
||||
"""Window singleton for managing the game window."""
|
||||
|
||||
resolution: Tuple[int, int]
|
||||
fullscreen: bool
|
||||
vsync: bool
|
||||
title: str
|
||||
fps_limit: int
|
||||
game_resolution: Tuple[int, int]
|
||||
scaling_mode: str
|
||||
|
||||
@staticmethod
|
||||
def get() -> 'Window':
|
||||
"""Get the window singleton instance."""
|
||||
...
|
||||
|
||||
class Animation:
|
||||
"""Animation object for animating UI properties."""
|
||||
|
||||
target: Any
|
||||
property: str
|
||||
duration: float
|
||||
easing: str
|
||||
loop: bool
|
||||
on_complete: Optional[Callable]
|
||||
|
||||
def __init__(self, target: Any, property: str, start_value: Any, end_value: Any,
|
||||
duration: float, easing: str = 'linear', loop: bool = False,
|
||||
on_complete: Optional[Callable] = None) -> None: ...
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the animation."""
|
||||
...
|
||||
|
||||
def update(self, dt: float) -> bool:
|
||||
"""Update animation, returns True if still running."""
|
||||
...
|
||||
|
||||
def get_current_value(self) -> Any:
|
||||
"""Get the current interpolated value."""
|
||||
...
|
||||
|
||||
# Module functions
|
||||
|
||||
def createSoundBuffer(filename: str) -> int:
|
||||
"""Load a sound effect from a file and return its buffer ID."""
|
||||
...
|
||||
|
||||
def loadMusic(filename: str) -> None:
|
||||
"""Load and immediately play background music from a file."""
|
||||
...
|
||||
|
||||
def setMusicVolume(volume: int) -> None:
|
||||
"""Set the global music volume (0-100)."""
|
||||
...
|
||||
|
||||
def setSoundVolume(volume: int) -> None:
|
||||
"""Set the global sound effects volume (0-100)."""
|
||||
...
|
||||
|
||||
def playSound(buffer_id: int) -> None:
|
||||
"""Play a sound effect using a previously loaded buffer."""
|
||||
...
|
||||
|
||||
def getMusicVolume() -> int:
|
||||
"""Get the current music volume level (0-100)."""
|
||||
...
|
||||
|
||||
def getSoundVolume() -> int:
|
||||
"""Get the current sound effects volume level (0-100)."""
|
||||
...
|
||||
|
||||
def sceneUI(scene: Optional[str] = None) -> UICollection:
|
||||
"""Get all UI elements for a scene."""
|
||||
...
|
||||
|
||||
def currentScene() -> str:
|
||||
"""Get the name of the currently active scene."""
|
||||
...
|
||||
|
||||
def setScene(scene: str, transition: Optional[str] = None, duration: float = 0.0) -> None:
|
||||
"""Switch to a different scene with optional transition effect."""
|
||||
...
|
||||
|
||||
def createScene(name: str) -> None:
|
||||
"""Create a new empty scene."""
|
||||
...
|
||||
|
||||
def keypressScene(handler: Callable[[str, bool], None]) -> None:
|
||||
"""Set the keyboard event handler for the current scene."""
|
||||
...
|
||||
|
||||
def setTimer(name: str, handler: Callable[[float], None], interval: int) -> None:
|
||||
"""Create or update a recurring timer."""
|
||||
...
|
||||
|
||||
def delTimer(name: str) -> None:
|
||||
"""Stop and remove a timer."""
|
||||
...
|
||||
|
||||
def exit() -> None:
|
||||
"""Cleanly shut down the game engine and exit the application."""
|
||||
...
|
||||
|
||||
def setScale(multiplier: float) -> None:
|
||||
"""Scale the game window size (deprecated - use Window.resolution)."""
|
||||
...
|
||||
|
||||
def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]:
|
||||
"""Find the first UI element with the specified name."""
|
||||
...
|
||||
|
||||
def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]:
|
||||
"""Find all UI elements matching a name pattern (supports * wildcards)."""
|
||||
...
|
||||
|
||||
def getMetrics() -> Dict[str, Union[int, float]]:
|
||||
"""Get current performance metrics."""
|
||||
...
|
||||
|
||||
# Submodule
|
||||
class automation:
|
||||
"""Automation API for testing and scripting."""
|
||||
|
||||
@staticmethod
|
||||
def screenshot(filename: str) -> bool:
|
||||
"""Save a screenshot to the specified file."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def position() -> Tuple[int, int]:
|
||||
"""Get current mouse position as (x, y) tuple."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def size() -> Tuple[int, int]:
|
||||
"""Get screen size as (width, height) tuple."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def onScreen(x: int, y: int) -> bool:
|
||||
"""Check if coordinates are within screen bounds."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def moveTo(x: int, y: int, duration: float = 0.0) -> None:
|
||||
"""Move mouse to absolute position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def moveRel(xOffset: int, yOffset: int, duration: float = 0.0) -> None:
|
||||
"""Move mouse relative to current position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def dragTo(x: int, y: int, duration: float = 0.0, button: str = 'left') -> None:
|
||||
"""Drag mouse to position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def dragRel(xOffset: int, yOffset: int, duration: float = 0.0, button: str = 'left') -> None:
|
||||
"""Drag mouse relative to current position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def click(x: Optional[int] = None, y: Optional[int] = None, clicks: int = 1,
|
||||
interval: float = 0.0, button: str = 'left') -> None:
|
||||
"""Click mouse at position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def mouseDown(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None:
|
||||
"""Press mouse button down."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def mouseUp(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None:
|
||||
"""Release mouse button."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def keyDown(key: str) -> None:
|
||||
"""Press key down."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def keyUp(key: str) -> None:
|
||||
"""Release key."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def press(key: str) -> None:
|
||||
"""Press and release a key."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def typewrite(text: str, interval: float = 0.0) -> None:
|
||||
"""Type text with optional interval between characters."""
|
||||
...
|
||||
'''
|
||||
|
||||
def main():
|
||||
"""Generate type stubs."""
|
||||
print("Generating comprehensive type stubs for McRogueFace...")
|
||||
|
||||
# Create stubs directory
|
||||
os.makedirs('stubs', exist_ok=True)
|
||||
|
||||
# Write main stub file
|
||||
with open('stubs/mcrfpy.pyi', 'w') as f:
|
||||
f.write(generate_mcrfpy_stub())
|
||||
|
||||
print("Generated stubs/mcrfpy.pyi")
|
||||
|
||||
# Create py.typed marker
|
||||
with open('stubs/py.typed', 'w') as f:
|
||||
f.write('')
|
||||
|
||||
print("Created py.typed marker")
|
||||
|
||||
print("\nType stubs generated successfully!")
|
||||
print("\nTo use in your IDE:")
|
||||
print("1. Add the 'stubs' directory to your project")
|
||||
print("2. Most IDEs will automatically detect the .pyi files")
|
||||
print("3. For VS Code: add to python.analysis.extraPaths in settings.json")
|
||||
print("4. For PyCharm: mark 'stubs' directory as Sources Root")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
344
tools/ui_methods_documentation.py
Normal file
344
tools/ui_methods_documentation.py
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
# Comprehensive UI Element Method Documentation
|
||||
# This can be inserted into generate_api_docs_html.py in the method_docs dictionary
|
||||
|
||||
ui_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.'
|
||||
}
|
||||
},
|
||||
|
||||
# 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.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Additional property documentation to complement the methods
|
||||
ui_property_docs = {
|
||||
'Drawable': {
|
||||
'visible': 'bool: Whether this element is rendered (default: True)',
|
||||
'opacity': 'float: Transparency level from 0.0 (invisible) to 1.0 (opaque)',
|
||||
'z_index': 'int: Rendering order, higher values appear on top',
|
||||
'name': 'str: Optional name for finding elements',
|
||||
'x': 'float: Horizontal position in pixels',
|
||||
'y': 'float: Vertical position in pixels',
|
||||
'click': 'callable: Click event handler function'
|
||||
},
|
||||
'Caption': {
|
||||
'text': 'str: The displayed text content',
|
||||
'font': 'Font: Font used for rendering',
|
||||
'fill_color': 'Color: Text color',
|
||||
'outline_color': 'Color: Text outline color',
|
||||
'outline': 'float: Outline thickness in pixels',
|
||||
'w': 'float: Read-only computed width based on text',
|
||||
'h': 'float: Read-only computed height based on text'
|
||||
},
|
||||
'Entity': {
|
||||
'grid_x': 'float: X position in grid coordinates',
|
||||
'grid_y': 'float: Y position in grid coordinates',
|
||||
'sprite_index': 'int: Index of sprite in texture atlas',
|
||||
'texture': 'Texture: Texture used for rendering',
|
||||
'gridstate': 'list: Read-only list of GridPointState objects'
|
||||
},
|
||||
'Frame': {
|
||||
'w': 'float: Width in pixels',
|
||||
'h': 'float: Height in pixels',
|
||||
'fill_color': 'Color: Background fill color',
|
||||
'outline_color': 'Color: Border color',
|
||||
'outline': 'float: Border thickness in pixels',
|
||||
'children': 'UICollection: Child drawable elements',
|
||||
'clip_children': 'bool: Whether to clip children to frame bounds'
|
||||
},
|
||||
'Grid': {
|
||||
'grid_size': 'tuple: Read-only (width, height) in tiles',
|
||||
'grid_x': 'int: Read-only width in tiles',
|
||||
'grid_y': 'int: Read-only height in tiles',
|
||||
'tile_width': 'int: Width of each tile in pixels',
|
||||
'tile_height': 'int: Height of each tile in pixels',
|
||||
'center': 'tuple: (x, y) center point for viewport',
|
||||
'zoom': 'float: Scale factor for rendering',
|
||||
'texture': 'Texture: Tile texture atlas',
|
||||
'background_color': 'Color: Grid background color',
|
||||
'entities': 'EntityCollection: Entities in this grid',
|
||||
'points': 'list: 2D array of GridPoint objects'
|
||||
},
|
||||
'Sprite': {
|
||||
'texture': 'Texture: The displayed texture',
|
||||
'sprite_index': 'int: Index in texture atlas',
|
||||
'scale': 'float: Scaling factor',
|
||||
'w': 'float: Read-only computed width (texture width * scale)',
|
||||
'h': 'float: Read-only computed height (texture height * scale)'
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue