forked from bot/app
315 lines
9.2 KiB
Python
315 lines
9.2 KiB
Python
|
# -*- 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 litedoc.docstring.docstring import Docstring
|
|||
|
from litedoc.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
|
|||
|
docs: Optional[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 = TypeHint.NO_RETURN
|
|||
|
decorators: list[str] = []
|
|||
|
src: str
|
|||
|
is_async: bool = False
|
|||
|
is_classmethod: bool = False
|
|||
|
|
|||
|
magic_methods: dict[str, str] = {
|
|||
|
"__add__" : "+",
|
|||
|
"__radd__" : "+",
|
|||
|
"__sub__" : "-",
|
|||
|
"__rsub__" : "-",
|
|||
|
"__mul__" : "*",
|
|||
|
"__rmul__" : "*",
|
|||
|
"__matmul__" : "@",
|
|||
|
"__rmatmul__": "@",
|
|||
|
"__mod__" : "%",
|
|||
|
"__truediv__": "/",
|
|||
|
"__rtruediv__": "/",
|
|||
|
"__neg__" : "-",
|
|||
|
} # 魔术方法, 例如运算符
|
|||
|
|
|||
|
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) -> str:
|
|||
|
"""
|
|||
|
Args:
|
|||
|
indent: int
|
|||
|
The number of spaces to indent the markdown.
|
|||
|
lang: str
|
|||
|
The language of the
|
|||
|
Returns:
|
|||
|
markdown style document
|
|||
|
"""
|
|||
|
self.complete_default_args()
|
|||
|
PREFIX = "" * indent
|
|||
|
# if is_classmethod:
|
|||
|
# PREFIX = "- #"
|
|||
|
func_type = "func" if not self.is_classmethod else "method"
|
|||
|
|
|||
|
md = ""
|
|||
|
# 装饰器部分
|
|||
|
if len(self.decorators) > 0:
|
|||
|
for decorator in self.decorators:
|
|||
|
md += PREFIX + f"### `@{decorator}`\n"
|
|||
|
|
|||
|
if self.is_async:
|
|||
|
md += PREFIX + f"### *async {func_type}* "
|
|||
|
else:
|
|||
|
md += PREFIX + f"### *{func_type}* "
|
|||
|
|
|||
|
# 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)
|
|||
|
|
|||
|
"""魔法方法"""
|
|||
|
if self.name in self.magic_methods:
|
|||
|
if len(args) == 2:
|
|||
|
md += f"`{args[0]} {self.magic_methods[self.name]} {args[1]}"
|
|||
|
elif len(args) == 1:
|
|||
|
md += f"`{self.magic_methods[self.name]} {args[0]}"
|
|||
|
if self.return_ != TypeHint.NO_RETURN:
|
|||
|
md += f" => {self.return_}"
|
|||
|
else:
|
|||
|
md += f"`{self.name}(" # code start
|
|||
|
md += ", ".join(args) + ")"
|
|||
|
if self.return_ != TypeHint.NO_RETURN:
|
|||
|
md += f" -> {self.return_}"
|
|||
|
|
|||
|
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> <b>{get_text(lang, 'src')}</b> </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.
|
|||
|
inherits: list["ClassNode"] = []
|
|||
|
The classes that the class inherits from
|
|||
|
"""
|
|||
|
name: str
|
|||
|
docs: Optional[Docstring] = None
|
|||
|
attrs: list[AttrNode] = []
|
|||
|
methods: list[FunctionNode] = []
|
|||
|
inherits: list[str] = []
|
|||
|
|
|||
|
def markdown(self, lang: str) -> str:
|
|||
|
"""
|
|||
|
返回类的markdown文档
|
|||
|
Args:
|
|||
|
lang: str
|
|||
|
The language of the
|
|||
|
Returns:
|
|||
|
markdown style document
|
|||
|
"""
|
|||
|
hidden_methods = [
|
|||
|
"__str__",
|
|||
|
"__repr__",
|
|||
|
]
|
|||
|
md = ""
|
|||
|
md += f"### **class** `{self.name}"
|
|||
|
if len(self.inherits) > 0:
|
|||
|
md += f"({', '.join([cls for cls in self.inherits])})"
|
|||
|
md += "`\n"
|
|||
|
for method in self.methods:
|
|||
|
if method.name in hidden_methods:
|
|||
|
continue
|
|||
|
md += method.markdown(lang, 2)
|
|||
|
for attr in self.attrs:
|
|||
|
if attr.type == TypeHint.NO_TYPEHINT:
|
|||
|
md += f"#### ***attr*** `{attr.name} = {attr.value}`\n\n"
|
|||
|
else:
|
|||
|
md += f"#### ***attr*** `{attr.name}: {attr.type} = {attr.value}`\n\n"
|
|||
|
|
|||
|
return md
|