642 lines
22 KiB
Python
642 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from generate_core_resources_canonical_pages import (
|
|
DOC_ROOT,
|
|
INCLUDE_ROOT,
|
|
REPO_ROOT,
|
|
build_namespace_map,
|
|
find_declarations,
|
|
group_methods,
|
|
select_primary,
|
|
)
|
|
|
|
|
|
TEMPLATE_METHOD_TOKENS = (
|
|
"当前页面用于固定",
|
|
"获取相关状态或对象。",
|
|
"设置相关状态或配置。",
|
|
"加载资源或数据。",
|
|
"公开方法,详见头文件声明。",
|
|
"参数语义详见头文件声明。",
|
|
"返回值语义详见头文件声明。",
|
|
)
|
|
TEMPLATE_TABLE_TOKENS = (
|
|
"获取相关状态或对象。",
|
|
"设置相关状态或配置。",
|
|
"加载资源或数据。",
|
|
"公开方法,详见头文件声明。",
|
|
)
|
|
GENERIC_FALLBACK_RE = re.compile(
|
|
r"执行该公开方法对应的当前实现。|"
|
|
r"返回 `[^`]+` 相关结果。|"
|
|
r"更新 `[^`]+` 相关状态。|"
|
|
r"判断 `[^`]+` 条件是否成立。|"
|
|
r"判断是否具备 `[^`]+`。|"
|
|
r"判断当前是否可以执行 `[^`]+`。"
|
|
)
|
|
METHOD_SECTION_RE = re.compile(r"(?ms)^## 公共方法\n.*?(?=\n## |\Z)")
|
|
BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
LINE_COMMENT_RE = re.compile(r"//.*")
|
|
RETURN_MEMBER_RE = re.compile(r"^return\s+(m_[A-Za-z_]\w*)\s*;?$")
|
|
RETURN_MEMBER_METHOD_RE = re.compile(
|
|
r"^return\s+(m_[A-Za-z_]\w*)\.(Data|data|Size|size|CStr|c_str|Get)\s*\((.*?)\)\s*;?$"
|
|
)
|
|
RETURN_SIMPLE_CALL_RE = re.compile(r"^return\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)\s*\((.*)\)\s*;?$")
|
|
RETURN_CONST_RE = re.compile(r"^return\s+([^;]+?)\s*;?$")
|
|
ASSIGN_MEMBER_RE = re.compile(r"^(m_[A-Za-z_]\w*)\s*=\s*([^;]+?)\s*;?$")
|
|
MEMBER_WRITE_RE = re.compile(r"\b(m_[A-Za-z_]\w*)\b\s*(?:=|\+=|-=|\*=|/=|%=|>>=|<<=|\+\+|--)")
|
|
MEMBER_CALL_RE = re.compile(r"\b(m_[A-Za-z_]\w*)\.(\w+)\s*\(")
|
|
DIRECT_CALL_RE = re.compile(r"\b([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)\s*\(")
|
|
ARROW_CALL_RE = re.compile(r"->\s*([A-Za-z_]\w*)\s*\(")
|
|
DOT_CALL_RE = re.compile(r"\.\s*([A-Za-z_]\w*)\s*\(")
|
|
FIELD_NAME_RE = re.compile(r"\bm_[A-Za-z_]\w*\b")
|
|
|
|
KEYWORDS = {
|
|
"if",
|
|
"for",
|
|
"while",
|
|
"switch",
|
|
"return",
|
|
"sizeof",
|
|
"static_cast",
|
|
"reinterpret_cast",
|
|
"const_cast",
|
|
"dynamic_cast",
|
|
"catch",
|
|
"new",
|
|
"delete",
|
|
}
|
|
MUTATING_MEMBER_CALLS = {
|
|
"Append",
|
|
"Assign",
|
|
"Clear",
|
|
"ClearDirty",
|
|
"Emplace",
|
|
"Erase",
|
|
"Insert",
|
|
"PopBack",
|
|
"PushBack",
|
|
"Release",
|
|
"Remove",
|
|
"Reset",
|
|
"Resize",
|
|
"Set",
|
|
"SetInvalid",
|
|
"Shrink",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class ImplementationFact:
|
|
summary: str | None
|
|
details: list[str]
|
|
|
|
|
|
@dataclass
|
|
class ImplementationBlock:
|
|
kind: str
|
|
body: str
|
|
|
|
|
|
def has_any_token(content: str, tokens: tuple[str, ...]) -> bool:
|
|
return any(token in content for token in tokens)
|
|
|
|
|
|
def has_generic_fallback(content: str) -> bool:
|
|
return GENERIC_FALLBACK_RE.search(content) is not None
|
|
|
|
|
|
def strip_comments(text: str) -> str:
|
|
return LINE_COMMENT_RE.sub("", BLOCK_COMMENT_RE.sub("", text))
|
|
|
|
|
|
def normalize_whitespace(text: str) -> str:
|
|
return " ".join(text.strip().split())
|
|
|
|
|
|
def split_statements(body: str) -> list[str]:
|
|
parts: list[str] = []
|
|
current: list[str] = []
|
|
depth_paren = 0
|
|
depth_brace = 0
|
|
depth_angle = 0
|
|
|
|
for char in body:
|
|
if char == "(":
|
|
depth_paren += 1
|
|
elif char == ")":
|
|
depth_paren = max(0, depth_paren - 1)
|
|
elif char == "{":
|
|
depth_brace += 1
|
|
elif char == "}":
|
|
depth_brace = max(0, depth_brace - 1)
|
|
elif char == "<":
|
|
depth_angle += 1
|
|
elif char == ">":
|
|
depth_angle = max(0, depth_angle - 1)
|
|
|
|
if char == ";" and depth_paren == 0 and depth_brace == 0 and depth_angle == 0:
|
|
statement = "".join(current).strip()
|
|
if statement:
|
|
parts.append(statement)
|
|
current = []
|
|
continue
|
|
|
|
current.append(char)
|
|
|
|
tail = "".join(current).strip()
|
|
if tail:
|
|
parts.append(tail)
|
|
return parts
|
|
|
|
|
|
def camel_tail(name: str, prefix_len: int) -> str:
|
|
tail = name[prefix_len:]
|
|
return tail or name
|
|
|
|
|
|
def format_identifier_list(items: list[str]) -> str:
|
|
unique: list[str] = []
|
|
seen: set[str] = set()
|
|
for item in items:
|
|
if item in seen:
|
|
continue
|
|
unique.append(item)
|
|
seen.add(item)
|
|
return "、".join(f"`{item}`" for item in unique)
|
|
|
|
|
|
def extract_balanced(text: str, start: int, open_char: str, close_char: str) -> tuple[str, int] | None:
|
|
depth = 0
|
|
for index in range(start, len(text)):
|
|
char = text[index]
|
|
if char == open_char:
|
|
depth += 1
|
|
elif char == close_char:
|
|
depth -= 1
|
|
if depth == 0:
|
|
return text[start:index + 1], index + 1
|
|
return None
|
|
|
|
|
|
def scan_definition_suffix(text: str, start: int) -> tuple[str, int]:
|
|
index = start
|
|
while True:
|
|
while index < len(text) and text[index].isspace():
|
|
index += 1
|
|
|
|
if text.startswith("const", index):
|
|
index += len("const")
|
|
continue
|
|
if text.startswith("override", index):
|
|
index += len("override")
|
|
continue
|
|
if text.startswith("final", index):
|
|
index += len("final")
|
|
continue
|
|
if text.startswith("constexpr", index):
|
|
index += len("constexpr")
|
|
continue
|
|
if text.startswith("noexcept", index):
|
|
index += len("noexcept")
|
|
while index < len(text) and text[index].isspace():
|
|
index += 1
|
|
if index < len(text) and text[index] == "(":
|
|
extracted = extract_balanced(text, index, "(", ")")
|
|
if extracted:
|
|
_, index = extracted
|
|
continue
|
|
if index < len(text) and text[index] in {"&", "*"}:
|
|
index += 1
|
|
continue
|
|
break
|
|
|
|
if text.startswith("= default", index):
|
|
return "default", index + len("= default")
|
|
if text.startswith("= delete", index):
|
|
return "delete", index + len("= delete")
|
|
if index < len(text) and text[index] == "{":
|
|
return "body", index
|
|
return "", index
|
|
|
|
|
|
def extract_qualified_blocks(text: str, class_name: str, method_name: str) -> list[ImplementationBlock]:
|
|
pattern = re.compile(rf"{re.escape(class_name)}\s*::\s*{re.escape(method_name)}\s*\(")
|
|
blocks: list[ImplementationBlock] = []
|
|
|
|
for match in pattern.finditer(text):
|
|
params_start = text.find("(", match.start())
|
|
extracted = extract_balanced(text, params_start, "(", ")")
|
|
if not extracted:
|
|
continue
|
|
_, cursor = extracted
|
|
kind, suffix_pos = scan_definition_suffix(text, cursor)
|
|
if kind == "body":
|
|
body_block = extract_balanced(text, suffix_pos, "{", "}")
|
|
if body_block:
|
|
body, _ = body_block
|
|
blocks.append(ImplementationBlock(kind="body", body=body[1:-1]))
|
|
elif kind in {"default", "delete"}:
|
|
blocks.append(ImplementationBlock(kind=kind, body=""))
|
|
|
|
return blocks
|
|
|
|
|
|
def extract_inline_blocks(text: str, method_name: str) -> list[ImplementationBlock]:
|
|
pattern = re.compile(rf"{re.escape(method_name)}\s*\(")
|
|
blocks: list[ImplementationBlock] = []
|
|
|
|
for match in pattern.finditer(text):
|
|
previous = text[match.start() - 1] if match.start() > 0 else ""
|
|
if previous.isalnum() or previous in {":", ".", ">"}:
|
|
continue
|
|
|
|
params_start = text.find("(", match.start())
|
|
extracted = extract_balanced(text, params_start, "(", ")")
|
|
if not extracted:
|
|
continue
|
|
_, cursor = extracted
|
|
kind, suffix_pos = scan_definition_suffix(text, cursor)
|
|
if kind == "body":
|
|
body_block = extract_balanced(text, suffix_pos, "{", "}")
|
|
if body_block:
|
|
body, _ = body_block
|
|
blocks.append(ImplementationBlock(kind="body", body=body[1:-1]))
|
|
elif kind in {"default", "delete"}:
|
|
blocks.append(ImplementationBlock(kind=kind, body=""))
|
|
|
|
return blocks
|
|
|
|
|
|
def summarize_return_value(expr: str) -> str:
|
|
cleaned = normalize_whitespace(expr)
|
|
if cleaned in {"true", "false", "nullptr"}:
|
|
return f"固定返回 `{cleaned}`。"
|
|
if FIELD_NAME_RE.fullmatch(cleaned):
|
|
return f"返回 `{cleaned}` 当前值。"
|
|
if cleaned.endswith("()"):
|
|
return f"返回 `{cleaned}` 的结果。"
|
|
return f"返回 `{cleaned}`。"
|
|
|
|
|
|
def analyze_simple_statement(statement: str) -> ImplementationFact | None:
|
|
normalized = normalize_whitespace(statement)
|
|
if not normalized:
|
|
return None
|
|
|
|
match = RETURN_MEMBER_RE.match(normalized)
|
|
if match:
|
|
field = match.group(1)
|
|
return ImplementationFact(
|
|
summary=f"返回 `{field}` 当前值。",
|
|
details=[f"内联返回 `{field}`。"],
|
|
)
|
|
|
|
match = RETURN_MEMBER_METHOD_RE.match(normalized)
|
|
if match:
|
|
field, method, args = match.groups()
|
|
if method.lower() == "data":
|
|
summary = f"返回 `{field}` 暴露的首地址。"
|
|
elif method.lower() == "size":
|
|
summary = f"返回 `{field}` 当前大小。"
|
|
elif method in {"CStr", "c_str"}:
|
|
summary = f"返回 `{field}` 的 C 风格字符串视图。"
|
|
else:
|
|
summary = f"返回 `{field}.{method}()` 的结果。"
|
|
detail = f"当前实现直接调用 `{field}.{method}({args})`。".replace("()", "()")
|
|
return ImplementationFact(summary=summary, details=[detail])
|
|
|
|
match = ASSIGN_MEMBER_RE.match(normalized)
|
|
if match:
|
|
field, value = match.groups()
|
|
return ImplementationFact(
|
|
summary=f"写入 `{field}`。",
|
|
details=[f"当前实现把 `{value}` 写入 `{field}`。"],
|
|
)
|
|
|
|
match = RETURN_SIMPLE_CALL_RE.match(normalized)
|
|
if match:
|
|
call_name = match.group(1)
|
|
return ImplementationFact(
|
|
summary=f"返回 `{call_name}(...)` 的结果。",
|
|
details=[f"当前实现直接转发到 `{call_name}(...)`。"],
|
|
)
|
|
|
|
match = RETURN_CONST_RE.match(normalized)
|
|
if match:
|
|
return ImplementationFact(
|
|
summary=summarize_return_value(match.group(1)),
|
|
details=[summarize_return_value(match.group(1))],
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
def collect_calls(body: str) -> list[str]:
|
|
calls: list[str] = []
|
|
for regex in (DIRECT_CALL_RE, ARROW_CALL_RE, DOT_CALL_RE):
|
|
for match in regex.finditer(body):
|
|
name = match.group(1)
|
|
short_name = name.split("::")[-1]
|
|
if short_name in KEYWORDS:
|
|
continue
|
|
calls.append(short_name)
|
|
return calls
|
|
|
|
|
|
def collect_member_writes(body: str) -> list[str]:
|
|
writes = MEMBER_WRITE_RE.findall(body)
|
|
for field, method in MEMBER_CALL_RE.findall(body):
|
|
if method in MUTATING_MEMBER_CALLS or method.startswith("Set"):
|
|
writes.append(field)
|
|
deduped: list[str] = []
|
|
seen: set[str] = set()
|
|
for field in writes:
|
|
if field in seen:
|
|
continue
|
|
deduped.append(field)
|
|
seen.add(field)
|
|
return deduped
|
|
|
|
|
|
def analyze_complex_body(body: str) -> ImplementationFact:
|
|
stripped = strip_comments(body)
|
|
normalized = normalize_whitespace(stripped)
|
|
statements = [normalize_whitespace(item) for item in split_statements(stripped)]
|
|
|
|
if not normalized:
|
|
return ImplementationFact(
|
|
summary="当前实现为空。",
|
|
details=["当前函数体为空。"],
|
|
)
|
|
|
|
if len(statements) == 1:
|
|
simple = analyze_simple_statement(statements[0])
|
|
if simple:
|
|
return simple
|
|
|
|
details: list[str] = []
|
|
summary: str | None = None
|
|
|
|
writes = collect_member_writes(stripped)
|
|
if writes:
|
|
details.append(f"会更新 {format_identifier_list(writes[:4])}。")
|
|
if len(writes) == 1 and summary is None:
|
|
summary = f"更新 `{writes[0]}`。"
|
|
|
|
calls = collect_calls(stripped)
|
|
filtered_calls = [call for call in calls if call not in {"Get", "Set", "Data", "Size"}]
|
|
if filtered_calls:
|
|
details.append(f"当前实现会调用 {format_identifier_list(filtered_calls[:5])}。")
|
|
if summary is None and len(filtered_calls) == 1:
|
|
summary = f"执行 `{filtered_calls[0]}(...)` 相关流程。"
|
|
elif summary is None:
|
|
summary = f"执行 {format_identifier_list(filtered_calls[:3])} 协同流程。"
|
|
|
|
return_values = re.findall(r"\breturn\s+([^;]+);", stripped)
|
|
if len(return_values) > 1 or ("if (" in stripped and return_values):
|
|
details.append("包含条件分支,并可能提前返回。")
|
|
elif return_values and summary is None:
|
|
summary = summarize_return_value(return_values[0])
|
|
|
|
if "nullptr" in stripped:
|
|
details.append("包含 `nullptr` 相关分支。")
|
|
if "not implemented" in stripped or "未实现" in stripped:
|
|
details.append("当前实现仍带有未完成分支。")
|
|
|
|
if summary is None:
|
|
summary = "执行该公开方法对应的当前实现。"
|
|
|
|
return ImplementationFact(summary=summary, details=details or [summary])
|
|
|
|
|
|
def dedupe_lines(lines: list[str]) -> list[str]:
|
|
result: list[str] = []
|
|
seen: set[str] = set()
|
|
for line in lines:
|
|
if line in seen:
|
|
continue
|
|
seen.add(line)
|
|
result.append(line)
|
|
return result
|
|
|
|
|
|
def analyze_method_group(
|
|
class_name: str,
|
|
method_name: str,
|
|
overloads: list[dict[str, object]],
|
|
header_text: str,
|
|
source_text: str,
|
|
source_rel: str | None,
|
|
) -> ImplementationFact:
|
|
implementation_blocks = extract_qualified_blocks(source_text, class_name, method_name)
|
|
if not implementation_blocks:
|
|
implementation_blocks = extract_inline_blocks(header_text, method_name)
|
|
|
|
details: list[str] = []
|
|
summaries: list[str] = []
|
|
|
|
for overload in overloads:
|
|
suffix = str(overload.get("suffix", "")).strip()
|
|
if "= 0" in suffix:
|
|
summaries.append("纯虚接口。")
|
|
details.append("该声明是纯虚接口,基类不提供实现。")
|
|
|
|
for block in implementation_blocks:
|
|
if block.kind == "default":
|
|
if method_name == class_name:
|
|
summaries.append(f"构造 `{class_name}` 实例。")
|
|
details.append("当前为默认构造实现。")
|
|
elif method_name == f"~{class_name}":
|
|
summaries.append(f"销毁 `{class_name}` 实例。")
|
|
details.append("当前为默认析构实现。")
|
|
else:
|
|
details.append("当前为 `= default` 实现。")
|
|
continue
|
|
|
|
if block.kind == "delete":
|
|
details.append("当前声明为 `= delete`。")
|
|
continue
|
|
|
|
fact = analyze_complex_body(block.body)
|
|
if fact.summary:
|
|
summaries.append(fact.summary)
|
|
details.extend(fact.details)
|
|
|
|
if not details and source_rel:
|
|
details.append(f"具体定义位于 `{source_rel}`。")
|
|
|
|
short_desc = next((item for item in summaries if item), None)
|
|
if short_desc is None:
|
|
if method_name == class_name:
|
|
short_desc = f"构造 `{class_name}` 实例。"
|
|
elif method_name == f"~{class_name}":
|
|
short_desc = f"销毁 `{class_name}` 实例。"
|
|
elif method_name.startswith("Get"):
|
|
short_desc = f"返回 `{camel_tail(method_name, 3)}` 相关结果。"
|
|
elif method_name.startswith("Set"):
|
|
short_desc = f"更新 `{camel_tail(method_name, 3)}` 相关状态。"
|
|
elif method_name.startswith("Is"):
|
|
short_desc = f"判断 `{camel_tail(method_name, 2)}` 条件是否成立。"
|
|
elif method_name.startswith("Has"):
|
|
short_desc = f"判断是否具备 `{camel_tail(method_name, 3)}`。"
|
|
elif method_name.startswith("Can"):
|
|
short_desc = f"判断当前是否可以执行 `{camel_tail(method_name, 3)}`。"
|
|
elif method_name.startswith("Load"):
|
|
short_desc = f"执行 `{method_name}` 加载流程。"
|
|
elif method_name.startswith("Create"):
|
|
short_desc = f"执行 `{method_name}` 创建流程。"
|
|
elif method_name.startswith("Update"):
|
|
short_desc = f"执行一次 `{method_name}` 更新。"
|
|
else:
|
|
short_desc = f"执行 `{method_name}` 对应的公开操作。"
|
|
|
|
if not details:
|
|
details.append(short_desc)
|
|
|
|
return ImplementationFact(summary=short_desc, details=dedupe_lines(details))
|
|
|
|
|
|
def build_method_page(
|
|
class_name: str,
|
|
namespace: str,
|
|
relative_header: str,
|
|
group: dict[str, object],
|
|
analysis: ImplementationFact,
|
|
) -> str:
|
|
label = str(group["label"])
|
|
method_name = str(group["method_name"])
|
|
overloads: list[dict[str, object]] = group["overloads"] # type: ignore[assignment]
|
|
|
|
lines: list[str] = []
|
|
lines.append(f"# {class_name}::{label}")
|
|
lines.append("")
|
|
lines.append(f"**命名空间**: `{namespace}`")
|
|
lines.append("")
|
|
lines.append("**类型**: `method`")
|
|
lines.append("")
|
|
lines.append(f"**头文件**: `{relative_header}`")
|
|
lines.append("")
|
|
lines.append("## 签名")
|
|
lines.append("")
|
|
lines.append("```cpp")
|
|
for overload in overloads:
|
|
lines.append(f"{overload['signature']};")
|
|
lines.append("```")
|
|
lines.append("")
|
|
lines.append("## 作用")
|
|
lines.append("")
|
|
lines.append(analysis.summary)
|
|
lines.append("")
|
|
lines.append("## 当前实现")
|
|
lines.append("")
|
|
for detail in analysis.details:
|
|
lines.append(f"- {detail}")
|
|
lines.append("")
|
|
lines.append("## 相关文档")
|
|
lines.append("")
|
|
lines.append(f"- [{class_name}]({class_name}.md)")
|
|
if method_name.startswith("Get") and any(str(item["file_name"]) == f"Set{method_name[3:]}" for item in group.get("siblings", [])): # type: ignore[operator]
|
|
lines.append(f"- [Set{method_name[3:]}](Set{method_name[3:]}.md)")
|
|
elif method_name.startswith("Set") and any(str(item["file_name"]) == f"Get{method_name[3:]}" for item in group.get("siblings", [])): # type: ignore[operator]
|
|
lines.append(f"- [Get{method_name[3:]}](Get{method_name[3:]}.md)")
|
|
|
|
return "\n".join(lines).rstrip() + "\n"
|
|
|
|
|
|
def rebuild_method_table(
|
|
content: str,
|
|
method_groups: list[dict[str, object]],
|
|
analyses: dict[str, ImplementationFact],
|
|
) -> str:
|
|
if "## 公共方法" not in content:
|
|
return content
|
|
|
|
lines = ["## 公共方法", "", "| 方法 | 描述 |", "|------|------|"]
|
|
for group in method_groups:
|
|
file_name = str(group["file_name"])
|
|
label = str(group["label"])
|
|
description = analyses[file_name].summary
|
|
lines.append(f"| [{label}]({file_name}.md) | {description} |")
|
|
section = "\n".join(lines) + "\n"
|
|
return METHOD_SECTION_RE.sub(section, content)
|
|
|
|
|
|
def main() -> int:
|
|
rewritten_method_pages = 0
|
|
rewritten_overviews = 0
|
|
|
|
for header_path in sorted(INCLUDE_ROOT.rglob("*.h")):
|
|
relative_header = header_path.relative_to(INCLUDE_ROOT.parent).as_posix()
|
|
relative_source = header_path.relative_to(INCLUDE_ROOT).with_suffix(".cpp")
|
|
source_path = REPO_ROOT / "engine" / "src" / relative_source
|
|
source_text = source_path.read_text(encoding="utf-8", errors="ignore") if source_path.exists() else ""
|
|
source_rel = source_path.relative_to(REPO_ROOT).as_posix() if source_path.exists() else None
|
|
|
|
header_text = header_path.read_text(encoding="utf-8", errors="ignore")
|
|
lines = header_text.splitlines()
|
|
declarations = find_declarations(lines, build_namespace_map(lines))
|
|
primary = select_primary(header_path.stem, declarations)
|
|
if primary is None or not primary.methods:
|
|
continue
|
|
|
|
doc_dir = DOC_ROOT / "XCEngine" / header_path.parent.relative_to(INCLUDE_ROOT) / header_path.stem
|
|
if not doc_dir.exists():
|
|
continue
|
|
|
|
method_groups = group_methods(primary.methods, primary.name)
|
|
analyses: dict[str, ImplementationFact] = {}
|
|
for group in method_groups:
|
|
group["siblings"] = method_groups
|
|
analyses[str(group["file_name"])] = analyze_method_group(
|
|
primary.name,
|
|
str(group["method_name"]),
|
|
group["overloads"], # type: ignore[arg-type]
|
|
header_text,
|
|
source_text,
|
|
source_rel,
|
|
)
|
|
|
|
overview_path = doc_dir / f"{header_path.stem}.md"
|
|
if overview_path.exists():
|
|
overview_content = overview_path.read_text(encoding="utf-8")
|
|
if has_any_token(overview_content, TEMPLATE_TABLE_TOKENS) or has_generic_fallback(overview_content):
|
|
updated = rebuild_method_table(overview_content, method_groups, analyses)
|
|
if updated != overview_content:
|
|
overview_path.write_text(updated, encoding="utf-8")
|
|
rewritten_overviews += 1
|
|
|
|
for group in method_groups:
|
|
file_name = str(group["file_name"])
|
|
page_path = doc_dir / f"{file_name}.md"
|
|
if not page_path.exists():
|
|
continue
|
|
|
|
content = page_path.read_text(encoding="utf-8")
|
|
if not has_any_token(content, TEMPLATE_METHOD_TOKENS) and not has_generic_fallback(content):
|
|
continue
|
|
|
|
updated = build_method_page(
|
|
primary.name,
|
|
primary.namespace,
|
|
relative_header,
|
|
group,
|
|
analyses[file_name],
|
|
)
|
|
if updated != content:
|
|
page_path.write_text(updated, encoding="utf-8")
|
|
rewritten_method_pages += 1
|
|
|
|
print(f"Rewritten overview pages: {rewritten_overviews}")
|
|
print(f"Rewritten method pages: {rewritten_method_pages}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|