修复了大量存在的问题,增加了假鸭子类型等等机制

This commit is contained in:
2026-06-25 14:49:46 +08:00
parent 19f2787db0
commit d88d11b646
827 changed files with 32617 additions and 18316 deletions

View File

@@ -1,42 +1,43 @@
"""StubGen 配置模块"""
import json
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class StubGenConfig:
"""存根生成器配置"""
InputDir: Optional[str] = None
OutputDir: str = "./stubs"
InputFiles: List[str] = field(default_factory=list)
IncludePatterns: List[str] = field(default_factory=lambda: ["*.h", "*.c", "*.py"])
ExcludePatterns: List[str] = field(default_factory=list)
TypeMappings: Dict[str, str] = field(default_factory=dict)
PreserveStructure: bool = True # 保持目录结构
GenerateGuards: bool = True # 生成宏守卫
verbose: bool = False
DryRun: bool = False
@classmethod
def from_dict(cls, data: Dict) -> 'StubGenConfig':
"""从字典创建配置"""
return cls(
InputDir=data.get('InputDir'),
OutputDir=data.get('OutputDir', './stubs'),
InputFiles=data.get('InputFiles', []),
IncludePatterns=data.get('IncludePatterns', ['*.h', '*.c', '*.py']),
ExcludePatterns=data.get('ExcludePatterns', []),
TypeMappings=data.get('TypeMappings', {}),
PreserveStructure=data.get('PreserveStructure', True),
GenerateGuards=data.get('GenerateGuards', True),
verbose=data.get('verbose', False),
DryRun=data.get('DryRun', False),
)
@classmethod
def from_file(cls, FilePath: str) -> 'StubGenConfig':
"""从 JSON 文件加载配置"""
with open(FilePath, 'r', encoding='utf-8') as f:
data = json.load(f)
return cls.from_dict(data)
"""StubGen 配置模块"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class StubGenConfig:
"""存根生成器配置"""
InputDir: Optional[str] = None
OutputDir: str = "./stubs"
InputFiles: List[str] = field(default_factory=list)
IncludePatterns: List[str] = field(default_factory=lambda: ["*.h", "*.c", "*.py"])
ExcludePatterns: List[str] = field(default_factory=list)
TypeMappings: Dict[str, str] = field(default_factory=dict)
PreserveStructure: bool = True # 保持目录结构
GenerateGuards: bool = True # 生成宏守卫
verbose: bool = False
DryRun: bool = False
@classmethod
def from_dict(cls, data: dict) -> StubGenConfig:
"""从字典创建配置"""
return cls(
InputDir=data.get('InputDir'),
OutputDir=data.get('OutputDir', './stubs'),
InputFiles=data.get('InputFiles', []),
IncludePatterns=data.get('IncludePatterns', ['*.h', '*.c', '*.py']),
ExcludePatterns=data.get('ExcludePatterns', []),
TypeMappings=data.get('TypeMappings', {}),
PreserveStructure=data.get('PreserveStructure', True),
GenerateGuards=data.get('GenerateGuards', True),
verbose=data.get('verbose', False),
DryRun=data.get('DryRun', False),
)
@classmethod
def from_file(cls, FilePath: str) -> StubGenConfig:
"""从 JSON 文件加载配置"""
with open(FilePath, 'r', encoding='utf-8') as f:
data: dict = json.load(f)
return cls.from_dict(data)

View File

@@ -1,7 +1,10 @@
"""StubGen 转换器模块 - 将 Python 文件转换为存根格式"""
from __future__ import annotations
import ast
import re
import fnmatch
from typing import List, Dict, Optional, Tuple
from lib.core.SymbolUtils import IsListAnnotation
class PythonToStubConverter:
@@ -17,24 +20,23 @@ class PythonToStubConverter:
MacroExcludeList: List[str] = []
@classmethod
def SetIncludeAliasMap(cls, alias_map: Dict[str, str]):
def SetIncludeAliasMap(cls, alias_map: Dict[str, str]) -> None:
"""设置 __include 别名映射表"""
cls.IncludeAliasMap = alias_map
@classmethod
def SetVarExcludeList(cls, exclude_list: List[str]):
def SetVarExcludeList(cls, exclude_list: List[str]) -> None:
"""设置变量排除列表"""
cls.VarExcludeList = exclude_list
@classmethod
def SetMacroExcludeList(cls, exclude_list: List[str]):
def SetMacroExcludeList(cls, exclude_list: List[str]) -> None:
"""设置宏排除列表"""
cls.MacroExcludeList = exclude_list
@classmethod
def _ShouldExcludeVar(cls, VarName: str) -> bool:
"""检查变量是否应该被排除"""
import fnmatch
for pattern in cls.VarExcludeList:
if fnmatch.fnmatch(VarName, pattern):
return True
@@ -43,7 +45,6 @@ class PythonToStubConverter:
@classmethod
def _ShouldExcludeMacro(cls, MacroName: str) -> bool:
"""检查宏是否应该被排除"""
import fnmatch
for pattern in cls.MacroExcludeList:
if fnmatch.fnmatch(MacroName, pattern):
return True
@@ -52,8 +53,8 @@ class PythonToStubConverter:
@staticmethod
def convert(PyContent: str, ModuleName: str) -> str:
"""将 Python 代码转换为存根格式"""
lines = PyContent.split('\n')
StubLines = []
lines: list[str] = PyContent.split('\n')
StubLines: list[str] = []
# 添加文件头
StubLines.append('"""')
@@ -63,11 +64,10 @@ class PythonToStubConverter:
StubLines.append('')
# 解析 Python 代码,检查是否已经导入了 t 和 c
import ast
HasImportT = False
HasImportC = False
HasImportT: bool = False
HasImportC: bool = False
try:
tree = ast.parse(PyContent)
tree: ast.Module = ast.parse(PyContent)
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
@@ -103,24 +103,24 @@ class PythonToStubConverter:
tree = ast.parse(PyContent)
# 第一遍扫描:收集 Postdefinition 变量
PostdefVars = {} # {ClassName: [(VarName, TypeStr, node), ...]}
PostdefVars: dict[str, list[tuple[str, str, ast.AnnAssign]]] = {} # {ClassName: [(VarName, TypeStr, node), ...]}
for node in tree.body:
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
TypeStr = PythonToStubConverter._GetTypeString(node.annotation)
TypeStr: str = PythonToStubConverter._GetTypeString(node.annotation)
# 检查是否是 Postdefinition 类型
if 't.Postdefinition' in TypeStr:
# 提取引用的类名
ClassName = PythonToStubConverter._ExtractPostdefClass(node.annotation)
ClassName: str | None = PythonToStubConverter._ExtractPostdefClass(node.annotation)
if ClassName:
if ClassName not in PostdefVars:
PostdefVars[ClassName] = []
PostdefVars[ClassName].append((node.target.id, TypeStr, node))
# 按原始顺序处理节点,保持代码的原始顺序
PrevType = None # 记录前一个节点的类型
PrevType: str | None = None # 记录前一个节点的类型
for node in tree.body:
CurrentType = None
CurrentType: str | None = None
if isinstance(node, ast.ClassDef):
CurrentType = 'class'
@@ -128,8 +128,8 @@ class PythonToStubConverter:
if PrevType is not None and PrevType != 'class':
StubLines.append('')
# 检查是否有对应的 Postdefinition 变量
ClassPostdefs = PostdefVars.get(node.name, [])
ClassLines = PythonToStubConverter._ConvertClass(
ClassPostdefs: list[tuple[str, str, ast.AnnAssign]] = PostdefVars.get(node.name, [])
ClassLines: list[str] = PythonToStubConverter._ConvertClass(
node, SourceLines=lines, PostdefVars=ClassPostdefs
)
StubLines.extend(ClassLines)
@@ -138,7 +138,7 @@ class PythonToStubConverter:
# 如果前一个是类或者是第一个函数,添加空行分隔
if PrevType == 'class' or (PrevType is not None and PrevType != 'function'):
StubLines.append('')
FuncLines = PythonToStubConverter._ConvertFunction(node, SourceLines=lines)
FuncLines: list[str] = PythonToStubConverter._ConvertFunction(node, SourceLines=lines)
StubLines.extend(FuncLines)
StubLines.append('') # 函数后空行
elif isinstance(node, ast.AnnAssign):
@@ -148,11 +148,11 @@ class PythonToStubConverter:
if 't.Postdefinition' in TypeStr:
continue
# 检查是否带有 t.CStatic 标志,如果有则不排除
HasStatic = 't.CStatic' in TypeStr
HasStatic: bool = 't.CStatic' in TypeStr
# 检查变量是否应该被排除
ShouldExclude = False
ShouldExclude: bool = False
if isinstance(node.target, ast.Name) and not HasStatic:
VarName = node.target.id
VarName: str = node.target.id
if PythonToStubConverter._ShouldExcludeVar(VarName):
ShouldExclude = True
if ShouldExclude:
@@ -160,14 +160,14 @@ class PythonToStubConverter:
# 如果前一个不是变量,添加空行分隔
if PrevType is not None and PrevType != 'variable':
StubLines.append('')
VarLines = PythonToStubConverter._ConvertVariable(node, SourceLines=lines)
VarLines: list[str] = PythonToStubConverter._ConvertVariable(node, SourceLines=lines)
# 移除多余的空行
VarLines = [line for line in VarLines if line.strip()]
StubLines.extend(VarLines)
elif isinstance(node, ast.Import):
# 处理导入语句
CurrentType = 'import'
ImportStr = PythonToStubConverter._GetImportString(node, SourceLines=lines)
ImportStr: str = PythonToStubConverter._GetImportString(node, SourceLines=lines)
if ImportStr:
if PrevType is not None and PrevType != 'import':
StubLines.append('')
@@ -177,7 +177,7 @@ class PythonToStubConverter:
# 处理模块级别的全局变量(无类型标注)
if PrevType is not None and PrevType != 'variable':
StubLines.append('')
AssignLines = PythonToStubConverter._ConvertGlobalAssign(node)
AssignLines: list[str] = PythonToStubConverter._ConvertGlobalAssign(node)
StubLines.extend(AssignLines)
elif isinstance(node, ast.ImportFrom):
# 处理 from ... import ... 语句
@@ -189,14 +189,14 @@ class PythonToStubConverter:
StubLines.append(ImportStr)
elif isinstance(node, ast.Expr):
# 处理模块级别的宏调用,如 c.CIf(...), c.CEndif()
ExprStr = PythonToStubConverter._GetExprString(node.value)
ExprStr: str = PythonToStubConverter._GetExprString(node.value)
if ExprStr:
CurrentType = 'macro'
StubLines.append(ExprStr)
StubLines.append('') # 宏后空行
elif isinstance(node, ast.If):
# 处理模块级别的 if 宏条件,如 if c.CIf(FF_MULTI_PARTITION):
IfStr = PythonToStubConverter._GetModuleIfMacroString(node)
IfStr: str = PythonToStubConverter._GetModuleIfMacroString(node)
if IfStr:
CurrentType = 'macro'
StubLines.append(IfStr)
@@ -220,7 +220,7 @@ class PythonToStubConverter:
return '\n'.join(StubLines)
@staticmethod
def _ConvertClass(node: ast.ClassDef, SourceLines: List[str] = None, PostdefVars: List[Tuple[str, str, ast.AnnAssign]] = None, indent: int = 0) -> List[str]:
def _ConvertClass(node: ast.ClassDef, SourceLines: list[str] | None = None, PostdefVars: list[tuple[str, str, ast.AnnAssign]] | None = None, indent: int = 0) -> list[str]:
"""转换类定义
Args:
@@ -229,24 +229,24 @@ class PythonToStubConverter:
PostdefVars: Postdefinition 变量列表 [(VarName, TypeStr, node), ...]
indent: 缩进级别(用于嵌套类)
"""
lines = []
lines: list[str] = []
PostdefVars = PostdefVars or []
IndentStr = ' ' * indent
IndentStr: str = ' ' * indent
# 处理类装饰器
for decorator in node.decorator_list:
DecoratorStr = PythonToStubConverter._GetDecoratorString(decorator)
DecoratorStr: str = PythonToStubConverter._GetDecoratorString(decorator)
if DecoratorStr:
lines.append(f'{IndentStr}@{DecoratorStr}')
# 检查是否是结构体、联合体或枚举
BaseNames = [base.attr if isinstance(base, ast.Attribute) else base.id
BaseNames: list[str] = [base.attr if isinstance(base, ast.Attribute) else base.id
for base in node.bases
if isinstance(base, (ast.Name, ast.Attribute))]
# 保留原始基类
if node.bases:
BaseStrs = []
BaseStrs: list[str] = []
for base in node.bases:
if isinstance(base, ast.Name):
BaseStrs.append(base.id)
@@ -255,9 +255,9 @@ class PythonToStubConverter:
elif isinstance(base, ast.Subscript):
# 处理泛型类型,如 memory_block_t[MAX_ORDER + 1]
BaseStrs.append(PythonToStubConverter._GetTypeString(base))
ClassTypeParamStr = ''
ClassTypeParamStr: str = ''
if hasattr(node, 'type_params') and node.type_params:
param_names = [tp.name for tp in node.type_params]
param_names: list[str] = [tp.name for tp in node.type_params]
ClassTypeParamStr = f'[{", ".join(param_names)}]'
if BaseStrs:
lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}({", ".join(BaseStrs)}):')
@@ -271,12 +271,12 @@ class PythonToStubConverter:
lines.append(f'{IndentStr}class {node.name}{ClassTypeParamStr}:')
# 处理类成员
HasMembers = False
seen_member_names = set()
HasMembers: bool = False
seen_member_names: set[str] = set()
for item in node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
seen_member_names.add(item.target.id)
init_members = []
init_members: list[ast.AnnAssign] = []
for item in node.body:
if isinstance(item, ast.FunctionDef) and item.name == '__init__':
for stmt in item.body:
@@ -285,7 +285,7 @@ class PythonToStubConverter:
and isinstance(stmt.target.value, ast.Name)
and stmt.target.value.id == 'self'):
init_members.append(stmt)
untyped_self_members = []
untyped_self_members: list[ast.Assign] = []
for m_name in [m.target.attr for m in init_members if isinstance(m.target, ast.Attribute)]:
seen_member_names.add(m_name)
for item in node.body:
@@ -296,7 +296,7 @@ class PythonToStubConverter:
and isinstance(stmt.targets[0], ast.Attribute)
and isinstance(stmt.targets[0].value, ast.Name)
and stmt.targets[0].value.id == 'self'):
attr_name = stmt.targets[0].attr
attr_name: str = stmt.targets[0].attr
if attr_name not in seen_member_names:
seen_member_names.add(attr_name)
untyped_self_members.append(stmt)
@@ -307,11 +307,11 @@ class PythonToStubConverter:
and isinstance(item.targets[0].value, ast.Name)
and item.targets[0].value.id == 'self'):
HasMembers = True
MemberIndent = IndentStr + ' '
MemberIndent: str = IndentStr + ' '
attr_name = item.targets[0].attr
if attr_name in seen_member_names:
continue
InferredType = 'int'
InferredType: str = 'int'
if item.value and isinstance(item.value, ast.Constant):
if isinstance(item.value.value, float):
InferredType = 'float'
@@ -324,86 +324,95 @@ class PythonToStubConverter:
HasMembers = True
MemberIndent = IndentStr + ' '
if SourceLines and hasattr(item, 'lineno'):
StartLine = item.lineno - 1
EndLine = item.EndLineno if hasattr(item, 'EndLineno') and item.EndLineno else StartLine + 1
StartLine: int = item.lineno - 1
EndLine: int = item.end_lineno if hasattr(item, 'end_lineno') and item.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(MemberIndent + SourceLines[i].lstrip())
elif isinstance(item, ast.AnnAssign):
HasMembers = True
TypeStr = PythonToStubConverter._GetTypeString(item.annotation)
TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation)
MemberIndent = IndentStr + ' '
# __provides__/__requires__/__require_must__ 元数据:保留值以便跨模块加载
if isinstance(item.target, ast.Name) and item.target.id in ('__provides__', '__requires__', '__require_must__'):
if item.value is not None:
try:
value_str: str = ast.unparse(item.value)
except Exception:
value_str = ''
lines.append(f'{MemberIndent}{item.target.id}: {TypeStr} = {value_str}')
else:
lines.append(f'{MemberIndent}{item.target.id}: {TypeStr}')
continue
# 检查是否是宏变量(类型包含 t.CDefine
if 't.CDefine' in TypeStr:
# 检查宏变量是否应该被排除
if isinstance(item.target, ast.Name):
MacroName = item.target.id
MacroName: str = item.target.id
if PythonToStubConverter._ShouldExcludeMacro(MacroName):
continue # 跳过该宏
# 宏变量保持原样(包括赋值)
if SourceLines and hasattr(item, 'lineno'):
StartLine = item.lineno - 1
EndLine = item.EndLineno if hasattr(item, 'EndLineno') and item.EndLineno else StartLine + 1
EndLine = item.end_lineno if hasattr(item, 'end_lineno') and item.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(MemberIndent + SourceLines[i].lstrip())
else:
lines.append(f'{MemberIndent}{item.target.id}: {TypeStr}')
else:
if isinstance(item.target, ast.Attribute):
MemberName = item.target.attr
MemberName: str = item.target.attr
else:
MemberName = item.target.id
FinalTypeStr = TypeStr
if (isinstance(item.annotation, ast.Subscript)
and isinstance(item.annotation.value, ast.Name)
and item.annotation.value.id == 'list'):
slice_node = item.annotation.slice
has_size = False
FinalTypeStr: str = TypeStr
if IsListAnnotation(item.annotation):
slice_node: ast.AST = item.annotation.slice
has_size: bool = False
if isinstance(slice_node, ast.Tuple) and len(slice_node.elts) == 2:
count_node = slice_node.elts[1]
count_node: ast.AST = slice_node.elts[1]
if isinstance(count_node, ast.Constant) and isinstance(count_node.value, int) and count_node.value > 0:
has_size = True
else:
try:
count_expr = ast.unparse(count_node)
count_expr: str = ast.unparse(count_node)
except Exception:
count_expr = ''
if count_expr and count_expr != 'None':
has_size = True
elem_type_str = PythonToStubConverter._GetTypeString(slice_node.elts[0])
FinalTypeStr = f'list[{elem_type_str}, {count_expr}]'
elem_type_str: str = PythonToStubConverter._GetTypeString(slice_node.elts[0])
FinalTypeStr = f't.CArray[{elem_type_str}, {count_expr}]'
if not has_size:
elem_type_str = PythonToStubConverter._GetTypeString(slice_node if not isinstance(slice_node, ast.Tuple) else slice_node.elts[0])
init_len = 0
init_len: int = 0
if item.value is not None:
if isinstance(item.value, ast.List):
init_len = len(item.value.elts)
elif isinstance(item.value, ast.Constant) and isinstance(item.value.value, str):
init_len = len(item.value.value) + 1
if init_len > 0:
FinalTypeStr = f'list[{elem_type_str}, {init_len}]'
FinalTypeStr = f't.CArray[{elem_type_str}, {init_len}]'
else:
FinalTypeStr = f'list[{elem_type_str}, None]'
FinalTypeStr = f't.CArray[{elem_type_str}, None]'
lines.append(f'{MemberIndent}{MemberName}: {FinalTypeStr}')
elif isinstance(item, ast.FunctionDef):
HasMembers = True
FuncStub = PythonToStubConverter._ConvertFunction(item, indent=indent+1, SourceLines=SourceLines, class_name=node.name)
FuncStub: list[str] = PythonToStubConverter._ConvertFunction(item, indent=indent+1, SourceLines=SourceLines, class_name=node.name)
lines.extend(FuncStub)
elif isinstance(item, ast.Expr):
# 处理宏调用,如 c.CIf(...), c.CEndif(), c.CElif(), c.CElse()
ExprStr = PythonToStubConverter._GetExprString(item.value)
ExprStr: str = PythonToStubConverter._GetExprString(item.value)
if ExprStr:
lines.append(f'{IndentStr} {ExprStr}')
elif isinstance(item, ast.If):
# 处理 if c.CIf(...): 形式的宏条件
IfStr = PythonToStubConverter._get_if_macro_string(item, indent=indent+1)
IfStr: str = PythonToStubConverter._get_if_macro_string(item, indent=indent+1)
if IfStr:
lines.append(IfStr)
elif isinstance(item, ast.ClassDef):
# 处理嵌套类
HasMembers = True
NestedClassLines = PythonToStubConverter._ConvertClass(item, SourceLines=SourceLines, PostdefVars=[], indent=indent+1)
NestedClassLines: list[str] = PythonToStubConverter._ConvertClass(item, SourceLines=SourceLines, PostdefVars=[], indent=indent+1)
# 移除嵌套类的宏守卫(因为已经在父类的宏守卫内)
FilteredLines = []
FilteredLines: list[str] = []
for line in NestedClassLines:
# 跳过宏守卫相关行
if line.strip().startswith('c.CIfndef(') or line.strip().startswith('c.CEndif()'):
@@ -425,15 +434,15 @@ class PythonToStubConverter:
return lines
@staticmethod
def _ConvertFunction(node: ast.FunctionDef, indent: int = 0, SourceLines: List[str] = None, class_name: str = None) -> List[str]:
def _ConvertFunction(node: ast.FunctionDef, indent: int = 0, SourceLines: list[str] | None = None, class_name: str | None = None) -> list[str]:
"""转换函数定义"""
lines = []
IndentStr = ' ' * indent
lines: list[str] = []
IndentStr: str = ' ' * indent
# 检查是否是宏函数(返回类型包含 t.CDefine
is_macro_func = False
is_macro_func: bool = False
if node.returns:
ReturnType = PythonToStubConverter._GetTypeString(node.returns)
ReturnType: str = PythonToStubConverter._GetTypeString(node.returns)
if 't.CDefine' in ReturnType:
is_macro_func = True
@@ -444,8 +453,8 @@ class PythonToStubConverter:
# 保持原样(包括代码块)
if SourceLines:
StartLine = node.lineno - 1
EndLine = node.EndLineno if hasattr(node, 'EndLineno') and node.EndLineno else StartLine + 1
StartLine: int = node.lineno - 1
EndLine: int = node.end_lineno if hasattr(node, 'end_lineno') and node.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(SourceLines[i])
if lines and lines[-1].strip() and not lines[-1].rstrip().endswith(':'):
@@ -456,16 +465,16 @@ class PythonToStubConverter:
# 处理函数装饰器
for decorator in node.decorator_list:
DecoratorStr = PythonToStubConverter._GetDecoratorString(decorator)
DecoratorStr: str = PythonToStubConverter._GetDecoratorString(decorator)
if DecoratorStr:
lines.append(f'{IndentStr}@{DecoratorStr}')
# 构建参数列表
params = []
params: list[str] = []
for arg_idx, arg in enumerate(node.args.args):
ArgName = arg.arg
ArgName: str = arg.arg
if arg.annotation:
TypeStr = PythonToStubConverter._GetTypeString(arg.annotation)
TypeStr: str = PythonToStubConverter._GetTypeString(arg.annotation)
params.append(f'{ArgName}: {TypeStr}')
elif class_name and arg_idx == 0 and ArgName == 'self':
params.append(f'self: {class_name}')
@@ -490,7 +499,7 @@ class PythonToStubConverter:
else:
params.append(f'**{ArgName}')
ParamStr = ', '.join(params)
ParamStr: str = ', '.join(params)
# 返回类型 - 保持原始类型不添加t.State
if node.returns:
@@ -498,21 +507,21 @@ class PythonToStubConverter:
else:
ReturnType = 't.CInt'
TypeParamStr = ''
TypeParamStr: str = ''
if hasattr(node, 'type_params') and node.type_params:
param_names = [tp.name for tp in node.type_params]
param_names: list[str] = [tp.name for tp in node.type_params]
TypeParamStr = f'[{", ".join(param_names)}]'
lines.append(f'{IndentStr}def {node.name}{TypeParamStr}({ParamStr}) -> {ReturnType}: pass')
return lines
@staticmethod
def _ConvertVariable(node: ast.AnnAssign, SourceLines: List[str] = None) -> List[str]:
def _ConvertVariable(node: ast.AnnAssign, SourceLines: list[str] | None = None) -> list[str]:
"""转换变量定义"""
lines = []
lines: list[str] = []
if isinstance(node.target, ast.Name):
VarName = node.target.id
TypeStr = PythonToStubConverter._GetTypeString(node.annotation)
VarName: str = node.target.id
TypeStr: str = PythonToStubConverter._GetTypeString(node.annotation)
# 检查是否是宏变量(类型包含 t.CDefine
if 't.CDefine' in TypeStr:
# 检查宏变量是否应该被排除
@@ -520,8 +529,8 @@ class PythonToStubConverter:
return lines # 返回空列表,排除该宏
# 宏变量保持原样(包括赋值)
if SourceLines and hasattr(node, 'lineno'):
StartLine = node.lineno - 1
EndLine = node.EndLineno if hasattr(node, 'EndLineno') and node.EndLineno else StartLine + 1
StartLine: int = node.lineno - 1
EndLine: int = node.end_lineno if hasattr(node, 'end_lineno') and node.end_lineno else StartLine + 1
for i in range(StartLine, min(EndLine, len(SourceLines))):
lines.append(SourceLines[i])
else:
@@ -529,9 +538,9 @@ class PythonToStubConverter:
return lines
# 非宏变量:不加 t.State
# 含有 typedef 的变量不加 extern普通变量需要加 extern
has_typedef = 't.CTypedef' in TypeStr or 'CTypedef' in TypeStr
has_typedef: bool = 't.CTypedef' in TypeStr or 'CTypedef' in TypeStr
if has_typedef and node.value is not None:
ValueTypeStr = PythonToStubConverter._GetTypeString(node.value)
ValueTypeStr: str = PythonToStubConverter._GetTypeString(node.value)
lines.append(f'{VarName}: {TypeStr} = {ValueTypeStr}')
else:
if not has_typedef and 't.CExtern' not in TypeStr and 't.CStatic' not in TypeStr:
@@ -540,20 +549,20 @@ class PythonToStubConverter:
return lines
@staticmethod
def _ConvertGlobalAssign(node: ast.Assign) -> List[str]:
def _ConvertGlobalAssign(node: ast.Assign) -> list[str]:
"""转换模块级别的全局变量(无类型标注)"""
lines = []
lines: list[str] = []
if not node.targets or not isinstance(node.targets[0], ast.Name):
return lines
VarName = node.targets[0].id
VarName: str = node.targets[0].id
# 跳过私有变量(以下划线开头)
if VarName.startswith('_'):
return lines
# 从值推断类型
inferred_type = PythonToStubConverter._InferTypeFromValue(node.value)
inferred_type: str | None = PythonToStubConverter._InferTypeFromValue(node.value)
if inferred_type:
lines.append(f'{VarName}: {inferred_type}')
else:
@@ -562,7 +571,7 @@ class PythonToStubConverter:
return lines
@staticmethod
def _InferTypeFromValue(value: ast.AST) -> str:
def _InferTypeFromValue(value: ast.AST) -> str | None:
"""从值推断 C 类型"""
if isinstance(value, ast.Constant):
if isinstance(value.value, int):
@@ -580,14 +589,14 @@ class PythonToStubConverter:
elif isinstance(value, ast.Name):
return 't.CInt'
elif isinstance(value, ast.BinOp):
left_type = PythonToStubConverter._InferTypeFromValue(value.left)
left_type: str | None = PythonToStubConverter._InferTypeFromValue(value.left)
if left_type:
return left_type
return 't.CInt'
elif isinstance(value, ast.Call):
# 函数调用返回值,假设为指针类型
if isinstance(value.func, ast.Name):
func_name = value.func.id
func_name: str = value.func.id
if func_name == 'malloc':
return 't.CVoidPtr'
elif func_name in ('ctypes.cast', 'cast'):
@@ -605,26 +614,26 @@ class PythonToStubConverter:
return f'{annotation.value.id}.{annotation.attr}'
else:
# 处理嵌套属性访问,如 t.attr.packed
ParentStr = PythonToStubConverter._GetTypeString(annotation.value)
ParentStr: str = PythonToStubConverter._GetTypeString(annotation.value)
return f'{ParentStr}.{annotation.attr}'
elif isinstance(annotation, ast.Subscript):
base = PythonToStubConverter._GetTypeString(annotation.value)
base: str = PythonToStubConverter._GetTypeString(annotation.value)
if isinstance(annotation.slice, ast.Tuple):
slice_strs = [PythonToStubConverter._GetTypeString(e) for e in annotation.slice.elts]
SliceStr = ', '.join(slice_strs)
slice_strs: list[str] = [PythonToStubConverter._GetTypeString(e) for e in annotation.slice.elts]
SliceStr: str = ', '.join(slice_strs)
else:
SliceStr = PythonToStubConverter._GetTypeString(annotation.slice)
return f'{base}[{SliceStr}]'
elif isinstance(annotation, ast.BinOp):
# 处理 BinOp如 t.CExtern | fd_table.__set_default__(...)
left = annotation.left
right = annotation.right
left: ast.AST = annotation.left
right: ast.AST = annotation.right
# 检查右侧是否是 __set_default__ 调用
if isinstance(right, ast.Call) and isinstance(right.func, ast.Attribute) and right.func.attr in ('__set_default__', '__set_default'):
# 去除 __set_default__保留左侧右侧替换为函数名部分
LeftStr = PythonToStubConverter._GetTypeString(left)
RightStr = PythonToStubConverter._GetTypeString(right.func.value)
LeftStr: str = PythonToStubConverter._GetTypeString(left)
RightStr: str = PythonToStubConverter._GetTypeString(right.func.value)
if isinstance(annotation.op, ast.BitOr):
return f'{LeftStr} | {RightStr}'
@@ -644,7 +653,7 @@ class PythonToStubConverter:
return f'{LeftStr} % {RightStr}'
elif isinstance(annotation, ast.UnaryOp):
if isinstance(annotation.op, ast.Not):
operand = PythonToStubConverter._GetTypeString(annotation.operand)
operand: str = PythonToStubConverter._GetTypeString(annotation.operand)
return f'not {operand}'
elif isinstance(annotation.op, ast.Invert):
operand = PythonToStubConverter._GetTypeString(annotation.operand)
@@ -654,9 +663,9 @@ class PythonToStubConverter:
return f'-{operand}'
elif isinstance(annotation, ast.Compare):
# 处理比较操作,如 FF_MAX_SS != FF_MIN_SS
left = PythonToStubConverter._GetTypeString(annotation.left)
comparators = [PythonToStubConverter._GetTypeString(c) for c in annotation.comparators]
ops = []
left: str = PythonToStubConverter._GetTypeString(annotation.left)
comparators: list[str] = [PythonToStubConverter._GetTypeString(c) for c in annotation.comparators]
ops: list[str] = []
for op in annotation.ops:
if isinstance(op, ast.Eq):
ops.append('==')
@@ -672,7 +681,7 @@ class PythonToStubConverter:
ops.append('>=')
else:
ops.append('?')
result = left
result: str = left
for i, op in enumerate(ops):
result += f' {op} {comparators[i]}'
return result
@@ -682,7 +691,7 @@ class PythonToStubConverter:
return PythonToStubConverter._GetTypeString(annotation.value)
elif isinstance(annotation, ast.List):
# 处理列表,如 [MAX_FILE_DESCRIPTORS]
elements = [PythonToStubConverter._GetTypeString(elem) for elem in annotation.elts]
elements: list[str] = [PythonToStubConverter._GetTypeString(elem) for elem in annotation.elts]
return f'[{", ".join(elements)}]'
elif isinstance(annotation, ast.Call):
# 处理函数调用形式的类型注解,如 t.Postdefinition(xxa) 或 callable(t.CVoid, app_id = t.CInt)
@@ -690,10 +699,10 @@ class PythonToStubConverter:
if isinstance(annotation.func, ast.Attribute) and annotation.func.attr in ('__set_default__', '__set_default'):
return PythonToStubConverter._GetTypeString(annotation.func.value)
FuncStr = PythonToStubConverter._GetTypeString(annotation.func)
ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in annotation.args])
FuncStr: str = PythonToStubConverter._GetTypeString(annotation.func)
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in annotation.args])
# 处理关键字参数
keywords_str = ', '.join([f'{kw.arg} = {PythonToStubConverter._GetTypeString(kw.value)}' for kw in annotation.keywords])
keywords_str: str = ', '.join([f'{kw.arg} = {PythonToStubConverter._GetTypeString(kw.value)}' for kw in annotation.keywords])
# 合并位置参数和关键字参数
if ArgsStr and keywords_str:
return f'{FuncStr}({ArgsStr}, {keywords_str})'
@@ -704,7 +713,7 @@ class PythonToStubConverter:
return 't.CVoid'
@staticmethod
def _ExtractPostdefClass(annotation: ast.AST) -> Optional[str]:
def _ExtractPostdefClass(annotation: ast.AST) -> str | None:
"""从 Postdefinition 类型注解中提取类名
例如t.Postdefinition(__buddy_system) -> '__buddy_system'
@@ -713,21 +722,21 @@ class PythonToStubConverter:
# 处理 BinOp (如: t.Postdefinition(xxx) | t.CTypedef)
if isinstance(annotation, ast.BinOp):
# 递归检查左操作数
left_result = PythonToStubConverter._ExtractPostdefClass(annotation.left)
left_result: str | None = PythonToStubConverter._ExtractPostdefClass(annotation.left)
if left_result:
return left_result
# 递归检查右操作数
right_result = PythonToStubConverter._ExtractPostdefClass(annotation.right)
right_result: str | None = PythonToStubConverter._ExtractPostdefClass(annotation.right)
if right_result:
return right_result
return None
# 处理函数调用 (如: t.Postdefinition(__buddy_system))
if isinstance(annotation, ast.Call):
FuncStr = PythonToStubConverter._GetTypeString(annotation.func)
FuncStr: str = PythonToStubConverter._GetTypeString(annotation.func)
if FuncStr == 't.Postdefinition' and annotation.args:
# 提取第一个参数
first_arg = annotation.args[0]
first_arg: ast.AST = annotation.args[0]
if isinstance(first_arg, ast.Name):
return first_arg.id
elif isinstance(first_arg, ast.Attribute):
@@ -736,20 +745,20 @@ class PythonToStubConverter:
return None
@staticmethod
def _GetImportString(node: ast.Import, SourceLines: List[str] = None) -> str:
def _GetImportString(node: ast.Import, SourceLines: list[str] | None = None) -> str:
"""获取导入语句字符串"""
parts = []
IncludeAliasMap = PythonToStubConverter.IncludeAliasMap
parts: list[str] = []
IncludeAliasMap: dict[str, str] = PythonToStubConverter.IncludeAliasMap
for alias in node.names:
ModuleName = alias.name
ModuleName: str = alias.name
# 检查是否是 __include.xxx 格式的导入
if ModuleName.startswith('__include.'):
# 提取别名xxx 部分)
SubModule = ModuleName[len('__include.'):]
SubModule: str = ModuleName[len('__include.'):]
if SubModule in IncludeAliasMap:
# 替换为实际模块路径
RealModule = IncludeAliasMap[SubModule]
RealModule: str = IncludeAliasMap[SubModule]
if alias.asname:
parts.append(f'{RealModule} as {alias.asname}')
else:
@@ -766,31 +775,31 @@ class PythonToStubConverter:
else:
parts.append(alias.name)
result = f'import {", ".join(parts)}'
result: str = f'import {", ".join(parts)}'
# 提取并保留注释
if SourceLines and hasattr(node, 'lineno'):
LineIdx = node.lineno - 1
LineIdx: int = node.lineno - 1
if 0 <= LineIdx < len(SourceLines):
line = SourceLines[LineIdx]
CommentMatch = re.search(r'#.*$', line)
line: str = SourceLines[LineIdx]
CommentMatch: re.Match | None = re.search(r'#.*$', line)
if CommentMatch:
result += ' ' + CommentMatch.group(0)
return result
@staticmethod
def _GetImportFromString(node: ast.ImportFrom, SourceLines: List[str] = None) -> str:
def _GetImportFromString(node: ast.ImportFrom, SourceLines: list[str] | None = None) -> str:
"""获取 from ... import ... 语句字符串"""
module = node.module or ''
parts = []
IncludeAliasMap = PythonToStubConverter.IncludeAliasMap
module: str = node.module or ''
parts: list[str] = []
IncludeAliasMap: dict[str, str] = PythonToStubConverter.IncludeAliasMap
# 处理相对导入 (from . import xxx 或 from ..package import xxx)
if node.level > 0:
if module:
if node.level == 1:
full_module = f'.{module}'
full_module: str = f'.{module}'
else:
full_module = '.' * node.level + module
else:
@@ -803,22 +812,22 @@ class PythonToStubConverter:
return f'from {full_module} import {", ".join(parts)}'
# 提取注释(在生成结果前获取)
comment = ''
comment: str = ''
if SourceLines and hasattr(node, 'lineno'):
LineIdx = node.lineno - 1
if 0 <= LineIdx < len(SourceLines):
line = SourceLines[LineIdx]
CommentMatch = re.search(r'#.*$', line)
CommentMatch: re.Match | None = re.search(r'#.*$', line)
if CommentMatch:
comment = ' ' + CommentMatch.group(0)
# 检查是否是 from __include import xxx 格式
if module == '__include':
for alias in node.names:
name = alias.name
name: str = alias.name
if name in IncludeAliasMap:
# 替换为实际模块路径
RealModule = IncludeAliasMap[name]
RealModule: str = IncludeAliasMap[name]
if alias.asname:
parts.append(f'import {RealModule} as {alias.asname}{comment}')
else:
@@ -850,8 +859,8 @@ class PythonToStubConverter:
return decorator.attr
elif isinstance(decorator, ast.Call):
# 处理带参数的装饰器,如 @c.Attribute(t.attr.packed)
FuncStr = PythonToStubConverter._GetDecoratorString(decorator.func)
ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in decorator.args])
FuncStr: str = PythonToStubConverter._GetDecoratorString(decorator.func)
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in decorator.args])
return f'{FuncStr}({ArgsStr})'
return ''
@@ -860,30 +869,30 @@ class PythonToStubConverter:
"""获取表达式字符串(用于宏调用)"""
if isinstance(expr, ast.Call):
# 处理函数调用,如 c.CIf(...), c.CEndif(), c.CUndef(...), c.CPragma(...)
FuncStr = PythonToStubConverter._GetDecoratorString(expr.func)
FuncStr: str = PythonToStubConverter._GetDecoratorString(expr.func)
if FuncStr and ('CIf' in FuncStr or 'CEndif' in FuncStr or 'CElif' in FuncStr or 'CElse' in FuncStr or 'CUndef' in FuncStr or 'CPragma' in FuncStr):
ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in expr.args])
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in expr.args])
return f'{FuncStr}({ArgsStr})' if ArgsStr else f'{FuncStr}()'
return ''
@staticmethod
def _get_if_macro_string(node: ast.If, indent: int = 1) -> str:
"""获取类内部的 if 宏字符串(如 if c.CIf(...):"""
IndentStr = ' ' * indent
InnerIndent = ' ' * (indent + 1)
IndentStr: str = ' ' * indent
InnerIndent: str = ' ' * (indent + 1)
# 检查条件是否是宏调用
if isinstance(node.test, ast.Call):
FuncStr = PythonToStubConverter._GetDecoratorString(node.test.func)
FuncStr: str = PythonToStubConverter._GetDecoratorString(node.test.func)
if FuncStr and 'CIf' in FuncStr:
ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args])
result = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():']
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args])
result: list[str] = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():']
# 处理 if 体内的语句
for item in node.body:
if isinstance(item, ast.AnnAssign):
TypeStr = PythonToStubConverter._GetTypeString(item.annotation)
TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation)
result.append(f'{InnerIndent}{item.target.id}: {TypeStr}')
elif isinstance(item, ast.Expr):
ExprStr = PythonToStubConverter._GetExprString(item.value)
ExprStr: str = PythonToStubConverter._GetExprString(item.value)
if ExprStr:
result.append(f'{InnerIndent}{ExprStr}')
return '\n'.join(result)
@@ -897,28 +906,28 @@ class PythonToStubConverter:
@staticmethod
def _ProcessIfMacro(node: ast.If, indent: int = 0) -> str:
"""处理 if 宏节点,支持 elif 和 else"""
IndentStr = ' ' * indent
IndentStr: str = ' ' * indent
# 检查条件是否是宏调用
if isinstance(node.test, ast.Call):
FuncStr = PythonToStubConverter._GetDecoratorString(node.test.func)
FuncStr: str = PythonToStubConverter._GetDecoratorString(node.test.func)
if FuncStr and 'CIf' in FuncStr:
ArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args])
result = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():']
ArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in node.test.args])
result: list[str] = [f'{IndentStr}if {FuncStr}({ArgsStr}):' if ArgsStr else f'{IndentStr}if {FuncStr}():']
# 处理 if 体内的语句
for item in node.body:
result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1))
# 处理 elif 和 else
current = node
current: ast.If = node
while current.orelse:
if len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If):
# elif 情况
elif_node = current.orelse[0]
elif_node: ast.If = current.orelse[0]
if isinstance(elif_node.test, ast.Call):
ElifFuncStr = PythonToStubConverter._GetDecoratorString(elif_node.test.func)
ElifFuncStr: str = PythonToStubConverter._GetDecoratorString(elif_node.test.func)
if ElifFuncStr and 'CIf' in ElifFuncStr:
ElifArgsStr = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in elif_node.test.args])
ElifArgsStr: str = ', '.join([PythonToStubConverter._GetTypeString(arg) for arg in elif_node.test.args])
result.append(f'{IndentStr}elif {ElifFuncStr}({ElifArgsStr}):' if ElifArgsStr else f'{IndentStr}elif {ElifFuncStr}():')
for item in elif_node.body:
result.extend(PythonToStubConverter._ProcessMacroBodyItem(item, indent + 1))
@@ -936,32 +945,32 @@ class PythonToStubConverter:
return ''
@staticmethod
def _ProcessMacroBodyItem(item: ast.AST, indent: int = 0) -> list:
def _ProcessMacroBodyItem(item: ast.AST, indent: int = 0) -> list[str]:
"""处理宏体内的单个语句"""
IndentStr = ' ' * indent
result = []
IndentStr: str = ' ' * indent
result: list[str] = []
if isinstance(item, ast.ClassDef):
class_stub = PythonToStubConverter._ConvertClass(item)
class_stub: list[str] = PythonToStubConverter._ConvertClass(item)
for line in class_stub:
if line.strip():
result.append(f'{IndentStr}{line}')
else:
result.append(line)
elif isinstance(item, ast.FunctionDef):
FuncStub = PythonToStubConverter._ConvertFunction(item, indent=indent)
FuncStub: list[str] = PythonToStubConverter._ConvertFunction(item, indent=indent)
result.extend(FuncStub)
elif isinstance(item, ast.AnnAssign):
TypeStr = PythonToStubConverter._GetTypeString(item.annotation)
TypeStr: str = PythonToStubConverter._GetTypeString(item.annotation)
result.append(f'{IndentStr}{item.target.id}: {TypeStr}')
elif isinstance(item, ast.Expr):
ExprStr = PythonToStubConverter._GetExprString(item.value)
ExprStr: str = PythonToStubConverter._GetExprString(item.value)
if ExprStr:
result.append(f'{IndentStr}{ExprStr}')
elif isinstance(item, ast.If):
# 处理嵌套的 if 宏
NestedIf = PythonToStubConverter._ProcessIfMacro(item, indent=indent)
NestedIf: str = PythonToStubConverter._ProcessIfMacro(item, indent=indent)
if NestedIf:
result.append(NestedIf)
return result
return result

View File

@@ -1,12 +1,15 @@
"""StubGen 生成器模块 - 存根生成器主类和命令行入口"""
from __future__ import annotations
import sys
import os
import re
import argparse
import logging
import traceback
from pathlib import Path
from typing import List, Tuple
from lib.core.VLogger import get_logger as _vlog
from lib.core.stub_generator import CHeaderParser, PythonStubGenerator, CTypeMapper
from lib.StubGen.Config import StubGenConfig
from lib.StubGen.Converter import PythonToStubConverter
@@ -15,9 +18,9 @@ from lib.StubGen.Converter import PythonToStubConverter
class StubGen:
"""存根生成器主类"""
def __init__(self, config: StubGenConfig):
self.config = config
self.logger = self._SetupLogger()
def __init__(self, config: StubGenConfig) -> None:
self.config: StubGenConfig = config
self.logger: logging.Logger = self._SetupLogger()
self.GeneratedFiles: List[str] = []
self.FailedFiles: List[str] = []
@@ -27,12 +30,12 @@ class StubGen:
def _SetupLogger(self) -> logging.Logger:
"""设置日志"""
logger = logging.getLogger('StubGen')
logger: logging.Logger = logging.getLogger('StubGen')
logger.setLevel(logging.DEBUG if self.config.verbose else logging.INFO)
if not logger.handlers:
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
handler: logging.StreamHandler = logging.StreamHandler(sys.stdout)
formatter: logging.Formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%H:%M:%S'
)
@@ -41,26 +44,26 @@ class StubGen:
return logger
def FindInputFiles(self) -> List[Tuple[str, str]]:
def FindInputFiles(self) -> list[tuple[str, str]]:
"""查找输入文件,返回 (文件路径, 相对路径) 列表"""
files = []
files: list[tuple[str, str]] = []
# 添加显式指定的文件
for FilePath in self.config.InputFiles:
if os.path.exists(FilePath):
RelPath = os.path.relpath(FilePath, self.config.InputDir or '.')
RelPath: str = os.path.relpath(FilePath, self.config.InputDir or '.')
files.append((FilePath, RelPath))
else:
self.logger.warning(f"Input file not found: {FilePath}")
# 从输入目录查找
if self.config.InputDir and os.path.exists(self.config.InputDir):
InputPath = Path(self.config.InputDir)
InputPath: Path = Path(self.config.InputDir)
for pattern in self.config.IncludePatterns:
for FilePath in InputPath.rglob(pattern):
# 检查是否在排除列表中
excluded = False
RelPath = os.path.relpath(str(FilePath), self.config.InputDir)
excluded: bool = False
RelPath: str = os.path.relpath(str(FilePath), self.config.InputDir)
for exclude_pattern in self.config.ExcludePatterns:
if FilePath.match(exclude_pattern) or RelPath == exclude_pattern:
@@ -71,10 +74,10 @@ class StubGen:
files.append((str(FilePath), RelPath))
# 去重并保持顺序
seen = set()
UniqueFiles = []
seen: set[str] = set()
UniqueFiles: list[tuple[str, str]] = []
for FilePath, RelPath in files:
key = FilePath
key: str = FilePath
if key not in seen:
seen.add(key)
UniqueFiles.append((FilePath, RelPath))
@@ -84,10 +87,10 @@ class StubGen:
def _GenerateGuardName(self, FilePath: str) -> str:
"""生成宏守卫名称"""
# 获取文件名(不含扩展名)
BaseName = os.path.splitext(os.path.basename(FilePath))[0]
BaseName: str = os.path.splitext(os.path.basename(FilePath))[0]
# 转换为大写,替换特殊字符为下划线
guard = re.sub(r'[^a-zA-Z0-9_]', '_', BaseName).upper()
guard: str = re.sub(r'[^a-zA-Z0-9_]', '_', BaseName).upper()
guard = re.sub(r'_+', '_', guard) # 合并多个下划线
guard = guard.strip('_')
@@ -96,12 +99,12 @@ class StubGen:
def _GetOutputPath(self, RelPath: str) -> str:
"""获取输出文件路径"""
# 更改扩展名为 .pyi (Python stub file)
BaseName = os.path.splitext(RelPath)[0]
OutputRelPath = BaseName + '.pyi'
BaseName: str = os.path.splitext(RelPath)[0]
OutputRelPath: str = BaseName + '.pyi'
# 构建完整输出路径
if self.config.OutputDir:
OutputPath = os.path.join(self.config.OutputDir, OutputRelPath)
OutputPath: str = os.path.join(self.config.OutputDir, OutputRelPath)
else:
OutputPath = OutputRelPath
@@ -113,10 +116,10 @@ class StubGen:
self.logger.info(f"Processing: {InputFile}")
# 确定输出文件路径
OutputFile = self._GetOutputPath(RelPath)
OutputFile: str = self._GetOutputPath(RelPath)
# 确保输出目录存在
OutputDir = os.path.dirname(OutputFile)
OutputDir: str = os.path.dirname(OutputFile)
if OutputDir and not os.path.exists(OutputDir):
if not self.config.DryRun:
os.makedirs(OutputDir, exist_ok=True)
@@ -127,11 +130,11 @@ class StubGen:
return True
# 根据文件类型选择处理方式
ext = os.path.splitext(InputFile)[1].lower()
ext: str = os.path.splitext(InputFile)[1].lower()
if ext in ['.h', '.c']:
# C 文件:使用 CHeaderParser
content = self._GenerateFromC(InputFile, RelPath)
content: str = self._GenerateFromC(InputFile, RelPath)
elif ext == '.py':
# Python 文件:使用 PythonToStubConverter
content = self._GenerateFromPy(InputFile, RelPath)
@@ -149,24 +152,23 @@ class StubGen:
except Exception as e:
self.logger.error(f"Failed to process {InputFile}: {e}")
import traceback
traceback.print_exc()
self.FailedFiles.append(InputFile)
return False
def _GenerateFromC(self, InputFile: str, RelPath: str) -> str:
"""从 C 文件生成存根"""
parser = CHeaderParser()
parser: CHeaderParser = CHeaderParser()
parser.parse_file(InputFile)
generator = PythonStubGenerator(parser)
ModuleName = os.path.splitext(os.path.basename(InputFile))[0]
content = generator.generate(ModuleName)
generator: PythonStubGenerator = PythonStubGenerator(parser)
ModuleName: str = os.path.splitext(os.path.basename(InputFile))[0]
content: str = generator.generate(ModuleName)
# 添加宏守卫(如果需要)
if self.config.GenerateGuards:
guard_name = self._GenerateGuardName(InputFile)
guard_comment = f"\n# Guard: {guard_name}\n"
guard_name: str = self._GenerateGuardName(InputFile)
guard_comment: str = f"\n# Guard: {guard_name}\n"
content = content + guard_comment
return content
@@ -174,15 +176,15 @@ class StubGen:
def _GenerateFromPy(self, InputFile: str, RelPath: str) -> str:
"""从 Python 文件生成存根"""
with open(InputFile, 'r', encoding='utf-8') as f:
PyContent = f.read()
PyContent: str = f.read()
ModuleName = os.path.splitext(os.path.basename(InputFile))[0]
content = PythonToStubConverter.convert(PyContent, ModuleName)
ModuleName: str = os.path.splitext(os.path.basename(InputFile))[0]
content: str = PythonToStubConverter.convert(PyContent, ModuleName)
# 添加宏守卫(如果需要)
if self.config.GenerateGuards:
guard_name = self._GenerateGuardName(InputFile)
guard_comment = f"\n# Guard: {guard_name}\n"
guard_name: str = self._GenerateGuardName(InputFile)
guard_comment: str = f"\n# Guard: {guard_name}\n"
content = content + guard_comment
return content
@@ -194,7 +196,7 @@ class StubGen:
self.logger.info("=" * 60)
# 查找输入文件
InputFiles = self.FindInputFiles()
InputFiles: list[tuple[str, str]] = self.FindInputFiles()
if not InputFiles:
self.logger.warning("No input files found!")
@@ -205,7 +207,7 @@ class StubGen:
self.logger.debug(f" - {RelPath}")
# 处理每个文件
SuccessCount = 0
SuccessCount: int = 0
for FilePath, RelPath in InputFiles:
if self.GenerateStub(FilePath, RelPath):
SuccessCount += 1
@@ -222,8 +224,8 @@ class StubGen:
return SuccessCount == len(InputFiles)
def main():
parser = argparse.ArgumentParser(
def main() -> None:
parser: argparse.ArgumentParser = argparse.ArgumentParser(
description='StubGen - Generate Python stub files from C/H/Py sources',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
@@ -246,7 +248,7 @@ Examples:
)
# 输入选项
InputGroup = parser.add_mutually_exclusive_group(required=True)
InputGroup: argparse._MutuallyExclusiveGroup = parser.add_mutually_exclusive_group(required=True)
InputGroup.add_argument('-c', '--config', help='Configuration file (JSON)')
InputGroup.add_argument('-i', '--input', help='Input file')
InputGroup.add_argument('-d', '--directory', help='Input directory')
@@ -268,14 +270,14 @@ Examples:
parser.add_argument('--dry-run', action='store_true',
help='Dry run (do not create files)')
args = parser.parse_args()
args: argparse.Namespace = parser.parse_args()
# 加载配置
if args.config:
if not os.path.exists(args.config):
print(f"Error: Config file not found: {args.config}")
_vlog().error(f"配置文件未找到: {args.config}")
sys.exit(1)
config = StubGenConfig.from_file(args.config)
config: StubGenConfig = StubGenConfig.from_file(args.config)
else:
config = StubGenConfig()
config.IncludePatterns = args.include or ['*.h', '*.c', '*.py']
@@ -295,12 +297,12 @@ Examples:
config.OutputDir = args.output
# 创建生成器并运行
generator = StubGen(config)
generator: StubGen = StubGen(config)
# 如果指定了单个输出文件,直接处理
if args.input and args.output and not os.path.isdir(args.output):
RelPath = os.path.relpath(args.input, config.InputDir or '.')
success = generator.GenerateStub(args.input, RelPath)
RelPath: str = os.path.relpath(args.input, config.InputDir or '.')
success: bool = generator.GenerateStub(args.input, RelPath)
else:
success = generator.run()
@@ -308,4 +310,4 @@ Examples:
if __name__ == '__main__':
main()
main()