Files
AgentLab/scripts/check-docs.py
T
ThearchyHelios 2714890d0d 文档重新梳理:README 精简成介绍和索引,细节按主题拆进 docs/zh-CN 与 docs/en(17 篇一一对应),英文文档不含中文
README.zh-CN.md / README.md 只留:这是什么、为什么用它、主要功能(按领域一行一条,链到文档)、快速开始、部署、文档索引、
开发与测试、许可(原文不动)。原来的每一节按读者和任务分到 17 篇主题文档:ask-data、workflows、evidence、
data-sources(新增:接入数据库、只读保证、上传表格、跨库合并、遮罩列)、data-catalog、tools(新增:工具、审批档位、
能力登记、端点、网络规则——它们管所有工作流,不只应用)、apps、evaluation-and-ledger、schedules(含通知)、deployment、
configuration、postgresql-and-scaling、identity-and-security、architecture、development、upgrade-notes(按时间倒序,
每波先写要做的事)、faq。删掉的只有模块名(api/copilot_gen.py、chat/drive.py、data/schema_view.py)、重复的话和
「以前…现在…」的叙述;操作步骤、命令、配置名、限制、权限边界都有归宿。中文是原文,英文按新的中文写;英文里界面文字
只写英文名,开头注明界面目前只有简体中文,中文占位符换成英文占位符,中文数字的例子改成不依赖中文的说法。
数据剖析可以同时剖析 2–3 张表(baf23998)收进 data-catalog。
新增 scripts/check-docs.py:英文文档(README.md、docs/en/**)不许有中日韩字符和全角符号(代码块也算),
README、docs/**、deploy/README.md 的相对链接和锚点都要通,docs/zh-CN 与 docs/en 文件一一对应、标题层级相同。
中文提示里指向 README 章节的地方改指新文档:cluster/guard.py、cluster/version.py、db/base.py 两处报错,
data/schema_view.py 文档串,.env.example 三处,scripts/prod.sh、scripts/test-postgres.sh,terms.ts 两处、
DataSourcesTab.tsx、check-manage.mjs 注释,deploy/README.md 三处链接。deploy/agentlab.env 未动。
验收:check-docs 0 问题(英文文档 18 个 0 处中文,相对链接 369 条 0 断链,18 对标题层级一致);相关后端测试
117 通过、7 跳过;tsc -b、check-copy 通过。
2026-10-21 10:23:26 +08:00

138 lines
5.7 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""文档检查:英文文档不许有中文、链接和锚点都要通、中英两套文档一一对应。
python3 scripts/check-docs.py
1. README.md 和 docs/en/** 里不许出现中日韩字符和全角符号(代码块也算),范围是
U+3000–U+303F、U+3400–U+4DBF、U+4E00–U+9FFF、U+F900–U+FAFF、U+FF00–U+FFEF。
2. README.md、README.zh-CN.md、docs/**/*.md、deploy/README.md 里的相对链接:文件要存在,带 #锚点 的要对得上
目标文件的标题(GitHub 的规则:小写,去掉字母、数字、空格、连字符、下划线以外的字符,空格换成 -,同名标题
依次加 -1、-2)。代码块里的标题不算标题,代码块和行内代码里的链接不算链接。
3. docs/zh-CN 和 docs/en 的文件名一一对应,每一对的标题层级序列相同。
有问题时逐条列出、退出码 1。
"""
import os
import re
import sys
import unicodedata
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CJK = re.compile("[ -〿㐀-䶿一-鿿豈-﫿＀-￯]")
HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$")
LINK = re.compile(r"\]\(([^)\s]+)\)")
CODE_SPAN = re.compile(r"(`+)(.+?)\1")
def md_files():
out = [os.path.join(ROOT, "README.md"), os.path.join(ROOT, "README.zh-CN.md"),
os.path.join(ROOT, "deploy", "README.md")]
for base, _dirs, files in os.walk(os.path.join(ROOT, "docs")):
out += [os.path.join(base, f) for f in sorted(files) if f.endswith(".md")]
return [p for p in out if os.path.exists(p)]
def slug_of(text):
text = CODE_SPAN.sub(lambda m: m.group(2), text)
text = re.sub(r"!?\[([^\]]*)\]\([^)]*\)", r"\1", text)
text = re.sub(r"[*_]{1,3}([^*_]+)[*_]{1,3}", r"\1", text)
kept = [ch for ch in text.strip().lower()
if ch in " -" or unicodedata.category(ch)[0] in "LMN" or unicodedata.category(ch) == "Pc"]
return "".join(kept).replace(" ", "-")
def scan(path):
"""返回 (锚点集合, 标题层级序列, 链接 [(行号, 目标)])。"""
anchors, seen, levels, links = set(), {}, [], []
fence = None
with open(path, encoding="utf-8") as f:
for no, line in enumerate(f, 1):
s = line.strip()
m = re.match(r"^(`{3,}|~{3,})", s)
if m:
if fence is None:
fence = m.group(1)[0] * 3
elif s.startswith(fence):
fence = None
continue
if fence:
continue
h = HEADING.match(line.rstrip("\n"))
if h:
levels.append(len(h.group(1)))
base = slug_of(h.group(2))
slug = base
while slug in seen:
seen[base] += 1
slug = "%s-%d" % (base, seen[base])
seen[slug] = 0
anchors.add(slug)
for target in LINK.findall(CODE_SPAN.sub("", line)):
links.append((no, target))
return anchors, levels, links
def main():
problems = []
rel = lambda p: os.path.relpath(p, ROOT)
# 1. 英文文档里的中文
english = [os.path.join(ROOT, "README.md")]
for base, _dirs, files in os.walk(os.path.join(ROOT, "docs", "en")):
english += [os.path.join(base, f) for f in sorted(files) if f.endswith(".md")]
cjk_hits = 0
for path in english:
with open(path, encoding="utf-8") as f:
for no, line in enumerate(f, 1):
for m in CJK.finditer(line):
cjk_hits += 1
problems.append("%s:%d:%d 英文文档里有中文字符 %r" % (rel(path), no, m.start() + 1, m.group(0)))
# 2. 链接和锚点
cache = {}
def info(p):
if p not in cache:
cache[p] = scan(p)
return cache[p]
link_count = 0
files = md_files()
for path in files:
anchors, _levels, links = info(path)
for no, target in links:
if re.match(r"^[a-z][a-z0-9+.-]*:", target): # http:、mailto: 之类的外链不查
continue
link_count += 1
file_part, _, frag = target.partition("#")
dest = os.path.normpath(os.path.join(os.path.dirname(path), file_part)) if file_part else path
if not os.path.exists(dest):
problems.append("%s:%d 链接的文件不存在:%s" % (rel(path), no, target))
elif frag and dest.endswith(".md") and frag not in info(dest)[0]:
problems.append("%s:%d 锚点对不上标题:%s" % (rel(path), no, target))
# 3. 中英两套文档一一对应
zh_dir, en_dir = os.path.join(ROOT, "docs", "zh-CN"), os.path.join(ROOT, "docs", "en")
zh = sorted(f for f in os.listdir(zh_dir) if f.endswith(".md")) if os.path.isdir(zh_dir) else []
en = sorted(f for f in os.listdir(en_dir) if f.endswith(".md")) if os.path.isdir(en_dir) else []
for name in sorted(set(zh) ^ set(en)):
problems.append("docs/zh-CN 和 docs/en 的文件对不上:%s 只在一边" % name)
pairs = sorted(set(zh) & set(en)) + [None]
for name in pairs:
if name is None:
a, b = os.path.join(ROOT, "README.zh-CN.md"), os.path.join(ROOT, "README.md")
else:
a, b = os.path.join(zh_dir, name), os.path.join(en_dir, name)
if info(a)[1] != info(b)[1]:
problems.append("标题层级对不上:%s %s ↔ %s %s" % (rel(a), info(a)[1], rel(b), info(b)[1]))
for p in problems:
print(p)
print("英文文档 %d 个,中文字符 %d 处;Markdown 文件 %d 个,相对链接 %d 条;中英文档 %d 对;问题 %d 条"
% (len(english), cjk_hits, len(files), link_count, len(pairs), len(problems)))
return 1 if problems else 0
if __name__ == "__main__":
sys.exit(main())