ErisPulse 2.1.13rc2__tar.gz → 2.1.14__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.
Files changed (89) hide show
  1. erispulse-2.1.14/.github/assets/docs/install_pip.gif +0 -0
  2. erispulse-2.1.14/.github/tools/update-api-docs.py +616 -0
  3. {erispulse-2.1.13rc2 → erispulse-2.1.14}/.github/workflows/auto-tag-release.yml +34 -5
  4. {erispulse-2.1.13rc2 → erispulse-2.1.14}/PKG-INFO +31 -16
  5. {erispulse-2.1.13rc2 → erispulse-2.1.14}/README.md +30 -15
  6. erispulse-2.1.14/SECURITY.md +25 -0
  7. {erispulse-2.1.13rc2 → erispulse-2.1.14}/devs/test.py +85 -116
  8. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AIDocs/ErisPulse-AdapterDev.md +670 -675
  9. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AIDocs/ErisPulse-Core.md +564 -524
  10. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AIDocs/ErisPulse-Full.md +897 -697
  11. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AIDocs/ErisPulse-ModuleDev.md +883 -685
  12. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AdapterStandards/APIResponse.md +1 -1
  13. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/CHANGELOG.md +60 -1
  14. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/Development/Adapter.md +12 -10
  15. erispulse-2.1.14/docs/Development/Module.md +323 -0
  16. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/PlatformFeatures.md +22 -8
  17. {erispulse-2.1.13rc2/style_guide → erispulse-2.1.14/docs/StyleGuide}/README.md +1 -1
  18. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/UseCore.md +56 -43
  19. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/api/ErisPulse/Core/adapter.md +120 -69
  20. erispulse-2.1.14/docs/api/ErisPulse/Core/config.md +17 -0
  21. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/api/ErisPulse/Core/env.md +111 -64
  22. erispulse-2.1.14/docs/api/ErisPulse/Core/erispulse_config.md +60 -0
  23. erispulse-2.1.14/docs/api/ErisPulse/Core/exceptions.md +45 -0
  24. erispulse-2.1.14/docs/api/ErisPulse/Core/logger.md +126 -0
  25. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/api/ErisPulse/Core/mods.md +61 -34
  26. erispulse-2.1.14/docs/api/ErisPulse/Core/router.md +111 -0
  27. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/api/ErisPulse/__init__.md +56 -78
  28. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/api/ErisPulse/__main__.md +59 -63
  29. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-adapter/MyAdapter/Core.py +5 -4
  30. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-module/MyModule/Core.py +4 -3
  31. {erispulse-2.1.13rc2 → erispulse-2.1.14}/pyproject.toml +1 -1
  32. {erispulse-2.1.13rc2 → erispulse-2.1.14}/scripts/install/install.ps1 +120 -17
  33. {erispulse-2.1.13rc2 → erispulse-2.1.14}/scripts/install/install.sh +36 -12
  34. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/Core/__init__.py +8 -6
  35. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/Core/adapter.py +18 -15
  36. erispulse-2.1.14/src/ErisPulse/Core/config.py +74 -0
  37. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/Core/env.py +1 -32
  38. erispulse-2.1.14/src/ErisPulse/Core/erispulse_config.py +105 -0
  39. erispulse-2.1.14/src/ErisPulse/Core/exceptions.py +108 -0
  40. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/Core/logger.py +74 -1
  41. erispulse-2.1.13rc2/src/ErisPulse/Core/server.py → erispulse-2.1.14/src/ErisPulse/Core/router.py +51 -70
  42. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/__init__.py +20 -9
  43. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/__main__.py +146 -28
  44. erispulse-2.1.13rc2/.github/tools/update-api-docs.py +0 -276
  45. erispulse-2.1.13rc2/docs/AIDocs/README.md +0 -3
  46. erispulse-2.1.13rc2/docs/CHANGELOG_1.x.md +0 -378
  47. erispulse-2.1.13rc2/docs/Development/Module.md +0 -132
  48. erispulse-2.1.13rc2/docs/api/ErisPulse/Core/config.md +0 -61
  49. erispulse-2.1.13rc2/docs/api/ErisPulse/Core/logger.md +0 -94
  50. erispulse-2.1.13rc2/docs/api/ErisPulse/Core/raiserr.md +0 -96
  51. erispulse-2.1.13rc2/docs/api/ErisPulse/Core/server.md +0 -124
  52. erispulse-2.1.13rc2/docs/api/ErisPulse/Core/util.md +0 -97
  53. erispulse-2.1.13rc2/docs/api/README.md +0 -3
  54. erispulse-2.1.13rc2/src/ErisPulse/Core/config.py +0 -172
  55. erispulse-2.1.13rc2/src/ErisPulse/Core/raiserr.py +0 -181
  56. erispulse-2.1.13rc2/src/ErisPulse/Core/util.py +0 -123
  57. {erispulse-2.1.13rc2 → erispulse-2.1.14}/.github/assets/erispulse_logo.png +0 -0
  58. {erispulse-2.1.13rc2 → erispulse-2.1.14}/.github/tools/merge_md.py +0 -0
  59. {erispulse-2.1.13rc2 → erispulse-2.1.14}/.github/workflows/pypi-publish.yml +0 -0
  60. {erispulse-2.1.13rc2 → erispulse-2.1.14}/.gitignore +0 -0
  61. {erispulse-2.1.13rc2 → erispulse-2.1.14}/.python-version +0 -0
  62. {erispulse-2.1.13rc2 → erispulse-2.1.14}/CODE_OF_CONDUCT.md +0 -0
  63. {erispulse-2.1.13rc2 → erispulse-2.1.14}/LICENSE +0 -0
  64. {erispulse-2.1.13rc2 → erispulse-2.1.14}/devs/test_adapter.py +0 -0
  65. {erispulse-2.1.13rc2 → erispulse-2.1.14}/devs/test_files/test.docx +0 -0
  66. {erispulse-2.1.13rc2 → erispulse-2.1.14}/devs/test_files/test.jpg +0 -0
  67. {erispulse-2.1.13rc2 → erispulse-2.1.14}/devs/test_files/test.mp4 +0 -0
  68. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AIModuleGeneration.md +0 -0
  69. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AdapterStandards/EventConversion.md +0 -0
  70. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/AdapterStandards/README.md +0 -0
  71. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/CLI.md +0 -0
  72. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/Development/CLI.md +0 -0
  73. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/Development/README.md +0 -0
  74. /erispulse-2.1.13rc2/style_guide/DOCSTRING_SPEC.md → /erispulse-2.1.14/docs/StyleGuide/DocstringSpec.md +0 -0
  75. {erispulse-2.1.13rc2 → erispulse-2.1.14}/docs/quick-start.md +0 -0
  76. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-adapter/LICENSE +0 -0
  77. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-adapter/MyAdapter/__init__.py +0 -0
  78. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-adapter/README.md +0 -0
  79. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-adapter/pyproject.toml +0 -0
  80. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-cli-module/LICENSE +0 -0
  81. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-cli-module/README.md +0 -0
  82. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-cli-module/my_cli_module/__init__.py +0 -0
  83. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-cli-module/my_cli_module/cli.py +0 -0
  84. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-cli-module/pyproject.toml +0 -0
  85. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-module/LICENSE +0 -0
  86. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-module/MyModule/__init__.py +0 -0
  87. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-module/README.md +0 -0
  88. {erispulse-2.1.13rc2 → erispulse-2.1.14}/examples/example-module/pyproject.toml +0 -0
  89. {erispulse-2.1.13rc2 → erispulse-2.1.14}/src/ErisPulse/Core/mods.py +0 -0
@@ -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
+ """)
@@ -1,4 +1,4 @@
1
- name: 🎀 艾莉丝的版本标签魔法 ~
1
+ name: 🎀🎀🎀 艾莉丝的版本标签魔法 ~
2
2
 
3
3
  permissions:
4
4
  contents: write
@@ -33,12 +33,16 @@ jobs:
33
33
 
34
34
  commit_msg=$(git log -1 --pretty=%B)
35
35
  commit_short=$(git rev-parse --short HEAD)
36
+ commit_author=$(git log -1 --pretty=%an)
37
+ commit_email=$(git log -1 --pretty=%ae)
36
38
  echo "commit_short=$commit_short" >> $GITHUB_OUTPUT
39
+ echo "commit_author=$commit_author" >> $GITHUB_OUTPUT
40
+ echo "commit_email=$commit_email" >> $GITHUB_OUTPUT
37
41
  echo "commit_msg<<EOF" >> $GITHUB_OUTPUT
38
42
  echo "$commit_msg" >> $GITHUB_OUTPUT
39
43
  echo "EOF" >> $GITHUB_OUTPUT
40
44
 
41
- - name: 获取变更文件列表
45
+ - name: 获取变更文件列表和贡献者
42
46
  id: changed_files
43
47
  shell: bash
44
48
  run: |
@@ -52,15 +56,21 @@ jobs:
52
56
  echo "before=$before" >> $GITHUB_OUTPUT
53
57
  echo "after=$after" >> $GITHUB_OUTPUT
54
58
 
59
+ # 获取变更文件列表
55
60
  files=$(git diff --name-only $before..$after | head -n 50 | xargs)
56
61
  file_count=$(git diff --name-only $before..$after | wc -l)
57
62
 
58
63
  if [ "$file_count" -gt 50 ]; then
59
64
  files="$files ..."
60
65
  fi
61
-
62
66
  echo "files=$files" >> $GITHUB_OUTPUT
63
67
 
68
+ # 获取贡献者列表
69
+ contributors=$(git log $before..$after --format="%an <%ae>" | sort -u)
70
+ echo "contributors<<EOF" >> $GITHUB_OUTPUT
71
+ echo "$contributors" >> $GITHUB_OUTPUT
72
+ echo "EOF" >> $GITHUB_OUTPUT
73
+
64
74
  - name: 施展版本魔法
65
75
  env:
66
76
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -68,19 +78,37 @@ jobs:
68
78
  run: |
69
79
  version="${{ steps.info.outputs.version }}"
70
80
  commit_short="${{ steps.info.outputs.commit_short }}"
81
+ commit_author="${{ steps.info.outputs.commit_author }}"
82
+ commit_email="${{ steps.info.outputs.commit_email }}"
71
83
  commit_msg="${{ steps.info.outputs.commit_msg }}"
72
84
  is_dev="${{ steps.info.outputs.is_dev }}"
85
+ contributors="${{ steps.changed_files.outputs.contributors }}"
73
86
  tag_name="v$version"
74
87
 
88
+ # 构建发布说明
75
89
  if [ -f "docs/CHANGELOG.md" ]; then
76
90
  changelog=$(sed -n "/^## \[$version\]/,/^## /p" docs/CHANGELOG.md | sed '1d;$d')
77
91
  fi
92
+
78
93
  if [ -z "$changelog" ]; then
79
- changelog="### 魔法更新\n\n- $commit_msg ($commit_short)"
94
+ changelog="### ✨✨ 魔法更新\n\n- $commit_msg ($commit_short)\n\n"
95
+ else
96
+ changelog="$changelog\n\n"
80
97
  fi
98
+
99
+ # 添加贡献者信息
100
+ changelog="${changelog}### 🧙‍♀️ 魔法贡献者\n\n"
101
+ while IFS= read -r contributor; do
102
+ changelog="${changelog}- ${contributor}\n"
103
+ done <<< "$contributors"
104
+
105
+ # 添加提交信息
106
+ changelog="${changelog}\n### 📜 魔法卷轴更新\n\n"
107
+ changelog="${changelog}- 最新提交: $commit_msg (by $commit_author, $commit_short)\n"
108
+ changelog="${changelog}- 变更文件: ${{ steps.changed_files.outputs.files }}\n"
81
109
 
82
110
  if ! git rev-parse "$tag_name" >/dev/null 2>&1; then
83
- echo "🎀 施加新的魔法印记..."
111
+ echo "🎀🎀 施加新的魔法印记..."
84
112
  git tag "$tag_name"
85
113
  git push origin "$tag_name"
86
114
  fi
@@ -97,3 +125,4 @@ jobs:
97
125
  --prerelease=$is_dev
98
126
  echo "✨ 已发布新的魔法版本 $tag_name"
99
127
  fi
128
+