mirror of
https://github.com/snowykami/mbcp.git
synced 2026-01-25 05:02:15 +00:00
📝 测试文档部署
This commit is contained in:
198
liteyuki_autodoc/syntax/astparser.py
Normal file
198
liteyuki_autodoc/syntax/astparser.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午2:13
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : astparser.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import ast
|
||||
|
||||
from .node import *
|
||||
from ..docstring.parser import parse
|
||||
|
||||
|
||||
class AstParser:
|
||||
def __init__(self, code: str):
|
||||
self.code = code
|
||||
self.tree = ast.parse(code)
|
||||
|
||||
self.classes: list[ClassNode] = []
|
||||
self.functions: list[FunctionNode] = []
|
||||
self.variables: list[AssignNode] = []
|
||||
|
||||
self.parse()
|
||||
|
||||
def parse(self):
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
if not self._is_module_level_class(node):
|
||||
continue
|
||||
|
||||
class_node = ClassNode(
|
||||
name=node.name,
|
||||
docs=parse(ast.get_docstring(node)) if ast.get_docstring(node) else None,
|
||||
inherits=[ast.unparse(base) for base in node.bases]
|
||||
)
|
||||
self.classes.append(class_node)
|
||||
|
||||
# 继续遍历类内部的函数
|
||||
for sub_node in node.body:
|
||||
if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
class_node.methods.append(FunctionNode(
|
||||
name=sub_node.name,
|
||||
docs=parse(ast.get_docstring(sub_node)) if ast.get_docstring(sub_node) else None,
|
||||
posonlyargs=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg in sub_node.args.posonlyargs
|
||||
],
|
||||
args=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg in sub_node.args.args
|
||||
],
|
||||
kwonlyargs=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg in sub_node.args.kwonlyargs
|
||||
],
|
||||
kw_defaults=[
|
||||
ConstantNode(
|
||||
value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT
|
||||
)
|
||||
for default in sub_node.args.kw_defaults
|
||||
],
|
||||
defaults=[
|
||||
ConstantNode(
|
||||
value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT
|
||||
)
|
||||
for default in sub_node.args.defaults
|
||||
],
|
||||
return_=ast.unparse(sub_node.returns).strip() if sub_node.returns else TypeHint.NO_RETURN,
|
||||
decorators=[ast.unparse(decorator).strip() for decorator in sub_node.decorator_list],
|
||||
is_async=isinstance(sub_node, ast.AsyncFunctionDef),
|
||||
src=ast.unparse(sub_node).strip()
|
||||
))
|
||||
elif isinstance(sub_node, (ast.Assign, ast.AnnAssign)):
|
||||
if isinstance(sub_node, ast.Assign):
|
||||
class_node.attrs.append(AttrNode(
|
||||
name=sub_node.targets[0].id, # type: ignore
|
||||
type=TypeHint.NO_TYPEHINT,
|
||||
value=ast.unparse(sub_node.value).strip()
|
||||
))
|
||||
elif isinstance(sub_node, ast.AnnAssign):
|
||||
class_node.attrs.append(AttrNode(
|
||||
name=sub_node.target.id,
|
||||
type=ast.unparse(sub_node.annotation).strip(),
|
||||
value=ast.unparse(sub_node.value).strip() if sub_node.value else TypeHint.NO_DEFAULT
|
||||
))
|
||||
else:
|
||||
raise ValueError(f"Unsupported node type: {type(sub_node)}")
|
||||
|
||||
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
# 仅打印模块级别的函数
|
||||
if not self._is_module_level_function(node):
|
||||
continue
|
||||
|
||||
self.functions.append(FunctionNode(
|
||||
name=node.name,
|
||||
docs=parse(ast.get_docstring(node)) if ast.get_docstring(node) else None,
|
||||
posonlyargs=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg in node.args.posonlyargs
|
||||
],
|
||||
args=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg, default in zip(node.args.args, node.args.defaults)
|
||||
],
|
||||
kwonlyargs=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg in node.args.kwonlyargs
|
||||
],
|
||||
kw_defaults=[
|
||||
ConstantNode(
|
||||
value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT
|
||||
)
|
||||
for default in node.args.kw_defaults
|
||||
],
|
||||
defaults=[
|
||||
ConstantNode(
|
||||
value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT
|
||||
)
|
||||
for default in node.args.defaults
|
||||
],
|
||||
return_=ast.unparse(node.returns).strip() if node.returns else TypeHint.NO_TYPEHINT,
|
||||
decorators=[ast.unparse(decorator).strip() for decorator in node.decorator_list],
|
||||
is_async=isinstance(node, ast.AsyncFunctionDef),
|
||||
src=ast.unparse(node).strip()
|
||||
))
|
||||
|
||||
elif isinstance(node, (ast.Assign, ast.AnnAssign)):
|
||||
if not self._is_module_level_variable(node):
|
||||
# print("变量不在模块级别", ast.unparse(node))
|
||||
continue
|
||||
else:
|
||||
print("变量在模块级别", ast.unparse(node))
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
self.variables.append(AssignNode(
|
||||
name=target.id,
|
||||
value=ast.unparse(node.value).strip(),
|
||||
type=ast.unparse(node.annotation).strip() if isinstance(node, ast.AnnAssign) else TypeHint.NO_TYPEHINT
|
||||
))
|
||||
elif isinstance(node, ast.AnnAssign):
|
||||
self.variables.append(AssignNode(
|
||||
name=node.target.id,
|
||||
value=ast.unparse(node.value).strip() if node.value else TypeHint.NO_DEFAULT,
|
||||
type=ast.unparse(node.annotation).strip()
|
||||
))
|
||||
|
||||
def _is_module_level_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef):
|
||||
for parent in ast.walk(self.tree):
|
||||
if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
if node in parent.body:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_module_level_class(self, node: ast.ClassDef):
|
||||
for parent in ast.walk(self.tree):
|
||||
if isinstance(parent, ast.ClassDef):
|
||||
if node in parent.body:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_module_level_variable(self, node: ast.Assign):
|
||||
for parent in ast.walk(self.tree):
|
||||
if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
if node in parent.body:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
s = ""
|
||||
for cls in self.classes:
|
||||
s += f"class {cls.name}:\n"
|
||||
for func in self.functions:
|
||||
s += f"def {func.name}:\n"
|
||||
for var in self.variables:
|
||||
s += f"{var.name} = {var.value}\n"
|
||||
return s
|
||||
258
liteyuki_autodoc/syntax/node.py
Normal file
258
liteyuki_autodoc/syntax/node.py
Normal file
@@ -0,0 +1,258 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午2:14
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : node.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Literal, Optional
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from liteyuki_autodoc.docstring.docstring import Docstring
|
||||
from liteyuki_autodoc.i18n import get_text
|
||||
|
||||
|
||||
class TypeHint:
|
||||
NO_TYPEHINT = "NO_TYPE_HINT"
|
||||
NO_DEFAULT = "NO_DEFAULT"
|
||||
NO_RETURN = "NO_RETURN"
|
||||
|
||||
|
||||
class AssignNode(BaseModel):
|
||||
"""
|
||||
AssignNode is a pydantic model that represents an assignment.
|
||||
Attributes:
|
||||
name: str
|
||||
The name of the assignment.
|
||||
type: str = ""
|
||||
The type of the assignment.
|
||||
value: str
|
||||
The value of the assignment.
|
||||
"""
|
||||
name: str
|
||||
type: str = ""
|
||||
value: str
|
||||
|
||||
|
||||
class ArgNode(BaseModel):
|
||||
"""
|
||||
ArgNode is a pydantic model that represents an argument.
|
||||
Attributes:
|
||||
name: str
|
||||
The name of the argument.
|
||||
type: str = ""
|
||||
The type of the argument.
|
||||
default: str = ""
|
||||
The default value of the argument.
|
||||
"""
|
||||
name: str
|
||||
type: str = TypeHint.NO_TYPEHINT
|
||||
|
||||
|
||||
class AttrNode(BaseModel):
|
||||
"""
|
||||
AttrNode is a pydantic model that represents an attribute.
|
||||
Attributes:
|
||||
name: str
|
||||
The name of the attribute.
|
||||
type: str = ""
|
||||
The type of the attribute.
|
||||
value: str = ""
|
||||
The value of the attribute
|
||||
"""
|
||||
name: str
|
||||
type: str = ""
|
||||
value: str = ""
|
||||
|
||||
|
||||
class ImportNode(BaseModel):
|
||||
"""
|
||||
ImportNode is a pydantic model that represents an import statement.
|
||||
Attributes:
|
||||
name: str
|
||||
The name of the import statement.
|
||||
as_: str = ""
|
||||
The alias of the import
|
||||
"""
|
||||
name: str
|
||||
as_: str = ""
|
||||
|
||||
|
||||
class ConstantNode(BaseModel):
|
||||
"""
|
||||
ConstantNode is a pydantic model that represents a constant.
|
||||
Attributes:
|
||||
value: str
|
||||
The value of the constant.
|
||||
"""
|
||||
value: str
|
||||
|
||||
|
||||
class FunctionNode(BaseModel):
|
||||
"""
|
||||
FunctionNode is a pydantic model that represents a function.
|
||||
Attributes:
|
||||
name: str
|
||||
The name of the function.
|
||||
docs: str = ""
|
||||
The docstring of the function.
|
||||
args: list[ArgNode] = []
|
||||
The arguments of the function.
|
||||
return_: ReturnNode = None
|
||||
The return value of the function.
|
||||
decorators: list[str] = []
|
||||
The decorators of the function.
|
||||
is_async: bool = False
|
||||
Whether the function is asynchronous.
|
||||
"""
|
||||
name: str
|
||||
docs: Optional[Docstring] = None
|
||||
|
||||
posonlyargs: list[ArgNode] = []
|
||||
args: list[ArgNode] = []
|
||||
kwonlyargs: list[ArgNode] = []
|
||||
kw_defaults: list[ConstantNode] = []
|
||||
defaults: list[ConstantNode] = []
|
||||
|
||||
return_: str = Field(TypeHint.NO_RETURN, alias="return")
|
||||
decorators: list[str] = []
|
||||
src: str
|
||||
is_async: bool = False
|
||||
|
||||
def is_private(self):
|
||||
"""
|
||||
Check if the function or method is private.
|
||||
Returns:
|
||||
bool: True if the function or method is private, False otherwise.
|
||||
"""
|
||||
return self.name.startswith("_")
|
||||
|
||||
def is_builtin(self):
|
||||
"""
|
||||
Check if the function or method is a builtin function or method.
|
||||
Returns:
|
||||
bool: True if the function or method is a builtin function or method, False otherwise.
|
||||
"""
|
||||
return self.name.startswith("__") and self.name.endswith("__")
|
||||
|
||||
def markdown(self, lang: str, indent: int = 0, is_classmethod: bool = False) -> str:
|
||||
"""
|
||||
Args:
|
||||
indent: int
|
||||
The number of spaces to indent the markdown.
|
||||
is_classmethod: bool
|
||||
lang: str
|
||||
The language of the
|
||||
Returns:
|
||||
markdown style document
|
||||
"""
|
||||
self.complete_default_args()
|
||||
PREFIX = "" * indent
|
||||
if is_classmethod:
|
||||
PREFIX = "- #"
|
||||
|
||||
md = ""
|
||||
|
||||
# 装饰器部分
|
||||
if len(self.decorators) > 0:
|
||||
for decorator in self.decorators:
|
||||
md += PREFIX + f"### `@{decorator}`\n"
|
||||
|
||||
if self.is_async:
|
||||
md += PREFIX + "### *async def* "
|
||||
else:
|
||||
md += PREFIX + "### *def* "
|
||||
|
||||
md += f"`{self.name}(" # code start
|
||||
|
||||
# 配对位置参数和位置参数默认值,无默认值用TypeHint.NO_DEFAULT
|
||||
args: list[str] = [] # 可直接", ".join(args)得到位置参数部分
|
||||
arg_i = 0
|
||||
|
||||
if len(self.posonlyargs) > 0:
|
||||
for arg in self.posonlyargs:
|
||||
arg_text = f"{arg.name}"
|
||||
if arg.type != TypeHint.NO_TYPEHINT:
|
||||
arg_text += f": {arg.type}"
|
||||
arg_default = self.defaults[arg_i].value
|
||||
if arg_default != TypeHint.NO_DEFAULT:
|
||||
arg_text += f" = {arg_default}"
|
||||
args.append(arg_text)
|
||||
arg_i += 1
|
||||
# 加位置参数分割符 /
|
||||
args.append("/")
|
||||
|
||||
for arg in self.args:
|
||||
arg_text = f"{arg.name}"
|
||||
if arg.type != TypeHint.NO_TYPEHINT:
|
||||
arg_text += f": {arg.type}"
|
||||
arg_default = self.defaults[arg_i].value
|
||||
if arg_default != TypeHint.NO_DEFAULT:
|
||||
arg_text += f" = {arg_default}"
|
||||
args.append(arg_text)
|
||||
arg_i += 1
|
||||
|
||||
if len(self.kwonlyargs) > 0:
|
||||
# 加关键字参数分割符 *
|
||||
args.append("*")
|
||||
for arg, kw_default in zip(self.kwonlyargs, self.kw_defaults):
|
||||
arg_text = f"{arg.name}"
|
||||
if arg.type != TypeHint.NO_TYPEHINT:
|
||||
arg_text += f": {arg.type}"
|
||||
|
||||
if kw_default.value != TypeHint.NO_DEFAULT:
|
||||
arg_text += f" = {kw_default.value}"
|
||||
args.append(arg_text)
|
||||
|
||||
md += ", ".join(args) + ")"
|
||||
|
||||
md += "`\n\n" # code end
|
||||
|
||||
"""此处预留docstring"""
|
||||
if self.docs is not None:
|
||||
md += f"\n{self.docs.markdown(lang, indent)}\n"
|
||||
else:
|
||||
pass
|
||||
# 源码展示
|
||||
md += PREFIX + f"\n<details>\n<summary>{get_text(lang, 'src')}</summary>\n\n```python\n{self.src}\n```\n</details>\n\n"
|
||||
|
||||
return md
|
||||
|
||||
def complete_default_args(self):
|
||||
"""
|
||||
补全位置参数默认值,用无默认值插入
|
||||
Returns:
|
||||
|
||||
"""
|
||||
num = len(self.args) + len(self.posonlyargs) - len(self.defaults)
|
||||
self.defaults = [ConstantNode(value=TypeHint.NO_DEFAULT) for _ in range(num)] + self.defaults
|
||||
|
||||
def __str__(self):
|
||||
return f"def {self.name}({', '.join([f'{arg.name}: {arg.type} = {arg.default}' for arg in self.args])}) -> {self.return_}"
|
||||
|
||||
|
||||
class ClassNode(BaseModel):
|
||||
"""
|
||||
ClassNode is a pydantic model that represents a class.
|
||||
Attributes:
|
||||
name: str
|
||||
The name of the class.
|
||||
docs: str = ""
|
||||
The docstring of the class.
|
||||
attrs: list[AttrNode] = []
|
||||
The attributes of the class.
|
||||
methods: list[MethodNode] = []
|
||||
The methods of the class.
|
||||
inherit: list["ClassNode"] = []
|
||||
The classes that the class inherits from
|
||||
"""
|
||||
name: str
|
||||
docs: Optional[Docstring] = None
|
||||
attrs: list[AttrNode] = []
|
||||
methods: list[FunctionNode] = []
|
||||
inherit: list["ClassNode"] = []
|
||||
Reference in New Issue
Block a user