Repository navigation
Expand file tree
/
Copy pathvalidate_docs.py
More file actions
176 lines (147 loc) · 5.38 KB
/
Copy pathvalidate_docs.py
File metadata and controls
176 lines (147 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""Offline validation for the Codex Help documentation repository."""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EN_GUIDE = ROOT / "docs" / "en-US.md"
ZH_GUIDE = ROOT / "docs" / "zh-CN.md"
SOURCE_CHECK = ROOT / "docs" / "source-check.md"
URL_RE = re.compile(r"https?://[^)\s]+")
DATE_HEADING_RE = re.compile(
r"^## Official update delta: [A-Z][a-z]+ \d{1,2}, \d{4}$", re.MULTILINE
)
PLACEHOLDER_RE = re.compile(
r"\[\u9879\u76ee\u540d\u79f0\]|\[\u586b\u5199[^\]]*\]|\u8bf7\u8865\u5145|\bTBD\b",
re.IGNORECASE,
)
OFFICIAL_PREFIXES = (
"https://learn.chatgpt.com/docs/",
"https://developers.openai.com/codex/",
"https://developers.openai.com/api/",
"https://github.com/openai/codex/",
)
STRUCTURED_TOKENS = (
"gpt-5.6",
"0.148.0",
"chatgpt work",
"10,000",
)
def read(path: Path) -> str:
return path.read_text(encoding="utf-8-sig")
def update_section(text: str, language: str) -> str:
marker = "official updates" if language == "en" else "\u5b98\u65b9\u66f4\u65b0"
lines = text.splitlines()
start = next(
(
index
for index, line in enumerate(lines)
if line.startswith("## ") and marker in line.lower()
),
None,
)
if start is None:
raise ValueError(f"missing update section: {language}")
end = next(
(index for index in range(start + 1, len(lines)) if lines[index].startswith("## ")),
len(lines),
)
return "\n".join(lines[start:end])
def urls(text: str) -> set[str]:
return {match.rstrip(".,;:!?\"'\\") for match in URL_RE.findall(text)}
def source_delta_section(text: str) -> str:
lines = text.splitlines()
start = next(
(
index
for index, line in enumerate(lines)
if line.startswith("### Official sources for this delta")
),
None,
)
if start is None:
raise ValueError("missing official source list for the current delta")
end = next(
(index for index in range(start + 1, len(lines)) if lines[index].startswith("## ")),
len(lines),
)
return "\n".join(lines[start:end])
def validate() -> list[str]:
errors: list[str] = []
for path in (EN_GUIDE, ZH_GUIDE, SOURCE_CHECK):
if not path.is_file():
errors.append(f"missing required file: {path.relative_to(ROOT)}")
if errors:
return errors
en = read(EN_GUIDE)
zh = read(ZH_GUIDE)
source_check = read(SOURCE_CHECK)
try:
en_update = update_section(en, "en")
zh_update = update_section(zh, "zh")
except ValueError as exc:
errors.append(str(exc))
en_update = zh_update = ""
if en_update and zh_update:
en_bullets = len(re.findall(r"^- ", en_update, re.MULTILINE))
zh_bullets = len(re.findall(r"^- ", zh_update, re.MULTILINE))
if en_bullets == 0 or zh_bullets == 0:
errors.append("current update sections must contain at least one bullet")
elif en_bullets != zh_bullets:
errors.append(
f"bilingual update bullet counts differ: en={en_bullets}, zh={zh_bullets}"
)
if urls(en_update) != urls(zh_update):
errors.append(
"official URLs in the current English and Chinese update sections differ"
)
en_lower = en_update.lower()
zh_lower = zh_update.lower()
for token in STRUCTURED_TOKENS:
if token not in en_lower or token not in zh_lower:
errors.append(f"structured update token is not bilingual: {token}")
if not DATE_HEADING_RE.search(source_check):
errors.append("source-check is missing a dated official update delta heading")
try:
delta_sources = source_delta_section(source_check)
except ValueError as exc:
errors.append(str(exc))
delta_sources = ""
if delta_sources:
source_urls = urls(delta_sources)
if len(source_urls) < 5:
errors.append("current source list contains fewer than five official URLs")
invalid_sources = sorted(
url for url in source_urls if not url.startswith(OFFICIAL_PREFIXES)
)
if invalid_sources:
errors.append(
"current source list contains non-official URLs: "
+ ", ".join(invalid_sources)
)
maintained_docs = (
ROOT / "README.md",
ROOT / "README.zh-CN.md",
EN_GUIDE,
ZH_GUIDE,
SOURCE_CHECK,
)
for path in maintained_docs:
if PLACEHOLDER_RE.search(read(path)):
errors.append(f"template placeholder found in {path.relative_to(ROOT)}")
if "https://learn.chatgpt.com/docs/" not in source_check:
errors.append("source-check does not record learn.chatgpt.com documentation")
if "https://developers.openai.com/codex/" not in source_check:
errors.append("source-check does not record developers.openai.com Codex documentation")
return errors
def main() -> int:
errors = validate()
if errors:
print("Documentation validation failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
return 1
print("Documentation validation passed: sources and bilingual structure are aligned.")
return 0
if __name__ == "__main__":
raise SystemExit(main())