Files
XCEngine/docs/api/fix_links.py
ssdfasd dc850d7739 docs: 重构 API 文档结构并修正源码准确性
- 重组文档目录结构: 每个模块的概述页移动到模块子目录
- 重命名 index.md 为 main.md
- 修正所有模块文档中的错误:
  - math: FromEuler→FromEulerAngles, TransformDirection 包含缩放, Box 是 OBB, Color::ToRGBA 格式
  - containers: 新增 operator==/!= 文档, 补充 std::hash DJB 算法细节
  - core: 修复 types 链接错误
  - debug: LogLevelToString 返回大写, timestamp 是秒, Profiler 空实现标注, Windows API vs ANSI
  - memory: 修复头文件路径, malloc vs operator new, 新增方法文档
  - resources: 修复 Shader/Texture 链接错误
  - threading: TaskSystem::Wait 空实现标注, ReadWriteLock 重入描述, LambdaTask 链接
- 验证: fix_links.py 确认 0 个断裂引用
2026-03-19 00:22:30 +08:00

239 lines
6.7 KiB
Python

#!/usr/bin/env python3
"""
Fix broken cross-references in API documentation.
"""
import os
import re
from pathlib import Path
API_DOCS_ROOT = Path(r"D:\Xuanchi\Main\XCEngine\docs\api")
def normalize_path(path_str):
"""Remove duplicate path segments like ./module/./module/."""
parts = []
for part in path_str.replace("\\", "/").split("/"):
if part == "." or part == "":
continue
parts.append(part)
return "/".join(parts)
def resolve_reference(current_file, ref_path):
"""Resolve a reference path relative to current file and check if it exists."""
current_file = Path(current_file)
parent_dir = current_file.parent
# Normalize the reference path
normalized_ref = normalize_path(ref_path)
# Try to resolve the path
resolved = (parent_dir / normalized_ref).resolve()
# Also try with normalized path
resolved2 = (parent_dir / ref_path.replace("\\", "/")).resolve()
return resolved, resolved2
def get_relative_path(from_file, to_file):
"""Get correct relative path from one file to another."""
from_file = Path(from_file)
to_file = Path(to_file)
# Get common prefix
from_parts = from_file.parts
to_parts = to_file.parts
# Find common prefix length
common_len = 0
for i in range(min(len(from_parts), len(to_parts))):
if from_parts[i] == to_parts[i]:
common_len = i + 1
else:
break
# Build relative path
up_count = len(from_parts) - common_len - 1 # -1 for the filename
parts = [".."] * up_count + list(to_parts[common_len:])
return "/".join(parts)
def find_file_by_name(target_name, base_dir):
"""Find a file with given name in the base directory."""
base_dir = Path(base_dir)
api_docs = API_DOCS_ROOT
# Search for the file
pattern = f"**/{target_name}"
matches = list(api_docs.glob(pattern))
# Filter for exact filename match
for match in matches:
if match.name == target_name:
return match
return None
def fix_links_in_file(file_path, verbose=True):
"""Analyze and fix links in a single file."""
file_path = Path(file_path)
if not file_path.exists():
return []
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
original_content = content
fixes = []
# Find all markdown links: [text](path)
link_pattern = r"\[([^\]]*)\]\(([^)]+\.md)\)"
def replace_link(match):
link_text = match.group(1)
link_path = match.group(2)
# Skip external links
if link_path.startswith("http://") or link_path.startswith("https://"):
return match.group(0)
# Skip anchor links
if link_path.startswith("#"):
return match.group(0)
# Normalize the path
normalized = normalize_path(link_path)
# Try to resolve the target file
parent_dir = file_path.parent
# Try the exact path first
target_path = parent_dir / link_path.replace("\\", "/")
target_exists = target_path.exists()
if not target_exists:
# Try normalized path
target_path = parent_dir / normalized
target_exists = target_path.exists()
if not target_exists:
# Try to find the file elsewhere
# Get just the filename
filename = Path(normalized).name
# Try to find it
found = find_file_by_name(filename, API_DOCS_ROOT)
if found:
# Calculate correct relative path
correct_rel = get_relative_path(file_path, found)
if correct_rel != normalized:
fixes.append(
{
"file": file_path,
"line": content[: match.start()].count("\n") + 1,
"old": link_path,
"new": correct_rel,
"target": found,
}
)
return f"[{link_text}]({correct_rel})"
else:
fixes.append(
{
"file": file_path,
"line": content[: match.start()].count("\n") + 1,
"old": link_path,
"new": None,
"target": None,
"error": "Target file not found",
}
)
return match.group(0)
elif normalized != link_path:
# Path has duplicate segments that need fixing
fixes.append(
{
"file": file_path,
"line": content[: match.start()].count("\n") + 1,
"old": link_path,
"new": normalized,
"target": target_path,
}
)
return f"[{link_text}]({normalized})"
return match.group(0)
new_content = re.sub(link_pattern, replace_link, content)
if new_content != original_content:
with open(file_path, "w", encoding="utf-8") as f:
f.write(new_content)
return fixes
def main():
print("=" * 70)
print("API Documentation Cross-Reference Fixer")
print("=" * 70)
# Collect all markdown files
md_files = list(API_DOCS_ROOT.glob("**/*.md"))
print(f"\nFound {len(md_files)} markdown files in {API_DOCS_ROOT}")
all_fixes = []
broken_refs = []
for md_file in md_files:
fixes = fix_links_in_file(md_file, verbose=False)
for fix in fixes:
if fix.get("error"):
broken_refs.append(fix)
else:
all_fixes.append(fix)
print(f"\n{'=' * 70}")
print("FIXES APPLIED:")
print("=" * 70)
if all_fixes:
for fix in all_fixes:
rel_file = fix["file"].relative_to(API_DOCS_ROOT)
print(f"\n File: {rel_file}")
print(f" Line: {fix['line']}")
print(f" Old: {fix['old']}")
print(f" New: {fix['new']}")
else:
print("\n No fixes needed.")
print(f"\n{'=' * 70}")
print("BROKEN REFERENCES (target files don't exist):")
print("=" * 70)
if broken_refs:
for ref in broken_refs:
rel_file = ref["file"].relative_to(API_DOCS_ROOT)
print(f"\n File: {rel_file}")
print(f" Line: {ref['line']}")
print(f" Broken ref: {ref['old']}")
else:
print("\n No broken references found.")
print(f"\n{'=' * 70}")
print(f"SUMMARY:")
print(f" Total fixes applied: {len(all_fixes)}")
print(f" Broken references: {len(broken_refs)}")
print("=" * 70)
return len(all_fixes), len(broken_refs)
if __name__ == "__main__":
main()