126 lines
3.5 KiB
Python
126 lines
3.5 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
自动生成 README.md:扫描当前目录下的 Python 脚本和可执行包,
|
|||
|
|
提取其 docstring 并更新到 README 中。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import ast
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
# 当前脚本所在目录
|
|||
|
|
SCRIPT_DIR = Path(__file__).parent.resolve()
|
|||
|
|
|
|||
|
|
# 输出 README 路径
|
|||
|
|
README_PATH = SCRIPT_DIR / "README.md"
|
|||
|
|
|
|||
|
|
# 模板路径(可选)
|
|||
|
|
TEMPLATE_PATH = SCRIPT_DIR / "README.template.md"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def extract_docstring_from_file(file_path: Path) -> str:
|
|||
|
|
"""从 Python 文件中提取模块级 docstring。"""
|
|||
|
|
try:
|
|||
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|||
|
|
node = ast.parse(f.read())
|
|||
|
|
return ast.get_docstring(node) or "(无文档说明)"
|
|||
|
|
except Exception as e:
|
|||
|
|
return f"(解析失败: {e})"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def collect_items():
|
|||
|
|
"""收集当前目录下所有 .py 文件和含 __main__.py 的子目录。"""
|
|||
|
|
items = []
|
|||
|
|
|
|||
|
|
for item in SCRIPT_DIR.iterdir():
|
|||
|
|
if item.name == Path(__file__).name:
|
|||
|
|
continue # 跳过本脚本
|
|||
|
|
if item.name.startswith("."):
|
|||
|
|
continue # 跳过隐藏文件/目录
|
|||
|
|
|
|||
|
|
if item.is_file() and item.suffix == ".py":
|
|||
|
|
name = item.stem
|
|||
|
|
doc = extract_docstring_from_file(item)
|
|||
|
|
items.append({"type": "script", "name": name, "doc": doc, "path": item.name})
|
|||
|
|
|
|||
|
|
elif item.is_dir():
|
|||
|
|
main_py = item / "__main__.py"
|
|||
|
|
if main_py.exists():
|
|||
|
|
name = item.name
|
|||
|
|
doc = extract_docstring_from_file(main_py)
|
|||
|
|
items.append({"type": "package", "name": name, "doc": doc, "path": str(item.name) + "/__main__.py"})
|
|||
|
|
|
|||
|
|
# 按名称排序
|
|||
|
|
items.sort(key=lambda x: x["name"].lower())
|
|||
|
|
return items
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load_template() -> str:
|
|||
|
|
"""加载模板:优先用 README.template.md,否则用内置模板。"""
|
|||
|
|
if TEMPLATE_PATH.exists():
|
|||
|
|
with open(TEMPLATE_PATH, "r", encoding="utf-8") as f:
|
|||
|
|
return f.read()
|
|||
|
|
else:
|
|||
|
|
# 内置默认模板
|
|||
|
|
return """# 工具集
|
|||
|
|
|
|||
|
|
本目录包含以下可执行脚本与工具包:
|
|||
|
|
|
|||
|
|
{content}
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
> 自动由 `{script_name}` 生成
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def generate_content(items):
|
|||
|
|
"""根据 items 生成 Markdown 内容。"""
|
|||
|
|
if not items:
|
|||
|
|
return "> 暂无 Python 脚本或可执行包。\n"
|
|||
|
|
|
|||
|
|
lines = []
|
|||
|
|
for item in items:
|
|||
|
|
title = f"`{item['name']}`"
|
|||
|
|
if item["type"] == "package":
|
|||
|
|
title += " (包)"
|
|||
|
|
lines.append(f"## {title}")
|
|||
|
|
lines.append("")
|
|||
|
|
# 渲染 docstring(保留原始换行,但缩进处理)
|
|||
|
|
doc = item["doc"].strip()
|
|||
|
|
if doc:
|
|||
|
|
# 如果 docstring 是多行,用引用块或直接段落
|
|||
|
|
if "\n" in doc:
|
|||
|
|
lines.append("```text")
|
|||
|
|
lines.append(doc)
|
|||
|
|
lines.append("```")
|
|||
|
|
else:
|
|||
|
|
lines.append(doc)
|
|||
|
|
else:
|
|||
|
|
lines.append("(无文档说明)")
|
|||
|
|
lines.append("")
|
|||
|
|
lines.append(f"> 文件路径: `{item['path']}`")
|
|||
|
|
lines.append("")
|
|||
|
|
return "\n".join(lines)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
items = collect_items()
|
|||
|
|
template = load_template()
|
|||
|
|
content = generate_content(items)
|
|||
|
|
readme_text = template.format(
|
|||
|
|
content=content,
|
|||
|
|
script_name=Path(__file__).name
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
with open(README_PATH, "w", encoding="utf-8") as f:
|
|||
|
|
f.write(readme_text)
|
|||
|
|
|
|||
|
|
print(f"✅ README.md 已更新,共收录 {len(items)} 个项目。")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|
|||
|
|
|