mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-11 04:07:23 +08:00
⬇️ 更新文档样式
This commit is contained in:
parent
50c0216435
commit
be5a4b270d
3
.github/workflows/deploy-docs.yml
vendored
3
.github/workflows/deploy-docs.yml
vendored
@ -20,7 +20,6 @@ jobs:
|
||||
fetch-depth: 0
|
||||
# 如果你文档需要 Git 子模块,取消注释下一行
|
||||
# submodules: true
|
||||
|
||||
- name: 安装 pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
@ -41,7 +40,7 @@ jobs:
|
||||
|
||||
- name: 生成API markdown
|
||||
run: |-
|
||||
python -m pip install pydantic
|
||||
python -m pip install litedoc
|
||||
python -m litedoc liteyuki -o docs/dev/api -l zh-Hans -t vuepress
|
||||
python -m litedoc liteyuki -o docs/en/dev/api -l en -t vuepress
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午12:52
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py.py
|
||||
@Software: PyCharm
|
||||
"""
|
@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午4:08
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __main__.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
# command line tool
|
||||
# args[0] path
|
||||
# -o|--output output path
|
||||
# -l|--lang zh-Hans en jp default zh-Hans
|
||||
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from litedoc.output import generate_from_module
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate documentation from Python modules.")
|
||||
parser.add_argument("path", type=str, help="Path to the Python module or package.")
|
||||
parser.add_argument("-o", "--output", default="doc-output", type=str, help="Output directory.")
|
||||
parser.add_argument("-c", "--contain-top", action="store_true", help="Whether to contain top-level dir in output dir.")
|
||||
parser.add_argument("-l", "--lang", default="zh_Hans", type=str, help="Languages of the document.")
|
||||
parser.add_argument("-t", "--theme", default="vitepress", type=str, help="Theme of the document.")
|
||||
parser.add_argument("-s", "--style", default="google", type=str, help="Style of the document.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.path):
|
||||
print(f"Error: The path {args.path} does not exist.")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(args.output):
|
||||
os.makedirs(args.output)
|
||||
|
||||
lang = args.lang
|
||||
|
||||
generate_from_module(args.path, args.output, with_top=args.contain_top, lang=lang, theme=args.theme, style=args.style)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午1:46
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py.py
|
||||
@Software: PyCharm
|
||||
"""
|
@ -1,157 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午1:46
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : docstring.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from litedoc.i18n import get_text
|
||||
|
||||
|
||||
class Attr(BaseModel):
|
||||
name: str
|
||||
type: str = ""
|
||||
desc: str = ""
|
||||
|
||||
|
||||
class Args(BaseModel):
|
||||
name: str
|
||||
type: str = ""
|
||||
desc: str = ""
|
||||
|
||||
|
||||
class Return(BaseModel):
|
||||
desc: str = ""
|
||||
|
||||
|
||||
class Exception_(BaseModel):
|
||||
name: str
|
||||
desc: str = ""
|
||||
|
||||
|
||||
class Raise(BaseModel):
|
||||
exceptions: list[Exception_] = []
|
||||
|
||||
|
||||
class Example(BaseModel):
|
||||
desc: str = ""
|
||||
input: str = ""
|
||||
output: str = ""
|
||||
|
||||
|
||||
class Docstring(BaseModel):
|
||||
desc: str = ""
|
||||
args: list[Args] = []
|
||||
attrs: list[Attr] = []
|
||||
return_: Optional[Return] = None
|
||||
raise_: list[Exception_] = []
|
||||
example: list[Example] = []
|
||||
|
||||
def add_desc(self, desc: str):
|
||||
if self.desc == "":
|
||||
self.desc = desc
|
||||
else:
|
||||
self.desc += "\n" + desc
|
||||
|
||||
def add_arg(self, name: str, type_: str = "", desc: str = ""):
|
||||
self.args.append(Args(name=name, type=type_, desc=desc))
|
||||
|
||||
def add_attrs(self, name: str, type_: str = "", desc: str = ""):
|
||||
self.attrs.append(Attr(name=name, type=type_, desc=desc))
|
||||
|
||||
def add_return(self, desc: str = ""):
|
||||
self.return_ = Return(desc=desc)
|
||||
|
||||
def add_raise(self, name: str, desc: str = ""):
|
||||
self.raise_.append(Exception_(name=name, desc=desc))
|
||||
|
||||
def add_example(self, desc: str = "", input_: str = "", output: str = ""):
|
||||
self.example.append(Example(desc=desc, input=input_, output=output))
|
||||
|
||||
def reduction(self) -> str:
|
||||
"""
|
||||
通过解析结果还原docstring
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
ret = ""
|
||||
ret += self.desc + "\n"
|
||||
if self.args:
|
||||
ret += "Args:\n"
|
||||
for arg in self.args:
|
||||
ret += f" {arg.name}: {arg.type}\n {arg.desc}\n"
|
||||
if self.attrs:
|
||||
ret += "Attributes:\n"
|
||||
for attr in self.attrs:
|
||||
ret += f" {attr.name}: {attr.type}\n {attr.desc}\n"
|
||||
if self.return_:
|
||||
ret += "Returns:\n"
|
||||
ret += f" {self.return_.desc}\n"
|
||||
|
||||
if self.raise_:
|
||||
ret += "Raises:\n"
|
||||
for exception in self.raise_:
|
||||
ret += f" {exception.name}\n {exception.desc}\n"
|
||||
|
||||
if self.example:
|
||||
ret += "Examples:\n"
|
||||
for example in self.example:
|
||||
ret += f" {example.desc}\n Input: {example.input}\n Output: {example.output}\n"
|
||||
|
||||
return ret
|
||||
|
||||
def markdown(self, lang: str, indent: int = 4, is_classmethod: bool = False) -> str:
|
||||
"""
|
||||
生成markdown文档
|
||||
Args:
|
||||
is_classmethod:
|
||||
lang:
|
||||
indent:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
PREFIX = "" * indent
|
||||
ret = ""
|
||||
# ret += self.desc + "\n\n"
|
||||
# print(self.reduction())
|
||||
# print(self.desc, self.return_)
|
||||
# 单数属性
|
||||
if self.desc:
|
||||
ret += PREFIX + f"\n**{get_text(lang, 'desc')}**: {self.desc}\n"
|
||||
|
||||
# 复数属性
|
||||
if self.args:
|
||||
ret += PREFIX + f"\n**{get_text(lang, 'docstring.args')}**:\n"
|
||||
for arg in self.args:
|
||||
ret += PREFIX + f"> - {arg.name}: {arg.type} {arg.desc}\n"
|
||||
if self.attrs:
|
||||
ret += PREFIX + f"\n**{get_text(lang, 'docstring.attrs')}**:\n"
|
||||
for attr in self.attrs:
|
||||
ret += PREFIX + f"> - {attr.name}: {attr.type} {attr.desc}\n"
|
||||
|
||||
# 单数属性
|
||||
if self.return_ is not None:
|
||||
ret += PREFIX + f"\n**{get_text(lang, 'docstring.return')}**: {self.return_.desc}\n"
|
||||
# 复数属性
|
||||
if self.raise_:
|
||||
ret += PREFIX + f"\n**{get_text(lang, 'docstring.raises')}**:\n"
|
||||
for exception in self.raise_:
|
||||
ret += PREFIX + f"> - {exception.name} {exception.desc}\n"
|
||||
if self.example:
|
||||
ret += PREFIX + f"\n**{get_text(lang, 'docstring.example')}**:\n"
|
||||
for example in self.example:
|
||||
ret += PREFIX + f" - {example.desc}\n> **{get_text(lang, 'docs.input')}**: {example.input}\n> **{get_text(lang, 'docs.output')}**: {example.output}\n"
|
||||
return ret
|
||||
|
||||
def __str__(self):
|
||||
return self.desc
|
@ -1,192 +0,0 @@
|
||||
"""
|
||||
Google docstring parser for Python.
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
from litedoc.docstring.docstring import Docstring
|
||||
|
||||
|
||||
class Parser:
|
||||
...
|
||||
|
||||
|
||||
class GoogleDocstringParser(Parser):
|
||||
_tokens = {
|
||||
"Args" : "args",
|
||||
"Arguments" : "args",
|
||||
"参数" : "args",
|
||||
|
||||
"Return" : "return",
|
||||
"Returns" : "return",
|
||||
"返回" : "return",
|
||||
|
||||
"Attribute" : "attribute",
|
||||
"Attributes" : "attribute",
|
||||
"属性" : "attribute",
|
||||
|
||||
"Raises" : "raises",
|
||||
"Raise" : "raises",
|
||||
"引发" : "raises",
|
||||
|
||||
"Example" : "example",
|
||||
"Examples" : "example",
|
||||
"示例" : "example",
|
||||
|
||||
"Yields" : "yields",
|
||||
"Yield" : "yields",
|
||||
"产出" : "yields",
|
||||
|
||||
"Requires" : "requires",
|
||||
"Require" : "requires",
|
||||
"需要" : "requires",
|
||||
|
||||
"FrontMatter": "front_matter",
|
||||
"前言" : "front_matter",
|
||||
}
|
||||
|
||||
def __init__(self, docstring: str, indent: int = 4):
|
||||
self.lines = docstring.splitlines()
|
||||
self.indent = indent
|
||||
self.lineno = 0 # Current line number
|
||||
self.char = 0 # Current character position
|
||||
|
||||
self.docstring = Docstring()
|
||||
|
||||
def read_line(self, move: bool = True) -> str:
|
||||
"""
|
||||
每次读取一行
|
||||
Args:
|
||||
move: 是否移动指针
|
||||
Returns:
|
||||
"""
|
||||
if self.lineno >= len(self.lines):
|
||||
return ""
|
||||
line = self.lines[self.lineno]
|
||||
if move:
|
||||
self.lineno += 1
|
||||
return line
|
||||
|
||||
def match_token(self) -> Optional[str]:
|
||||
"""
|
||||
解析下一行的token
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for token in self._tokens:
|
||||
line = self.read_line(move=False)
|
||||
if line.strip().startswith(token):
|
||||
self.lineno += 1
|
||||
return self._tokens[token]
|
||||
return None
|
||||
|
||||
def parse_args(self):
|
||||
"""
|
||||
依次解析后面的参数行,直到缩进小于等于当前行的缩进
|
||||
"""
|
||||
while line := self.match_next_line():
|
||||
if ":" in line:
|
||||
name, desc = line.split(":", 1)
|
||||
self.docstring.add_arg(name.strip(), desc.strip())
|
||||
else:
|
||||
self.docstring.add_arg(line.strip())
|
||||
|
||||
def parse_return(self):
|
||||
"""
|
||||
解析返回值行
|
||||
"""
|
||||
if line := self.match_next_line():
|
||||
self.docstring.add_return(line.strip())
|
||||
|
||||
def parse_raises(self):
|
||||
"""
|
||||
解析异常行
|
||||
"""
|
||||
while line := self.match_next_line():
|
||||
if ":" in line:
|
||||
name, desc = line.split(":", 1)
|
||||
self.docstring.add_raise(name.strip(), desc.strip())
|
||||
else:
|
||||
self.docstring.add_raise(line.strip())
|
||||
|
||||
def parse_example(self):
|
||||
"""
|
||||
解析示例行
|
||||
"""
|
||||
while line := self.match_next_line():
|
||||
if ":" in line:
|
||||
name, desc = line.split(":", 1)
|
||||
self.docstring.add_example(name.strip(), desc.strip())
|
||||
else:
|
||||
self.docstring.add_example(line.strip())
|
||||
|
||||
def parse_attrs(self):
|
||||
"""
|
||||
解析属性行
|
||||
"""
|
||||
while line := self.match_next_line():
|
||||
if ":" in line:
|
||||
name, desc = line.split(":", 1)
|
||||
self.docstring.add_attrs(name.strip(), desc.strip())
|
||||
else:
|
||||
self.docstring.add_attrs(line.strip())
|
||||
|
||||
def match_next_line(self) -> Optional[str]:
|
||||
"""
|
||||
在一个子解析器中,解析下一行,直到缩进小于等于当前行的缩进
|
||||
Returns:
|
||||
"""
|
||||
line = self.read_line(move=False)
|
||||
if line.startswith(" " * self.indent):
|
||||
self.lineno += 1
|
||||
return line[self.indent:]
|
||||
else:
|
||||
return None
|
||||
|
||||
def parse(self) -> Docstring:
|
||||
"""
|
||||
逐行解析,直到遇到EOS
|
||||
|
||||
最开始未解析到的内容全部加入desc
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
add_desc = True
|
||||
while self.lineno < len(self.lines):
|
||||
token = self.match_token()
|
||||
if token is None and add_desc:
|
||||
self.docstring.add_desc(self.lines[self.lineno].strip())
|
||||
|
||||
if token is not None:
|
||||
add_desc = False
|
||||
|
||||
match token:
|
||||
case "args":
|
||||
self.parse_args()
|
||||
case "return":
|
||||
self.parse_return()
|
||||
case "attribute":
|
||||
self.parse_attrs()
|
||||
case "raises":
|
||||
self.parse_raises()
|
||||
case "example":
|
||||
self.parse_example()
|
||||
case _:
|
||||
self.lineno += 1
|
||||
|
||||
return self.docstring
|
||||
|
||||
|
||||
class NumpyDocstringParser(Parser):
|
||||
...
|
||||
|
||||
|
||||
class ReStructuredParser(Parser):
|
||||
...
|
||||
|
||||
|
||||
def parse(docstring: str, parser: str = "google", indent: int = 4) -> Docstring:
|
||||
if parser == "google":
|
||||
return GoogleDocstringParser(docstring, indent).parse()
|
||||
else:
|
||||
raise ValueError(f"Unknown parser: {parser}")
|
131
litedoc/i18n.py
131
litedoc/i18n.py
@ -1,131 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Internationalization module.
|
||||
"""
|
||||
from typing import Optional, TypeAlias
|
||||
|
||||
NestedDict: TypeAlias = dict[str, 'str | NestedDict']
|
||||
|
||||
i18n_dict: dict[str, NestedDict] = {
|
||||
"en" : {
|
||||
"docstring": {
|
||||
"args" : "Arguments",
|
||||
"return" : "Return",
|
||||
"attribute": "Attribute",
|
||||
"raises" : "Raises",
|
||||
"example" : "Examples",
|
||||
"yields" : "Yields",
|
||||
},
|
||||
"src": "Source code",
|
||||
"desc": "Description",
|
||||
"type": "Type",
|
||||
},
|
||||
"zh-Hans": {
|
||||
"docstring": {
|
||||
"args" : "参数",
|
||||
"return" : "返回",
|
||||
"attribute": "属性",
|
||||
"raises" : "引发",
|
||||
"example" : "示例",
|
||||
"yields" : "产出",
|
||||
},
|
||||
"src": "源代码",
|
||||
"desc": "说明",
|
||||
"type": "类型",
|
||||
},
|
||||
"zh-Hant": {
|
||||
"docstring": {
|
||||
"args" : "變數説明",
|
||||
"return" : "返回",
|
||||
"attribute": "屬性",
|
||||
"raises" : "抛出",
|
||||
"example" : "範例",
|
||||
"yields" : "產出",
|
||||
},
|
||||
"src": "源碼",
|
||||
"desc": "説明",
|
||||
"type": "類型",
|
||||
},
|
||||
"ja" : {
|
||||
"docstring": {
|
||||
"args" : "引数",
|
||||
"return" : "戻り値",
|
||||
"attribute": "属性",
|
||||
"raises" : "例外",
|
||||
"example" : "例",
|
||||
"yields" : "生成",
|
||||
},
|
||||
"src": "ソースコード",
|
||||
"desc": "説明",
|
||||
"type": "タイプ",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def flat_i18n_dict(data: dict[str, NestedDict]) -> dict[str, dict[str, str]]:
|
||||
"""
|
||||
Flatten i18n_dict.
|
||||
Examples:
|
||||
```python
|
||||
{
|
||||
"en": {
|
||||
"docs": {
|
||||
"key1": "val1",
|
||||
"key2": "val2",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```python
|
||||
{
|
||||
"en": {
|
||||
"docs.key1": "val1",
|
||||
"docs.key2": "val2",
|
||||
}
|
||||
}
|
||||
```
|
||||
Returns:
|
||||
"""
|
||||
ret: dict[str, dict[str, str]] = {}
|
||||
|
||||
def _flat(_lang_data: NestedDict) -> dict[str, str]:
|
||||
res = {}
|
||||
for k, v in _lang_data.items():
|
||||
if isinstance(v, dict):
|
||||
for kk, vv in _flat(v).items():
|
||||
res[f"{k}.{kk}"] = vv
|
||||
else:
|
||||
res[k] = v
|
||||
return res
|
||||
|
||||
for lang, lang_data in data.items():
|
||||
ret[lang] = _flat(lang_data)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
i18n_flat_dict = flat_i18n_dict(i18n_dict)
|
||||
|
||||
|
||||
def get_text(lang: str, key: str, default: Optional[str] = None, fallback: Optional[str] = "en") -> str:
|
||||
"""
|
||||
Get text from i18n_dict.
|
||||
Args:
|
||||
lang: language name
|
||||
key: text key
|
||||
default: default text, if None return fallback language or key
|
||||
fallback: fallback language, priority is higher than default
|
||||
Returns:
|
||||
str: text
|
||||
"""
|
||||
if lang in i18n_flat_dict:
|
||||
if key in i18n_flat_dict[lang]:
|
||||
return i18n_flat_dict[lang][key]
|
||||
|
||||
if fallback is not None:
|
||||
return i18n_flat_dict.get(fallback, {}).get(key, default or key)
|
||||
else:
|
||||
return default or key
|
@ -1,116 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午3:59
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : output.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import os.path
|
||||
|
||||
from litedoc.style.markdown import generate
|
||||
from litedoc.syntax.astparser import AstParser
|
||||
|
||||
|
||||
def write_to_file(content: str, output: str) -> None:
|
||||
"""
|
||||
Write content to file.
|
||||
|
||||
Args:
|
||||
content: str, content to write.
|
||||
output: str, path to output file.
|
||||
"""
|
||||
if not os.path.exists(os.path.dirname(output)):
|
||||
os.makedirs(os.path.dirname(output))
|
||||
|
||||
with open(output, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def get_file_list(module_folder: str):
|
||||
file_list = []
|
||||
for root, dirs, files in os.walk(module_folder):
|
||||
for file in files:
|
||||
if file.endswith((".py", ".pyi")):
|
||||
file_list.append(os.path.join(root, file))
|
||||
return file_list
|
||||
|
||||
|
||||
def get_relative_path(base_path: str, target_path: str) -> str:
|
||||
"""
|
||||
获取相对路径
|
||||
Args:
|
||||
base_path: 基础路径
|
||||
target_path: 目标路径
|
||||
"""
|
||||
return os.path.relpath(target_path, base_path)
|
||||
|
||||
|
||||
def generate_from_module(module_folder: str,
|
||||
output_dir: str,
|
||||
with_top: bool = False,
|
||||
lang: str = "zh-Hans",
|
||||
ignored_paths=None,
|
||||
theme: str = "vitepress",
|
||||
style: str = "google"
|
||||
):
|
||||
"""
|
||||
生成文档
|
||||
Args:
|
||||
module_folder: 模块文件夹
|
||||
output_dir: 输出文件夹
|
||||
with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md
|
||||
ignored_paths: 忽略的路径
|
||||
lang: 语言
|
||||
theme: 主题
|
||||
style: 样式
|
||||
"""
|
||||
if ignored_paths is None:
|
||||
ignored_paths = []
|
||||
file_data: dict[str, str] = {} # 路径 -> 字串
|
||||
|
||||
file_list = get_file_list(module_folder)
|
||||
|
||||
# 清理输出目录
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
replace_data = {
|
||||
"__init__": "index" if theme == "vitepress" else "README",
|
||||
".py" : ".md",
|
||||
}
|
||||
|
||||
for pyfile_path in file_list:
|
||||
if any(ignored_path.replace("\\", "/") in pyfile_path.replace("\\", "/") for ignored_path in ignored_paths):
|
||||
continue
|
||||
|
||||
no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path) # 去头路径
|
||||
|
||||
# markdown相对路径
|
||||
rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path
|
||||
for rk, rv in replace_data.items():
|
||||
rel_md_path = rel_md_path.replace(rk, rv)
|
||||
|
||||
abs_md_path = os.path.join(output_dir, rel_md_path)
|
||||
|
||||
# 获取模块信息
|
||||
ast_parser = AstParser(open(pyfile_path, "r", encoding="utf-8").read())
|
||||
|
||||
# 生成markdown
|
||||
|
||||
front_matter = {
|
||||
"title": pyfile_path.replace("\\", "/").
|
||||
replace("/", ".").
|
||||
replace(".py", "").
|
||||
replace(".__init__", ""),
|
||||
|
||||
}
|
||||
|
||||
md_content = generate(ast_parser, lang=lang, frontmatter=front_matter)
|
||||
print(f"Generate {pyfile_path} -> {abs_md_path}")
|
||||
file_data[abs_md_path] = md_content
|
||||
|
||||
for fn, content in file_data.items():
|
||||
write_to_file(content, fn)
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午3:39
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py.py
|
||||
@Software: PyCharm
|
||||
"""
|
@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/28 下午3:39
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : markdown.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
from litedoc.syntax.astparser import AstParser
|
||||
from litedoc.syntax.node import *
|
||||
from litedoc.i18n import get_text
|
||||
|
||||
|
||||
def generate(parser: AstParser, lang: str, frontmatter: Optional[dict] = None, style: str = "google") -> str:
|
||||
"""
|
||||
Generate markdown style document from ast
|
||||
You can modify this function to generate markdown style that enjoys you
|
||||
Args:
|
||||
parser:
|
||||
lang: language
|
||||
frontmatter:
|
||||
style: style of docs
|
||||
Returns:
|
||||
markdown style document
|
||||
"""
|
||||
if frontmatter is not None:
|
||||
md = "---\n"
|
||||
for k, v in frontmatter.items():
|
||||
md += f"{k}: {v}\n"
|
||||
md += "---\n"
|
||||
else:
|
||||
md = ""
|
||||
|
||||
# var > func > class
|
||||
|
||||
"""遍历函数"""
|
||||
for func in parser.functions:
|
||||
if func.name.startswith("_"):
|
||||
continue
|
||||
md += func.markdown(lang)
|
||||
|
||||
"""遍历类"""
|
||||
|
||||
for cls in parser.classes:
|
||||
md += cls.markdown(lang)
|
||||
|
||||
"""遍历变量"""
|
||||
for var in parser.variables:
|
||||
md += f"### ***var*** `{var.name} = {var.value}`\n\n"
|
||||
if var.type != TypeHint.NO_TYPEHINT:
|
||||
md += f"- **{get_text(lang, 'type')}**: `{var.type}`\n\n"
|
||||
|
||||
if var.docs is not None:
|
||||
md += f"- **{get_text(lang, 'desc')}**: {var.docs}\n\n"
|
||||
|
||||
return md
|
@ -1,300 +0,0 @@
|
||||
# -*- 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
|
||||
import inspect
|
||||
|
||||
from .node import *
|
||||
from ..docstring.parser import parse
|
||||
|
||||
class AstParser:
|
||||
def __init__(self, code: str, style: str = "google"):
|
||||
"""
|
||||
从代码解析AST
|
||||
Args:
|
||||
code: 代码
|
||||
style: 注释风格
|
||||
"""
|
||||
self.style = style
|
||||
self.code = code
|
||||
self.tree = ast.parse(code)
|
||||
|
||||
self.classes: list[ClassNode] = []
|
||||
self.functions: list[FunctionNode] = []
|
||||
self.variables: list[AssignNode] = []
|
||||
|
||||
self.parse()
|
||||
|
||||
@staticmethod
|
||||
def clear_quotes(s: str) -> str:
|
||||
"""
|
||||
去除类型注解中的引号
|
||||
Args:
|
||||
s:
|
||||
Returns:
|
||||
"""
|
||||
return s.replace("'", "").replace('"', "")
|
||||
|
||||
def get_line_content(self, lineno: int, ignore_index_out: bool = True) -> str:
|
||||
"""获取代码行内容
|
||||
Args:
|
||||
lineno: 行号
|
||||
ignore_index_out: 是否忽略索引越界
|
||||
Returns:
|
||||
代码行内容
|
||||
"""
|
||||
if ignore_index_out:
|
||||
if lineno < 1 or lineno > len(self.code.split("\n")):
|
||||
return ""
|
||||
return self.code.split("\n")[lineno - 1]
|
||||
|
||||
@staticmethod
|
||||
def match_line_docs(linecontent: str) -> str:
|
||||
"""匹配行内注释
|
||||
Args:
|
||||
linecontent: 行内容
|
||||
Returns:
|
||||
文档字符串
|
||||
"""
|
||||
in_string = False
|
||||
string_char = ''
|
||||
for i, char in enumerate(linecontent):
|
||||
if char in ('"', "'"):
|
||||
if in_string:
|
||||
if char == string_char:
|
||||
in_string = False
|
||||
else:
|
||||
in_string = True
|
||||
string_char = char
|
||||
elif char == '#' and not in_string:
|
||||
return linecontent[i + 1:].strip()
|
||||
return ""
|
||||
|
||||
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), parser=self.style) 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), parser=self.style) if ast.get_docstring(sub_node) else None,
|
||||
posonlyargs=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=self.clear_quotes(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=self.clear_quotes(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=self.clear_quotes(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_=self.clear_quotes(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(),
|
||||
is_classmethod=True
|
||||
))
|
||||
# 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), parser=self.style) if ast.get_docstring(node) else None,
|
||||
posonlyargs=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT,
|
||||
)
|
||||
for arg in node.args.posonlyargs
|
||||
],
|
||||
args=[
|
||||
ArgNode(
|
||||
name=arg.arg,
|
||||
type=self.clear_quotes(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=self.clear_quotes(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_=self.clear_quotes(ast.unparse(node.returns).strip()) if node.returns else TypeHint.NO_RETURN,
|
||||
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_variable2(node):
|
||||
continue
|
||||
else:
|
||||
pass
|
||||
lineno = node.lineno
|
||||
prev_line = self.get_line_content(lineno - 1).strip()
|
||||
curr_line = self.get_line_content(lineno).strip()
|
||||
next_line = self.get_line_content(lineno + 1).strip()
|
||||
|
||||
# 获取文档字符串,优先检测下行"""
|
||||
if next_line.startswith('"""'):
|
||||
docs = next_line[3:-3]
|
||||
elif prev_line.startswith('"""'):
|
||||
docs = prev_line[3:-3]
|
||||
else:
|
||||
curr_docs = self.match_line_docs(curr_line)
|
||||
if curr_docs:
|
||||
docs = curr_docs
|
||||
else:
|
||||
docs = None
|
||||
|
||||
# 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
|
||||
# ))
|
||||
if 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(),
|
||||
docs=docs
|
||||
))
|
||||
|
||||
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 | ast.AnnAssign):
|
||||
"""在类方法或函数内部的变量不会被记录"""
|
||||
|
||||
# for parent in ast.walk(self.tree):
|
||||
# if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
# if node in parent.body:
|
||||
# return False
|
||||
# else:
|
||||
# for sub_node in parent.body:
|
||||
# if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
# if node in sub_node.body:
|
||||
# return False
|
||||
# return True
|
||||
# 递归检查
|
||||
def _check(_node, _parent):
|
||||
if isinstance(_parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
if _node in _parent.body:
|
||||
return False
|
||||
else:
|
||||
for sub_node in _parent.body:
|
||||
if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
return _check(_node, sub_node)
|
||||
return True
|
||||
|
||||
for parent in ast.walk(self.tree):
|
||||
if not _check(node, parent):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_module_level_variable2(self, node: ast.Assign | ast.AnnAssign) -> bool:
|
||||
"""
|
||||
检查变量是否在模块级别定义。
|
||||
"""
|
||||
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
|
@ -1,314 +0,0 @@
|
||||
# -*- 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
|
@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/29 下午12:02
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : translator.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
from translate import Translator # type: ignore
|
||||
|
||||
# 特殊映射语言
|
||||
i18n_lang2googletrans_lang = {
|
||||
"zh-Hans": "zh-cn",
|
||||
"zh-Hant": "zh-tw",
|
||||
"en" : "en",
|
||||
}
|
||||
|
||||
|
||||
def get_google_lang(lang: str) -> str:
|
||||
"""
|
||||
Get google translate language
|
||||
Args:
|
||||
lang: language
|
||||
Returns:
|
||||
google translate language
|
||||
"""
|
||||
return i18n_lang2googletrans_lang.get(lang, lang)
|
||||
|
||||
|
||||
def translate(text: str, lang: str, source_lang: str) -> str:
|
||||
"""
|
||||
Translate text to target language
|
||||
Args:
|
||||
source_lang:
|
||||
text: text
|
||||
lang: target language
|
||||
Returns:
|
||||
translated text
|
||||
"""
|
||||
if lang == source_lang:
|
||||
return text
|
||||
google_lang = get_google_lang(lang)
|
||||
return Translator(to_lang=google_lang, from_lang=source_lang).translate(text)
|
Loading…
Reference in New Issue
Block a user