Directory structure cleanup and organization overhaul
This commit is contained in:
parent
1a143982e1
commit
98fc49a978
119 changed files with 10483 additions and 4042 deletions
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!")
|
||||
Loading…
Add table
Add a link
Reference in a new issue