diff options
Diffstat (limited to 'sources/pyside-tools/metaobjectdump.py')
-rw-r--r-- | sources/pyside-tools/metaobjectdump.py | 110 |
1 files changed, 79 insertions, 31 deletions
diff --git a/sources/pyside-tools/metaobjectdump.py b/sources/pyside-tools/metaobjectdump.py index 05a14fdd6..0970f9974 100644 --- a/sources/pyside-tools/metaobjectdump.py +++ b/sources/pyside-tools/metaobjectdump.py @@ -8,7 +8,7 @@ import sys import tokenize from argparse import ArgumentParser, RawTextHelpFormatter from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Set, Tuple, Union DESCRIPTION = """Parses Python source code to create QObject metatype @@ -27,7 +27,19 @@ QML_IMPORT_MINOR_VERSION = "QML_IMPORT_MINOR_VERSION" QT_MODULES = "QT_MODULES" +ITEM_MODELS = ["QAbstractListModel", "QAbstractProxyModel", + "QAbstractTableModel", "QConcatenateTablesProxyModel", + "QFileSystemModel", "QIdentityProxyModel", "QPdfBookmarkModel", + "QPdfSearchModel", "QSortFilterProxyModel", "QSqlQueryModel", + "QStandardItemModel", "QStringListModel", "QTransposeProxyModel", + "QWebEngineHistoryModel"] + + +QOBJECT_DERIVED = ["QObject", "QQuickItem", "QQuickPaintedItem"] + ITEM_MODELS + + AstDecorator = Union[ast.Name, ast.Call] +AstPySideTypeSpec = Union[ast.Name, ast.Constant] ClassList = List[dict] @@ -35,9 +47,10 @@ ClassList = List[dict] PropertyEntry = Dict[str, Union[str, int, bool]] -SignalArgument = Dict[str, str] -SignalArguments = List[SignalArgument] -Signal = Dict[str, Union[str, SignalArguments]] +Argument = Dict[str, str] +Arguments = List[Argument] +Signal = Dict[str, Union[str, Arguments]] +Slot = Dict[str, Union[str, Arguments]] def _decorator(name: str, value: str) -> Dict[str, str]: @@ -84,12 +97,42 @@ def _parse_assignment(node: ast.Assign) -> Tuple[Optional[str], Optional[ast.AST return (None, None) +def _parse_pyside_type(type_spec: AstPySideTypeSpec) -> str: + """Parse type specification of a Slot/Property decorator. Usually a type, + but can also be a string constant with a C++ type name.""" + if isinstance(type_spec, ast.Constant): + return type_spec.value + return _python_to_cpp_type(_name(type_spec)) + + +def _parse_call_args(call: ast.Call): + """Parse arguments of a Signal call/Slot decorator (type list).""" + result: Arguments = [] + for n, arg in enumerate(call.args): + par_name = f"a{n+1}" + par_type = _parse_pyside_type(arg) + result.append({"name": par_name, "type": par_type}) + return result + + +def _parse_slot(func_name: str, call: ast.Call) -> Slot: + """Parse a 'Slot' decorator.""" + return_type = "void" + for kwarg in call.keywords: + if kwarg.arg == "result": + return_type = _python_to_cpp_type(_name(kwarg.value)) + break + return {"access": "public", "name": func_name, + "arguments": _parse_call_args(call), + "returnType": return_type} + + class VisitorContext: """Stores a list of QObject-derived classes encountered in order to find out which classes inherit QObject.""" def __init__(self): - self.qobject_derived = ["QObject", "QQuickItem", "QQuickPaintedItem"] + self.qobject_derived = QOBJECT_DERIVED class MetaObjectDumpVisitor(ast.NodeVisitor): @@ -104,7 +147,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): self._properties: List[PropertyEntry] = [] self._signals: List[Signal] = [] self._within_class: bool = False - self._qt_modules: List[str] = [] + self._qt_modules: Set[str] = set() self._qml_import_name = "" self._qml_import_major_version = 0 self._qml_import_minor_version = 0 @@ -119,7 +162,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): return (self._qml_import_major_version, self._qml_import_minor_version) def qt_modules(self): - return self._qt_modules + return sorted(self._qt_modules) @staticmethod def create_ast(filename: Path) -> ast.Module: @@ -146,6 +189,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): """Visit a class definition""" self._properties = [] self._signals = [] + self._slots = [] self._within_class = True qualified_name = node.name last_dot = qualified_name.rfind('.') @@ -158,12 +202,14 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): q_object = False bases = [] for b in node.bases: - base_name = _name(b) - if base_name in self._context.qobject_derived: - q_object = True - self._context.qobject_derived.append(name) - base_dict = {"access": "public", "name": base_name} - bases.append(base_dict) + # PYSIDE-2202: catch weird constructs like "class C(type(Base)):" + if isinstance(b, ast.Name): + base_name = _name(b) + if base_name in self._context.qobject_derived: + q_object = True + self._context.qobject_derived.append(name) + base_dict = {"access": "public", "name": base_name} + bases.append(base_dict) data["object"] = q_object if bases: @@ -188,6 +234,9 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): if self._signals: data["signals"] = self._signals + if self._slots: + data["slots"] = self._slots + self._json_class_list.append(data) self._within_class = False @@ -224,7 +273,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): elif name == "QmlNamedElement" and node.args: name = node.args[0].value class_decorators.append(_decorator("QML.Element", name)) - else: + elif name.startswith('Q'): print('Unknown decorator with parameters:', name, file=sys.stderr) return @@ -237,7 +286,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): class_decorators.append(_decorator("QML.Singleton", "true")) elif name == "QmlAnonymous": class_decorators.append(_decorator("QML.Element", "anonymous")) - else: + elif name.startswith('Q'): print('Unknown decorator:', name, file=sys.stderr) return @@ -269,16 +318,16 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): return if isinstance(node, ast.Call): - name = node.func.id + name = _name(node.func) if name == "Property": # Property getter - if node.args: # 1st is type - type = _python_to_cpp_type(_name(node.args[0])) + if node.args: # 1st is type/type string + type = _parse_pyside_type(node.args[0]) prop = self._create_property_entry(func_name, type, func_name) _parse_property_kwargs(node.keywords, prop) self._properties.append(prop) elif name == "Slot": - pass + self._slots.append(_parse_slot(func_name, node)) else: print('Unknown decorator with parameters:', name, file=sys.stderr) @@ -290,13 +339,8 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): return func_name = _func_name(call) if func_name == "Signal" or func_name == "QtCore.Signal": - arguments: SignalArguments = [] - for n, arg in enumerate(call.args): - par_name = f"a{n+1}" - par_type = _python_to_cpp_type(_name(arg)) - arguments.append({"name": par_name, "type": par_type}) signal: Signal = {"access": "public", "name": var_name, - "arguments": arguments, + "arguments": _parse_call_args(call), "returnType": "void"} self._signals.append(signal) elif func_name == "Property" or func_name == "QtCore.Property": @@ -313,16 +357,20 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): self._properties.append(prop) def visit_Import(self, node): - if node.names: - self._handle_import(node.names[0].name) + for n in node.names: # "import PySide6.QtWidgets" + self._handle_import(n.name) def visit_ImportFrom(self, node): - self._handle_import(node.module) + if "." in node.module: # "from PySide6.QtWidgets import QWidget" + self._handle_import(node.module) + elif node.module == "PySide6": # "from PySide6 import QtWidgets" + for n in node.names: + if n.name.startswith("Qt"): + self._qt_modules.add(n.name) def _handle_import(self, mod: str): - if mod.startswith('PySide'): - dot = mod.index(".") - self._qt_modules.append(mod[dot + 1:]) + if mod.startswith("PySide6."): + self._qt_modules.add(mod[8:]) def create_arg_parser(desc: str) -> ArgumentParser: |