第十版
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""检测 Rust 源码文件头部是否有 MIT 协议,若无则自动注入。
|
||||
|
||||
用法:
|
||||
python3 check_license.py # 检测 chanlun/ 和 chanlun-py/ 下所有 .rs
|
||||
python3 check_license.py --check-only # 仅检测,不修改
|
||||
python3 check_license.py --fix # 检测并修复
|
||||
python3 check_license.py path/to/dir # 指定目录
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_repo_root() -> Path:
|
||||
"""从脚本位置向上查找仓库根目录(含 LICENSE 文件的目录)。"""
|
||||
current = Path(__file__).resolve().parent
|
||||
while current != current.parent:
|
||||
if (current / "LICENSE").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
# Fallback: 脚本所在目录的父目录
|
||||
return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def build_license_header(license_path: Path) -> str:
|
||||
"""读取 LICENSE 文件并格式化为 Rust 块注释头。"""
|
||||
lines = license_path.read_text(encoding="utf-8").rstrip("\n").split("\n")
|
||||
header_lines = ["/*"]
|
||||
for line in lines:
|
||||
if line.strip():
|
||||
header_lines.append(f" * {line}")
|
||||
else:
|
||||
header_lines.append(" *")
|
||||
header_lines.append(" */")
|
||||
header_lines.append("") # 末尾空行分隔
|
||||
return "\n".join(header_lines) + "\n"
|
||||
|
||||
|
||||
def has_license_header(file_path: Path) -> bool:
|
||||
"""检测文件头部是否已包含块注释风格的 MIT License。"""
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
head = f.read(512)
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return True
|
||||
return head.lstrip().startswith("/*") and "MIT License" in head
|
||||
|
||||
|
||||
def strip_old_license_header(text: str) -> str:
|
||||
"""去除文件中已有的 // 风格 license header(重新注入前调用)。"""
|
||||
stripped = text.lstrip("\n")
|
||||
if stripped.startswith("// MIT License"):
|
||||
# 找到 // 注释块结束位置(第一个非 // 非空行)
|
||||
lines = stripped.split("\n")
|
||||
end_idx = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("//") or line.strip() == "":
|
||||
end_idx = i + 1
|
||||
else:
|
||||
break
|
||||
return "\n".join(lines[end_idx:])
|
||||
return text
|
||||
|
||||
|
||||
def inject_license(file_path: Path, header: str) -> bool:
|
||||
"""将 license header 注入文件头部。返回 True 表示已修改。"""
|
||||
original = file_path.read_text(encoding="utf-8")
|
||||
# 已有块注释风格则跳过
|
||||
if original.lstrip().startswith("/*") and "MIT License" in original[:512]:
|
||||
return False
|
||||
# 去除旧的 // 风格 header(如果存在)
|
||||
cleaned = strip_old_license_header(original)
|
||||
file_path.write_text(header + cleaned, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def collect_rs_files(roots: list[Path]) -> list[Path]:
|
||||
"""递归收集所有 .rs 文件,排除 target/ 等构建产物目录。"""
|
||||
exclude_dirs = {"target", ".git", "__pycache__", "dist", "build", ".venv", "venv"}
|
||||
files = []
|
||||
for root in roots:
|
||||
if not root.is_dir():
|
||||
continue
|
||||
for path in root.rglob("*.rs"):
|
||||
if any(excl in path.parts for excl in exclude_dirs):
|
||||
continue
|
||||
files.append(path)
|
||||
return sorted(files)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="检测 Rust 源码 MIT 协议头")
|
||||
parser.add_argument(
|
||||
"paths",
|
||||
nargs="*",
|
||||
help="要检测的目录(默认: chanlun 和 chanlun-py 源码目录)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--check-only",
|
||||
action="store_true",
|
||||
help="仅检测,不修改文件",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fix",
|
||||
action="store_true",
|
||||
help="检测并自动注入缺失的协议头(默认行为)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = find_repo_root()
|
||||
license_path = repo_root / "LICENSE"
|
||||
|
||||
if not license_path.exists():
|
||||
print(f"错误: 未找到 LICENSE 文件 ({license_path})", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
header = build_license_header(license_path)
|
||||
|
||||
# 确定扫描目录
|
||||
if args.paths:
|
||||
roots = [Path(p).resolve() for p in args.paths]
|
||||
else:
|
||||
roots = [
|
||||
repo_root / "chanlun" / "src",
|
||||
repo_root / "chanlun-py" / "src",
|
||||
repo_root / "chanlun" / "tests",
|
||||
repo_root / "chanlun-py" / "tests",
|
||||
]
|
||||
roots = [r for r in roots if r.is_dir()]
|
||||
|
||||
if not roots:
|
||||
print("错误: 未找到任何源码目录", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
rs_files = collect_rs_files(roots)
|
||||
|
||||
if not rs_files:
|
||||
print("未找到 .rs 文件")
|
||||
return 0
|
||||
|
||||
missing = []
|
||||
injected = []
|
||||
|
||||
for f in rs_files:
|
||||
if has_license_header(f):
|
||||
continue
|
||||
missing.append(f)
|
||||
if not args.check_only:
|
||||
if inject_license(f, header):
|
||||
injected.append(f)
|
||||
|
||||
if args.check_only:
|
||||
if missing:
|
||||
print(f"缺失 MIT 协议头: {len(missing)} 个文件")
|
||||
for f in missing:
|
||||
print(f" {f}")
|
||||
return 1
|
||||
else:
|
||||
print(f"全部 {len(rs_files)} 个 .rs 文件均已包含 MIT 协议头")
|
||||
return 0
|
||||
else:
|
||||
if injected:
|
||||
print(f"已注入 MIT 协议头: {len(injected)} 个文件")
|
||||
for f in injected:
|
||||
print(f" {f}")
|
||||
if missing:
|
||||
already = len(missing) - len(injected)
|
||||
if already > 0:
|
||||
print(f"已有协议头: {already} 个文件(无需修改)")
|
||||
total = len(rs_files) - len(missing)
|
||||
print(f"总计: {len(rs_files)} 个 .rs 文件, {total} 个已含协议头")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user