ErisPulse 2.1.13rc2__tar.gz → 2.1.14.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erispulse-2.1.14.dev1/.github/assets/docs/install_pip.gif +0 -0
- erispulse-2.1.14.dev1/.github/tools/update-api-docs.py +616 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/PKG-INFO +1 -1
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AIDocs/ErisPulse-AdapterDev.md +574 -638
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AIDocs/ErisPulse-Core.md +460 -490
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AIDocs/ErisPulse-Full.md +788 -657
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AIDocs/ErisPulse-ModuleDev.md +778 -649
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/CHANGELOG.md +34 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/Development/Adapter.md +10 -8
- erispulse-2.1.14.dev1/docs/Development/Module.md +323 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/PlatformFeatures.md +10 -6
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/UseCore.md +25 -45
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/Core/adapter.md +120 -69
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/Core/config.md +8 -8
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/Core/env.md +111 -64
- erispulse-2.1.14.dev1/docs/api/ErisPulse/Core/exceptions.md +74 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/Core/logger.md +14 -18
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/Core/mods.md +61 -34
- erispulse-2.1.14.dev1/docs/api/ErisPulse/Core/router.md +111 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/__init__.md +55 -76
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/api/ErisPulse/__main__.md +59 -63
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/pyproject.toml +1 -1
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/Core/__init__.py +8 -6
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/Core/adapter.py +11 -5
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/Core/env.py +1 -32
- erispulse-2.1.14.dev1/src/ErisPulse/Core/exceptions.py +136 -0
- erispulse-2.1.13rc2/src/ErisPulse/Core/server.py → erispulse-2.1.14.dev1/src/ErisPulse/Core/router.py +51 -70
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/__init__.py +8 -5
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/__main__.py +70 -23
- erispulse-2.1.13rc2/.github/tools/update-api-docs.py +0 -276
- erispulse-2.1.13rc2/docs/AIDocs/README.md +0 -3
- erispulse-2.1.13rc2/docs/Development/Module.md +0 -132
- erispulse-2.1.13rc2/docs/api/ErisPulse/Core/raiserr.md +0 -96
- erispulse-2.1.13rc2/docs/api/ErisPulse/Core/server.md +0 -124
- erispulse-2.1.13rc2/docs/api/ErisPulse/Core/util.md +0 -97
- erispulse-2.1.13rc2/docs/api/README.md +0 -3
- erispulse-2.1.13rc2/src/ErisPulse/Core/raiserr.py +0 -181
- erispulse-2.1.13rc2/src/ErisPulse/Core/util.py +0 -123
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/.github/assets/erispulse_logo.png +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/.github/tools/merge_md.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/.github/workflows/auto-tag-release.yml +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/.github/workflows/pypi-publish.yml +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/.gitignore +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/.python-version +0 -0
- {erispulse-2.1.13rc2/docs → erispulse-2.1.14.dev1}/CHANGELOG_1.x.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/CODE_OF_CONDUCT.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/LICENSE +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/README.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/devs/test.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/devs/test_adapter.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/devs/test_files/test.docx +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/devs/test_files/test.jpg +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/devs/test_files/test.mp4 +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AIModuleGeneration.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AdapterStandards/APIResponse.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AdapterStandards/EventConversion.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/AdapterStandards/README.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/CLI.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/Development/CLI.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/Development/README.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/docs/quick-start.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-adapter/LICENSE +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-adapter/MyAdapter/Core.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-adapter/MyAdapter/__init__.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-adapter/README.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-adapter/pyproject.toml +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-cli-module/LICENSE +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-cli-module/README.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-cli-module/my_cli_module/__init__.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-cli-module/my_cli_module/cli.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-cli-module/pyproject.toml +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-module/LICENSE +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-module/MyModule/Core.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-module/MyModule/__init__.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-module/README.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/examples/example-module/pyproject.toml +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/scripts/install/install.ps1 +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/scripts/install/install.sh +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/Core/config.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/src/ErisPulse/Core/mods.py +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/style_guide/DOCSTRING_SPEC.md +0 -0
- {erispulse-2.1.13rc2 → erispulse-2.1.14.dev1}/style_guide/README.md +0 -0
|
Binary file
|
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import ast
|
|
3
|
+
import re
|
|
4
|
+
import argparse
|
|
5
|
+
from typing import List, Dict, Tuple, Optional, Set
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
def process_docstring(docstring: str) -> Optional[str]:
|
|
9
|
+
"""
|
|
10
|
+
处理文档字符串中的特殊标签
|
|
11
|
+
|
|
12
|
+
:param docstring: 原始文档字符串
|
|
13
|
+
:return: 处理后的文档字符串或None(如果包含忽略标签)
|
|
14
|
+
"""
|
|
15
|
+
if not docstring:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
# 检查忽略标签
|
|
19
|
+
if "{!--< ignore >!--}" in docstring:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
# 替换 {!--< internal-use >!--}
|
|
23
|
+
docstring = re.sub(
|
|
24
|
+
r"{!--< internal-use >!--}(.*)",
|
|
25
|
+
lambda m: f"<div class='admonition warning'><p class='admonition-title'>内部方法</p><p>{m.group(1).strip()}</p></div>",
|
|
26
|
+
docstring
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# 替换过时标签
|
|
30
|
+
docstring = re.sub(
|
|
31
|
+
r"\{!--< deprecated >!--\}(.*)",
|
|
32
|
+
lambda m: f"<div class='admonition attention'><p class='admonition-title'>已弃用</p><p>{m.group(1).strip()}</p></div>",
|
|
33
|
+
docstring
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# 替换实验性标签
|
|
37
|
+
docstring = re.sub(
|
|
38
|
+
r"\{!--< experimental >!--\}(.*)",
|
|
39
|
+
lambda m: f"<div class='admonition tip'><p class='admonition-title'>实验性功能</p><p>{m.group(1).strip()}</p></div>",
|
|
40
|
+
docstring
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# 处理提示标签(多行)
|
|
44
|
+
docstring = re.sub(
|
|
45
|
+
r"\{!--< tips >!--\}(.*?)\{!--< /tips >!--\}",
|
|
46
|
+
lambda m: f"<div class='admonition tip'><p class='admonition-title'>提示</p><p>{m.group(1).strip()}</p></div>",
|
|
47
|
+
docstring,
|
|
48
|
+
flags=re.DOTALL
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 处理单行提示标签
|
|
52
|
+
docstring = re.sub(
|
|
53
|
+
r"\{!--< tips >!--\}([^\n]*)",
|
|
54
|
+
lambda m: f"<div class='admonition tip'><p class='admonition-title'>提示</p><p>{m.group(1).strip()}</p></div>",
|
|
55
|
+
docstring
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# 参数说明
|
|
59
|
+
docstring = re.sub(
|
|
60
|
+
r":param (\w+):\s*\[([^\]]+)\]\s*(.*)",
|
|
61
|
+
lambda m: f"<dt><code>{m.group(1)}</code> <span class='type-hint'>{m.group(2)}</span></dt><dd>{m.group(3).strip()}</dd>",
|
|
62
|
+
docstring
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# 返回值说明
|
|
66
|
+
docstring = re.sub(
|
|
67
|
+
r":return:\s*\[([^\]]+)\]\s*(.*)",
|
|
68
|
+
lambda m: f"<dt>返回值</dt><dd><span class='type-hint'>{m.group(1)}</span> {m.group(2).strip()}</dd>",
|
|
69
|
+
docstring
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 异常说明
|
|
73
|
+
docstring = re.sub(
|
|
74
|
+
r":raises (\w+):\s*(.*)",
|
|
75
|
+
lambda m: f"<dt>异常</dt><dd><code>{m.group(1)}</code> {m.group(2).strip()}</dd>",
|
|
76
|
+
docstring
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# 示例代码
|
|
80
|
+
docstring = re.sub(
|
|
81
|
+
r":example:\s*(.*?)(?=\n\w|\Z)",
|
|
82
|
+
lambda m: f"<details class='example'><summary>示例</summary>\n\n```python\n{m.group(1).strip()}\n```\n</details>",
|
|
83
|
+
docstring,
|
|
84
|
+
flags=re.DOTALL
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 统一换行符为两个换行
|
|
88
|
+
docstring = re.sub(r"\n{2,}", "\n\n", docstring.strip())
|
|
89
|
+
|
|
90
|
+
return docstring.strip()
|
|
91
|
+
|
|
92
|
+
def parse_python_file(file_path: str) -> Tuple[Optional[str], List[Dict], List[Dict]]:
|
|
93
|
+
"""
|
|
94
|
+
解析Python文件,提取模块文档、类和函数信息
|
|
95
|
+
|
|
96
|
+
:param file_path: Python文件路径
|
|
97
|
+
:return: (模块文档, 类列表, 函数列表)
|
|
98
|
+
"""
|
|
99
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
100
|
+
source = f.read()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
module = ast.parse(source)
|
|
104
|
+
except SyntaxError:
|
|
105
|
+
print(f"⚠️ 语法错误,跳过文件: {file_path}")
|
|
106
|
+
return None, [], []
|
|
107
|
+
|
|
108
|
+
# 提取模块文档
|
|
109
|
+
module_doc = ast.get_docstring(module)
|
|
110
|
+
processed_module_doc = process_docstring(module_doc) if module_doc else None
|
|
111
|
+
|
|
112
|
+
classes = []
|
|
113
|
+
functions = []
|
|
114
|
+
|
|
115
|
+
# 遍历AST节点
|
|
116
|
+
for node in module.body:
|
|
117
|
+
# 处理类定义
|
|
118
|
+
if isinstance(node, ast.ClassDef):
|
|
119
|
+
class_doc = ast.get_docstring(node)
|
|
120
|
+
processed_class_doc = process_docstring(class_doc) if class_doc else None
|
|
121
|
+
|
|
122
|
+
if processed_class_doc is None:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
methods = []
|
|
126
|
+
# 提取类方法
|
|
127
|
+
for item in node.body:
|
|
128
|
+
if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
129
|
+
method_doc = ast.get_docstring(item)
|
|
130
|
+
processed_method_doc = process_docstring(method_doc) if method_doc else None
|
|
131
|
+
|
|
132
|
+
if processed_method_doc:
|
|
133
|
+
# 获取函数签名
|
|
134
|
+
args = []
|
|
135
|
+
defaults = dict(zip([arg.arg for arg in item.args.args][-len(item.args.defaults):], item.args.defaults)) if item.args.defaults else {}
|
|
136
|
+
for arg in item.args.args:
|
|
137
|
+
if arg.arg == "self":
|
|
138
|
+
continue
|
|
139
|
+
arg_str = arg.arg
|
|
140
|
+
if arg.annotation:
|
|
141
|
+
arg_str += f": {ast.unparse(arg.annotation)}"
|
|
142
|
+
if arg.arg in defaults:
|
|
143
|
+
default_val = ast.unparse(defaults[arg.arg])
|
|
144
|
+
arg_str += f" = {default_val}"
|
|
145
|
+
args.append(arg_str)
|
|
146
|
+
|
|
147
|
+
signature = f"{item.name}({', '.join(args)})"
|
|
148
|
+
if isinstance(item, ast.AsyncFunctionDef):
|
|
149
|
+
signature = f"async {signature}"
|
|
150
|
+
|
|
151
|
+
methods.append({
|
|
152
|
+
"name": item.name,
|
|
153
|
+
"signature": signature,
|
|
154
|
+
"doc": processed_method_doc,
|
|
155
|
+
"is_async": isinstance(item, ast.AsyncFunctionDef)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
# 获取类签名
|
|
159
|
+
bases = [ast.unparse(base) for base in node.bases] if node.bases else []
|
|
160
|
+
class_signature = f"class {node.name}({', '.join(bases)})" if bases else f"class {node.name}"
|
|
161
|
+
|
|
162
|
+
classes.append({
|
|
163
|
+
"name": node.name,
|
|
164
|
+
"signature": class_signature,
|
|
165
|
+
"doc": processed_class_doc,
|
|
166
|
+
"methods": methods
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
# 处理函数定义
|
|
170
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
171
|
+
func_doc = ast.get_docstring(node)
|
|
172
|
+
processed_func_doc = process_docstring(func_doc) if func_doc else None
|
|
173
|
+
|
|
174
|
+
if processed_func_doc:
|
|
175
|
+
# 获取函数签名
|
|
176
|
+
args = []
|
|
177
|
+
defaults = dict(zip([arg.arg for arg in node.args.args][-len(node.args.defaults):], node.args.defaults)) if node.args.defaults else {}
|
|
178
|
+
for arg in node.args.args:
|
|
179
|
+
arg_str = arg.arg
|
|
180
|
+
if arg.annotation:
|
|
181
|
+
arg_str += f": {ast.unparse(arg.annotation)}"
|
|
182
|
+
if arg.arg in defaults:
|
|
183
|
+
default_val = ast.unparse(defaults[arg.arg])
|
|
184
|
+
arg_str += f" = {default_val}"
|
|
185
|
+
args.append(arg_str)
|
|
186
|
+
|
|
187
|
+
signature = f"{node.name}({', '.join(args)})"
|
|
188
|
+
if isinstance(node, ast.AsyncFunctionDef):
|
|
189
|
+
signature = f"async {signature}"
|
|
190
|
+
|
|
191
|
+
functions.append({
|
|
192
|
+
"name": node.name,
|
|
193
|
+
"signature": signature,
|
|
194
|
+
"doc": processed_func_doc,
|
|
195
|
+
"is_async": isinstance(node, ast.AsyncFunctionDef)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
return processed_module_doc, classes, functions
|
|
199
|
+
|
|
200
|
+
def generate_markdown(module_path: str, module_doc: Optional[str],
|
|
201
|
+
classes: List[Dict], functions: List[Dict]) -> str:
|
|
202
|
+
"""
|
|
203
|
+
生成Markdown格式API文档
|
|
204
|
+
|
|
205
|
+
:param module_path: 模块路径(点分隔)
|
|
206
|
+
:param module_doc: 模块文档
|
|
207
|
+
:param classes: 类信息列表
|
|
208
|
+
:param functions: 函数信息列表
|
|
209
|
+
:return: Markdown格式的文档字符串
|
|
210
|
+
"""
|
|
211
|
+
content = []
|
|
212
|
+
|
|
213
|
+
# 文档头部
|
|
214
|
+
content.append(f"""# 📦 `{module_path}` 模块
|
|
215
|
+
|
|
216
|
+
<sup>自动生成于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</sup>
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 模块概述
|
|
221
|
+
|
|
222
|
+
""")
|
|
223
|
+
|
|
224
|
+
# 模块文档
|
|
225
|
+
if module_doc:
|
|
226
|
+
content.append(f"{module_doc}\n\n---\n")
|
|
227
|
+
else:
|
|
228
|
+
content.append("该模块暂无概述信息。\n\n---\n")
|
|
229
|
+
|
|
230
|
+
# 函数部分
|
|
231
|
+
if functions:
|
|
232
|
+
content.append("## 🛠️ 函数\n")
|
|
233
|
+
for func in functions:
|
|
234
|
+
async_marker = "🔷 " if func["is_async"] else ""
|
|
235
|
+
content.append(f"""### {async_marker}`{func['signature']}`
|
|
236
|
+
|
|
237
|
+
{func['doc']}
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
""")
|
|
241
|
+
|
|
242
|
+
# 类部分
|
|
243
|
+
if classes:
|
|
244
|
+
content.append("## 🏛️ 类\n")
|
|
245
|
+
for cls in classes:
|
|
246
|
+
content.append(f"""### `{cls['signature']}`
|
|
247
|
+
|
|
248
|
+
{cls['doc']}
|
|
249
|
+
|
|
250
|
+
""")
|
|
251
|
+
|
|
252
|
+
# 类方法
|
|
253
|
+
if cls["methods"]:
|
|
254
|
+
content.append("#### 🧰 方法\n")
|
|
255
|
+
for method in cls["methods"]:
|
|
256
|
+
async_marker = "🔷 " if method["is_async"] else ""
|
|
257
|
+
content.append(f"""##### {async_marker}`{method['signature']}`
|
|
258
|
+
|
|
259
|
+
{method['doc']}
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
""")
|
|
263
|
+
|
|
264
|
+
# 文档尾部
|
|
265
|
+
content.append(f"<sub>文档最后更新于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</sub>")
|
|
266
|
+
|
|
267
|
+
return "\n".join(content)
|
|
268
|
+
|
|
269
|
+
def generate_html(module_path: str, module_doc: Optional[str],
|
|
270
|
+
classes: List[Dict], functions: List[Dict]) -> str:
|
|
271
|
+
"""
|
|
272
|
+
生成HTML格式API文档
|
|
273
|
+
|
|
274
|
+
:param module_path: 模块路径(点分隔)
|
|
275
|
+
:param module_doc: 模块文档
|
|
276
|
+
:param classes: 类信息列表
|
|
277
|
+
:param functions: 函数信息列表
|
|
278
|
+
:return: HTML格式的文档字符串
|
|
279
|
+
"""
|
|
280
|
+
# 处理模块文档中的参数列表
|
|
281
|
+
if module_doc:
|
|
282
|
+
module_doc = re.sub(
|
|
283
|
+
r"(:param .+?:.+?)(?=:param|\Z)",
|
|
284
|
+
r"<dl>\1</dl>",
|
|
285
|
+
module_doc + "\n",
|
|
286
|
+
flags=re.DOTALL
|
|
287
|
+
)
|
|
288
|
+
module_doc = re.sub(
|
|
289
|
+
r"<dl>(.*?)</dl>",
|
|
290
|
+
lambda m: f"<dl class='params'>{m.group(1)}</dl>",
|
|
291
|
+
module_doc,
|
|
292
|
+
flags=re.DOTALL
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
html_content = [f"""<!DOCTYPE html>
|
|
296
|
+
<html lang="zh-CN">
|
|
297
|
+
<head>
|
|
298
|
+
<meta charset="UTF-8">
|
|
299
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
300
|
+
<title>{module_path} - ErisPulse API 文档</title>
|
|
301
|
+
<style>
|
|
302
|
+
:root {{
|
|
303
|
+
--primary-color: #3498db;
|
|
304
|
+
--secondary-color: #2c3e50;
|
|
305
|
+
--background-color: #f8f9fa;
|
|
306
|
+
--code-background: #f1f1f1;
|
|
307
|
+
--border-color: #e1e1e1;
|
|
308
|
+
--text-color: #333;
|
|
309
|
+
--heading-color: #2c3e50;
|
|
310
|
+
--warning-color: #f39c12;
|
|
311
|
+
--danger-color: #e74c3c;
|
|
312
|
+
--success-color: #27ae60;
|
|
313
|
+
}}
|
|
314
|
+
|
|
315
|
+
body {{
|
|
316
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
317
|
+
line-height: 1.6;
|
|
318
|
+
color: var(--text-color);
|
|
319
|
+
background-color: var(--background-color);
|
|
320
|
+
margin: 0;
|
|
321
|
+
padding: 0;
|
|
322
|
+
}}
|
|
323
|
+
|
|
324
|
+
.container {{
|
|
325
|
+
max-width: 1200px;
|
|
326
|
+
margin: 0 auto;
|
|
327
|
+
padding: 20px;
|
|
328
|
+
}}
|
|
329
|
+
|
|
330
|
+
header {{
|
|
331
|
+
background-color: white;
|
|
332
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
333
|
+
padding: 20px 0;
|
|
334
|
+
margin-bottom: 30px;
|
|
335
|
+
}}
|
|
336
|
+
|
|
337
|
+
h1, h2, h3, h4, h5, h6 {{
|
|
338
|
+
color: var(--heading-color);
|
|
339
|
+
}}
|
|
340
|
+
|
|
341
|
+
h1 {{
|
|
342
|
+
border-bottom: 2px solid var(--primary-color);
|
|
343
|
+
padding-bottom: 10px;
|
|
344
|
+
}}
|
|
345
|
+
|
|
346
|
+
h2 {{
|
|
347
|
+
border-left: 4px solid var(--primary-color);
|
|
348
|
+
padding-left: 10px;
|
|
349
|
+
}}
|
|
350
|
+
|
|
351
|
+
a {{
|
|
352
|
+
color: var(--primary-color);
|
|
353
|
+
text-decoration: none;
|
|
354
|
+
}}
|
|
355
|
+
|
|
356
|
+
a:hover {{
|
|
357
|
+
text-decoration: underline;
|
|
358
|
+
}}
|
|
359
|
+
|
|
360
|
+
code {{
|
|
361
|
+
background-color: var(--code-background);
|
|
362
|
+
padding: 2px 4px;
|
|
363
|
+
border-radius: 3px;
|
|
364
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
365
|
+
}}
|
|
366
|
+
|
|
367
|
+
pre {{
|
|
368
|
+
background-color: var(--code-background);
|
|
369
|
+
padding: 15px;
|
|
370
|
+
border-radius: 5px;
|
|
371
|
+
overflow-x: auto;
|
|
372
|
+
}}
|
|
373
|
+
|
|
374
|
+
pre code {{
|
|
375
|
+
background-color: transparent;
|
|
376
|
+
padding: 0;
|
|
377
|
+
}}
|
|
378
|
+
|
|
379
|
+
.admonition {{
|
|
380
|
+
margin: 20px 0;
|
|
381
|
+
padding: 15px;
|
|
382
|
+
border-radius: 5px;
|
|
383
|
+
border-left: 4px solid;
|
|
384
|
+
}}
|
|
385
|
+
|
|
386
|
+
.admonition.warning {{
|
|
387
|
+
background-color: #fff3cd;
|
|
388
|
+
border-color: var(--warning-color);
|
|
389
|
+
}}
|
|
390
|
+
|
|
391
|
+
.admonition.attention {{
|
|
392
|
+
background-color: #f8d7da;
|
|
393
|
+
border-color: var(--danger-color);
|
|
394
|
+
}}
|
|
395
|
+
|
|
396
|
+
.admonition.tip {{
|
|
397
|
+
background-color: #d1ecf1;
|
|
398
|
+
border-color: var(--success-color);
|
|
399
|
+
}}
|
|
400
|
+
|
|
401
|
+
.admonition-title {{
|
|
402
|
+
font-weight: bold;
|
|
403
|
+
margin-bottom: 5px;
|
|
404
|
+
}}
|
|
405
|
+
|
|
406
|
+
.example {{
|
|
407
|
+
margin: 15px 0;
|
|
408
|
+
}}
|
|
409
|
+
|
|
410
|
+
.example summary {{
|
|
411
|
+
cursor: pointer;
|
|
412
|
+
padding: 10px;
|
|
413
|
+
background-color: var(--code-background);
|
|
414
|
+
border-radius: 5px;
|
|
415
|
+
}}
|
|
416
|
+
|
|
417
|
+
dl.params {{
|
|
418
|
+
display: grid;
|
|
419
|
+
grid-template-columns: auto 1fr;
|
|
420
|
+
gap: 10px;
|
|
421
|
+
margin: 15px 0;
|
|
422
|
+
}}
|
|
423
|
+
|
|
424
|
+
dt {{
|
|
425
|
+
font-weight: bold;
|
|
426
|
+
text-align: right;
|
|
427
|
+
}}
|
|
428
|
+
|
|
429
|
+
dd {{
|
|
430
|
+
margin-inline-start: 0;
|
|
431
|
+
}}
|
|
432
|
+
|
|
433
|
+
.type-hint {{
|
|
434
|
+
font-size: 0.9em;
|
|
435
|
+
color: #6c757d;
|
|
436
|
+
font-style: italic;
|
|
437
|
+
}}
|
|
438
|
+
|
|
439
|
+
.signature {{
|
|
440
|
+
background-color: var(--code-background);
|
|
441
|
+
padding: 10px 15px;
|
|
442
|
+
border-radius: 5px;
|
|
443
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
444
|
+
margin: 10px 0;
|
|
445
|
+
overflow-x: auto;
|
|
446
|
+
}}
|
|
447
|
+
|
|
448
|
+
.function-signature, .class-signature {{
|
|
449
|
+
border-left: 3px solid var(--primary-color);
|
|
450
|
+
}}
|
|
451
|
+
|
|
452
|
+
.method-signature {{
|
|
453
|
+
border-left: 3px solid #95a5a6;
|
|
454
|
+
}}
|
|
455
|
+
|
|
456
|
+
footer {{
|
|
457
|
+
margin-top: 40px;
|
|
458
|
+
padding-top: 20px;
|
|
459
|
+
border-top: 1px solid var(--border-color);
|
|
460
|
+
text-align: center;
|
|
461
|
+
color: #6c757d;
|
|
462
|
+
font-size: 0.9em;
|
|
463
|
+
}}
|
|
464
|
+
|
|
465
|
+
@media (max-width: 768px) {{
|
|
466
|
+
.container {{
|
|
467
|
+
padding: 10px;
|
|
468
|
+
}}
|
|
469
|
+
|
|
470
|
+
dl.params {{
|
|
471
|
+
grid-template-columns: 1fr;
|
|
472
|
+
}}
|
|
473
|
+
|
|
474
|
+
dt {{
|
|
475
|
+
text-align: left;
|
|
476
|
+
}}
|
|
477
|
+
}}
|
|
478
|
+
</style>
|
|
479
|
+
</head>
|
|
480
|
+
<body>
|
|
481
|
+
<header>
|
|
482
|
+
<div class="container">
|
|
483
|
+
<h1>📦 <code>{module_path}</code> 模块</h1>
|
|
484
|
+
<p><small>自动生成于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</small></p>
|
|
485
|
+
</div>
|
|
486
|
+
</header>
|
|
487
|
+
|
|
488
|
+
<div class="container">
|
|
489
|
+
<section>
|
|
490
|
+
<h2>模块概述</h2>
|
|
491
|
+
"""]
|
|
492
|
+
|
|
493
|
+
# 模块文档
|
|
494
|
+
if module_doc:
|
|
495
|
+
html_content.append(f"<div>{module_doc}</div>\n")
|
|
496
|
+
else:
|
|
497
|
+
html_content.append("<p>该模块暂无概述信息。</p>\n")
|
|
498
|
+
|
|
499
|
+
# 函数部分
|
|
500
|
+
if functions:
|
|
501
|
+
html_content.append("</section>\n\n<section>\n<h2>🛠️ 函数</h2>\n")
|
|
502
|
+
for func in functions:
|
|
503
|
+
async_marker = "🔷 " if func["is_async"] else ""
|
|
504
|
+
html_content.append(f"""<article>
|
|
505
|
+
<h3>{async_marker}<code class="signature function-signature">{func['signature']}</code></h3>
|
|
506
|
+
<div>{func['doc']}</div>
|
|
507
|
+
</article>
|
|
508
|
+
""")
|
|
509
|
+
|
|
510
|
+
# 类部分
|
|
511
|
+
if classes:
|
|
512
|
+
html_content.append("</section>\n\n<section>\n<h2>🏛️ 类</h2>\n")
|
|
513
|
+
for cls in classes:
|
|
514
|
+
html_content.append(f"""<article>
|
|
515
|
+
<h3><code class="signature class-signature">{cls['signature']}</code></h3>
|
|
516
|
+
<div>{cls['doc']}</div>
|
|
517
|
+
""")
|
|
518
|
+
|
|
519
|
+
# 类方法
|
|
520
|
+
if cls["methods"]:
|
|
521
|
+
html_content.append("<h4>🧰 方法</h4>\n")
|
|
522
|
+
for method in cls["methods"]:
|
|
523
|
+
async_marker = "🔷 " if method["is_async"] else ""
|
|
524
|
+
html_content.append(f"""<article>
|
|
525
|
+
<h5>{async_marker}<code class="signature method-signature">{method['signature']}</code></h5>
|
|
526
|
+
<div>{method['doc']}</div>
|
|
527
|
+
</article>
|
|
528
|
+
""")
|
|
529
|
+
html_content.append("</article>\n")
|
|
530
|
+
|
|
531
|
+
# 文档尾部
|
|
532
|
+
html_content.append(f"""</section>
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<footer>
|
|
536
|
+
<div class="container">
|
|
537
|
+
<p>文档最后更新于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
|
538
|
+
</div>
|
|
539
|
+
</footer>
|
|
540
|
+
</body>
|
|
541
|
+
</html>""")
|
|
542
|
+
|
|
543
|
+
return "\n".join(html_content)
|
|
544
|
+
|
|
545
|
+
def generate_api_docs(src_dir: str, output_dir: str, format: str = "markdown"):
|
|
546
|
+
"""
|
|
547
|
+
生成API文档
|
|
548
|
+
|
|
549
|
+
:param src_dir: 源代码目录
|
|
550
|
+
:param output_dir: 输出目录
|
|
551
|
+
:param format: 输出格式 ("markdown" 或 "html")
|
|
552
|
+
"""
|
|
553
|
+
# 确保输出目录存在
|
|
554
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
555
|
+
|
|
556
|
+
# 遍历源代码目录
|
|
557
|
+
for root, _, files in os.walk(src_dir):
|
|
558
|
+
for file in files:
|
|
559
|
+
if file.endswith(".py"):
|
|
560
|
+
file_path = os.path.join(root, file)
|
|
561
|
+
|
|
562
|
+
# 计算模块路径
|
|
563
|
+
rel_path = os.path.relpath(file_path, src_dir)
|
|
564
|
+
module_path = rel_path.replace(".py", "").replace(os.sep, ".")
|
|
565
|
+
|
|
566
|
+
# 解析Python文件
|
|
567
|
+
module_doc, classes, functions = parse_python_file(file_path)
|
|
568
|
+
|
|
569
|
+
# 跳过没有文档的文件
|
|
570
|
+
if not module_doc and not classes and not functions:
|
|
571
|
+
print(f"⏭️ 跳过无文档文件: {file_path}")
|
|
572
|
+
continue
|
|
573
|
+
|
|
574
|
+
# 生成内容
|
|
575
|
+
if format == "html":
|
|
576
|
+
content = generate_html(module_path, module_doc, classes, functions)
|
|
577
|
+
ext = ".html"
|
|
578
|
+
else:
|
|
579
|
+
content = generate_markdown(module_path, module_doc, classes, functions)
|
|
580
|
+
ext = ".md"
|
|
581
|
+
|
|
582
|
+
# 写入文件
|
|
583
|
+
output_path = os.path.join(output_dir, f"{module_path.replace('.', '/')}{ext}")
|
|
584
|
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
585
|
+
|
|
586
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
587
|
+
f.write(content)
|
|
588
|
+
|
|
589
|
+
print(f"✅ 已生成: {output_path}")
|
|
590
|
+
|
|
591
|
+
if __name__ == "__main__":
|
|
592
|
+
parser = argparse.ArgumentParser(description="API文档生成器")
|
|
593
|
+
parser.add_argument("--src", default="src", help="源代码目录 (默认: src)")
|
|
594
|
+
parser.add_argument("--output", default="docs/api", help="输出目录 (默认: docs/api)")
|
|
595
|
+
parser.add_argument("--format", choices=["markdown", "html"], default="markdown", help="输出格式 (默认: markdown)")
|
|
596
|
+
parser.add_argument("--version", action="version", version="API文档生成器 3.0")
|
|
597
|
+
|
|
598
|
+
args = parser.parse_args()
|
|
599
|
+
|
|
600
|
+
print(f"""📁 源代码目录: {args.src}
|
|
601
|
+
📂 输出目录: {args.output}
|
|
602
|
+
📄 输出格式: {args.format}
|
|
603
|
+
⏳ 正在生成API文档...""")
|
|
604
|
+
|
|
605
|
+
generate_api_docs(args.src, args.output, args.format)
|
|
606
|
+
|
|
607
|
+
print(f"""🎉 API文档生成完成!
|
|
608
|
+
|
|
609
|
+
生成文档包含以下改进:
|
|
610
|
+
✨ 更现代化的样式和布局
|
|
611
|
+
📅 自动添加生成时间戳
|
|
612
|
+
🔖 使用emoji图标提高可读性
|
|
613
|
+
📝 优化了参数和返回值的显示方式
|
|
614
|
+
📱 响应式设计,适配移动设备
|
|
615
|
+
💡 改进了示例代码的展示方式
|
|
616
|
+
""")
|