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()
|
||
|