get_class_info.py•10.2 kB
import inspect
import importlib
import unreal # Keep the direct import for potential type hinting or fallback
import json
import re
import sys
def get_type_from_docstring(doc):
    """尝试从文档字符串中提取类型信息 (例如 '(Vector):')"""
    if not doc:
        return "Unknown"
    # 匹配 '(Type):' 或 '[Read-Write]' 后面的类型
    match = re.search(r"\(([\w\.]+)\):", doc)
    if match:
        return match.group(1)
    match_rw = re.search(r"\[Read-Write\]\s*\(([\w\.]+)\)", doc)
    if match_rw:
        return match_rw.group(1)
    return "Unknown"
def get_editor_properties_from_docstring(doc):
    """尝试从类文档字符串中提取 Editor Properties"""
    props = []
    if not doc:
        return props
    # 使用更健壮的正则匹配 Editor Properties 部分
    editor_props_section = re.search(r"\*\*Editor Properties:\*\*\s*\(see get_editor_property/set_editor_property\)\s*\n(.*?)(?=\n\n|\Z)", doc, re.DOTALL | re.IGNORECASE)
    if editor_props_section:
        prop_lines = editor_props_section.group(1).strip().split('\n')
        for line in prop_lines:
            line = line.strip()
            # 匹配 '- ``prop_name`` (type):' 格式
            match = re.search(r"-\s*``(.*?)``\s*\((.*?)\):", line)
            if match:
                prop_name = match.group(1).strip()
                prop_type = match.group(2).strip()
                # 避免重复添加相同的属性名
                # 真的有相同的属性名哇...
                # - ``fixed_bounds`` (bool):  [Read-Write] Whether or not fixed bounds are enabled.
                # - ``fixed_bounds`` (Box):  [Read-Write] The fixed bounding box value for the whole system. When placed in the level and the bounding box is not visible to the camera, the effect is culled from rendering.
                # if not any(p[0] == prop_name for p in props):
                props.append([prop_name, prop_type])
    return props
def inspect_class_to_json(cls):
    """为单个类生成结构化的 JSON 信息"""
    info = {
        "children": [],
        "class_methods": [],
        "editor_properties": [],
        "generation": 0,
        "grand_parent": None,
        "methods": [],
        "name": cls.__name__,
        "parent": None,
        "parents": [],
        "properties": [],
        "referenced_by": {},
        "references": {},
        "static_methods": []
    }
    try:
        mro = inspect.getmro(cls)
        info["parents"] = [base.__name__ for base in mro[1:]]
        if len(mro) > 1:
            info["parent"] = mro[1].__name__
        if len(mro) > 2:
            # 查找 _WrapperBase 或 object 之前的最后一个作为祖父类
            grand_parent_index = -1
            for i in range(len(mro) - 1, 1, -1):
                if mro[i].__name__ not in ('object', '_WrapperBase'):
                    grand_parent_index = i
                    break
                elif mro[i].__name__ == '_WrapperBase' and i > 1: # Prefer _WrapperBase if not direct parent
                    grand_parent_index = i
                    break
            if grand_parent_index != -1 and grand_parent_index < len(mro) -1 :
                # Ensure grand_parent is not the direct parent if possible
                actual_grand_parent_index = grand_parent_index
                if mro[grand_parent_index] == mro[1]: # If calculated grand_parent is the parent
                    if grand_parent_index + 1 < len(mro) and mro[grand_parent_index+1].__name__ != 'object':
                        actual_grand_parent_index = grand_parent_index + 1
                    elif grand_parent_index > 1: # Fallback to parent's parent if exists
                        actual_grand_parent_index = 2
                if actual_grand_parent_index < len(mro):
                    info["grand_parent"] = mro[actual_grand_parent_index].__name__
        class_doc = inspect.getdoc(cls)
        # 获取类的注释部分
        # print(class_doc)
        info["editor_properties"] = get_editor_properties_from_docstring(class_doc)
        references_count = {}
        processed_members = set()
        # 使用 classify_class_attrs 获取并分类当前类定义的成员
        for attr in inspect.classify_class_attrs(cls):
            # print(attr.defining_class)
            
            # 只处理当前类定义的成员
            if attr.defining_class != cls:
                continue
            # 跳过特殊方法和私有方法,除非是 __init__
            if attr.name.startswith('_') and attr.name != '__init__':
                continue
            # print(f"  {attr.name}:{attr.kind}:{attr.defining_class}")
            member_obj = attr.object # 获取成员对象
            if attr.kind == "property":
                info["properties"].append(attr.name)
                prop_doc = inspect.getdoc(member_obj)
                # 假设 get_type_from_docstring 函数存在且可用
                member_type_str = get_type_from_docstring(prop_doc)
                if member_type_str != "Unknown":
                    references_count[member_type_str] = references_count.get(member_type_str, 0) + 1
            elif attr.kind == "class method":
                info["class_methods"].append(attr.name)
                # 注意:原始代码未对类方法或静态方法进行签名分析以查找引用
                # 如果需要,可以在此处添加类似方法的签名分析逻辑
            elif attr.kind == "static method":
                info["static_methods"].append(attr.name)
            elif attr.kind == "method":
                info["methods"].append(attr.name)
                # 分析签名以查找引用 (与原始代码逻辑保持一致)
                try:
                    # 对于方法,attr.object 通常是函数本身
                    sig = inspect.signature(member_obj)
                    for param in sig.parameters.values():
                        if param.annotation != inspect.Parameter.empty and hasattr(param.annotation, '__name__'):
                            ref_name = param.annotation.__name__
                            references_count[ref_name] = references_count.get(ref_name, 0) + 1
                    if sig.return_annotation != inspect.Signature.empty and hasattr(sig.return_annotation, '__name__'):
                        ref_name = sig.return_annotation.__name__
                        references_count[ref_name] = references_count.get(ref_name, 0) + 1
                except (ValueError, TypeError):
                    # 如果无法获取签名(例如,某些内置方法),则忽略
                    pass
            # 可以选择性地处理 'data' 类型的属性
            # elif attr.kind == "data":
            #     if "attributes" not in info: info["attributes"] = []
            #     info["attributes"].append(attr.name)
            #     # 可以在这里添加对数据属性类型注解的分析逻辑
            #     annotations = getattr(cls, '__annotations__', {})
            #     if attr.name in annotations:
            #         annotation = annotations[attr.name]
            #         if hasattr(annotation, '__name__'):
            #              ref_name = annotation.__name__
            #              references_count[ref_name] = references_count.get(ref_name, 0) + 1
        # 注意: 使用 classify_class_attrs 和 defining_class 检查后,
        # 'processed_members' 集合通常不再需要,因为它能正确处理继承和覆盖。
        # Add parent reference
        if info["parent"]:
            references_count[info["parent"]] = references_count.get(info["parent"], 0) + 1
        info["references"] = references_count
        info = {k: v for k, v in info.items() if v or k in ["name", "generation", "children", "referenced_by"]}
    except Exception as e:
        print(f"Error inspecting class {cls.__name__}: {e}")
        # Return basic info on error
        return json.dumps({"name": cls.__name__, "error": str(e)}, indent=2)
    # print(info)
    return json.dumps(info, indent=2)
def get_module_classes(module_name):
    """动态导入模块并获取其中定义的类"""
    try:
        module = importlib.import_module(module_name)
    except ImportError:
        print(f"错误:无法导入模块 '{module_name}'。")
        return []
    # print("断点测试---1")
    # print(module)
    classes = []
    for name, obj in inspect.getmembers(module, inspect.isclass):
        # print(name)
        # 确保类是在当前模块中定义的 (或其子模块,对 unreal 可能需要)
        # if hasattr(obj, '__module__') and obj.__module__ is not None and obj.__module__.startswith(module_name):
            # 进一步检查,避免导入第三方库的类,如果需要的话
            # if 'unreal' in str(obj.__module__): # Example filter
        classes.append((name, obj))
        # print("断点测试---2")
    return classes
if __name__ == "__main__":
    module_to_inspect = "unreal"
    # print(f"Inspecting module: {module_to_inspect}")
    class_list = get_module_classes(module_to_inspect)[1:]
    all_class_info = {}
    # class_json_str = inspect_class_to_json(unreal.Box)
    # print(class_json_str)
    
    if class_list:
        # print(f"Found {len(class_list)} classes in module '{module_to_inspect}'. Generating JSON...")
        # 使用字典来存储所有类的 JSON 信息,以类名为键
        for name, cls_obj in class_list:
            # print("断点测试---3")
            # print(f"Processing: {name}")
            # if name == "Box":
            class_json_str = inspect_class_to_json(cls_obj)
            try:
                # 解析单个类的 JSON 以便合并
                all_class_info[name] = json.loads(class_json_str)
            except json.JSONDecodeError:
                print(f"Warning: Could not decode JSON for class {name}")
                all_class_info[name] = {"name": name, "error": "JSON Decode Error"}
        # 打印包含所有类信息的单个 JSON 对象
        print(json.dumps(all_class_info, indent=2))
    else:
        print(f"No classes found in module '{module_to_inspect}'.")