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