2"""Generate robust Doxygen index pages and structured reference views."""
4from __future__
import annotations
9from pathlib
import Path
11HEADER_SUFFIXES = {
".h",
".hpp"}
12SOURCE_SUFFIXES = {
".c",
".cc",
".cpp"}
13SCRIPT_SUFFIXES = {
".py",
".sh",
".flow"}
14REPO_BLOB_URL =
"https://github.com/VishalKandala/PICurv/blob/main/"
15IGNORED_STRUCT_NAMES = {
"Name"}
17NAMED_STRUCT_RE = re.compile(
r"\bstruct\s+([A-Za-z_]\w*)\s*\{")
18TYPEDEF_START_RE = re.compile(
r"^\s*typedef\s+struct(?:\s+([A-Za-z_]\w*))?")
19TYPEDEF_END_RE = re.compile(
r"^\s*}\s*([A-Za-z_]\w*)\s*;")
23 return name.replace(
"_",
"__").replace(
".",
"_8") +
".html"
27 return rel_path.replace(
"_",
"__").replace(
"/",
"_2").replace(
".",
"_8") +
".html"
33 text = path.read_text(encoding=
"utf-8", errors=
"ignore")
34 return "Detailed file index was not generated in this build." in text
40 text = path.read_text(encoding=
"utf-8", errors=
"ignore")
41 return "Detailed structure index was not generated in this build." in text
45 name = Path(rel_path).name
47 if (html_dir / candidate).exists():
50 if (html_dir / candidate_path).exists():
56 return REPO_BLOB_URL + rel_path
59def collect_file_rows(repo_root: Path, html_dir: Path, base_dir: str, suffixes: set[str]) -> list[tuple[str, str, str]]:
60 rows: list[tuple[str, str, str]] = []
61 root = repo_root / base_dir
64 for path
in sorted(root.rglob(
"*")):
65 if not path.is_file():
67 if suffixes
and path.suffix.lower()
not in suffixes:
69 rel = path.relative_to(repo_root).as_posix()
71 rows.append((path.name, rel, href))
76 rows: list[tuple[str, str, str]] = []
84 struct_to_header: dict[str, str] = {}
85 include_dir = repo_root /
"include"
86 if not include_dir.exists():
89 for header
in sorted(include_dir.rglob(
"*.h")):
90 text = header.read_text(encoding=
"utf-8", errors=
"ignore")
91 header_rel = header.relative_to(repo_root).as_posix()
94 struct_to_header.setdefault(name, header_rel)
96 rows: list[tuple[str, str, str]] = []
97 for name, header_rel
in sorted(struct_to_header.items(), key=
lambda item: item[0].lower()):
98 if name
in IGNORED_STRUCT_NAMES:
100 struct_page = f
"struct{name}.html"
101 if (html_dir / struct_page).exists():
105 rows.append((name, header_rel, href))
110 names: set[str] = set()
112 for match
in NAMED_STRUCT_RE.finditer(text):
113 names.add(match.group(1))
115 in_typedef_struct =
False
117 typedef_tag_name: str |
None =
None
118 for line
in text.splitlines():
119 if not in_typedef_struct:
120 start = TYPEDEF_START_RE.search(line)
123 in_typedef_struct =
True
124 typedef_tag_name = start.group(1)
126 names.add(typedef_tag_name)
127 brace_depth = line.count(
"{") - line.count(
"}")
129 in_typedef_struct =
False
130 typedef_tag_name =
None
133 brace_depth += line.count(
"{") - line.count(
"}")
134 end = TYPEDEF_END_RE.search(line)
136 names.add(end.group(1))
138 in_typedef_struct =
False
139 typedef_tag_name =
None
145 if name.startswith(
"BC")
or "Boundary" in name
or name ==
"FlowWave":
146 return "Boundary Condition System"
147 if name.startswith(
"IBM")
or name
in {
"FSInfo",
"SurfElmtInfo",
"Cstart"}:
148 return "Immersed Boundary and FSI"
149 if name.startswith(
"Particle")
or name
in {
"MigrationInfo"}:
150 return "Particle Transport and Statistics"
151 if name
in {
"SimCtx",
"UserCtx",
"UserMG",
"MGCtx",
"ScalingCtx",
"DualMonitorCtx",
"ProfiledFunction"}:
152 return "Runtime Control and Solver Orchestration"
153 if name.startswith(
"VTK")
or name ==
"PostProcessParams":
154 return "I/O and Postprocessing"
155 if name
in {
"BoundingBox",
"Cell",
"Cmpnts",
"Cmpnts2",
"Cpt2D",
"RankCellInfo",
"RankNeighbors"}:
156 return "Grid and Geometry"
157 return "Generic Containers and Utilities"
161 label_esc = html.escape(label)
162 href_esc = html.escape(href)
163 if href.startswith(
"http"):
164 return f
"<a class='el' href='{href_esc}' target='_blank' rel='noopener'>{label_esc}</a>"
165 return f
"<a class='el' href='{href_esc}'>{label_esc}</a>"
168def render_rows(rows: list[tuple[str, str, str]], empty_msg: str) -> str:
170 return f
"<tr><td colspan='2'>{html.escape(empty_msg)}</td></tr>"
172 for name, rel, href
in rows:
175 f
"<td class='indexkey'>{render_link(name, href)}</td>"
176 f
"<td class='indexvalue'><code>{html.escape(rel)}</code></td>"
179 return "\n".join(out)
184 f
"<h2>{html.escape(title)}</h2>\n"
185 "<table class='doxtable'>\n"
186 "<thead><tr><th>Name</th><th>Location</th></tr></thead>\n"
187 f
"<tbody>\n{rows_html}\n</tbody>\n"
193 return f
"""<!DOCTYPE html>
196 <meta charset="utf-8" />
197 <meta name="viewport" content="width=device-width, initial-scale=1" />
198 <title>PICurv: {html.escape(title)}</title>
199 <link href="doxygen.css" rel="stylesheet" />
200 <link href="custom.css" rel="stylesheet" />
201 <script type="text/javascript" src="theme-sync.js"></script>
205 <div class="headertitle"><div class="title">{html.escape(title)}</div></div>
207 <div class="contents">
208 <p>{html.escape(intro)}</p>
210 <p>See <a href="Documentation_Map.html">Documentation Map</a> for structural navigation.</p>
226 out = html_dir /
"files_structured.html"
229 "File List (Structured)",
230 "Organized by file role: headers, source files, and scripts.",
235 print(f
"[index] wrote {out}")
240 grouped: dict[str, list[tuple[str, str, str]]] = {}
245 "Runtime Control and Solver Orchestration",
247 "Boundary Condition System",
248 "Particle Transport and Statistics",
249 "Immersed Boundary and FSI",
250 "I/O and Postprocessing",
251 "Generic Containers and Utilities",
253 body_parts: list[str] = []
254 for section
in ordered_sections:
258 render_rows(grouped.get(section, []), f
"No structures found for section: {section}."),
261 out = html_dir /
"annotated_structured.html"
264 "Data Structures (By Module)",
265 "Grouped by major solver modules and responsibilities.",
266 "\n".join(body_parts),
270 print(f
"[index] wrote {out}")
276 out = html_dir /
"files.html"
280 "Fallback file list generated from include/src/scripts.",
285 print(f
"[fallback] wrote {out}")
291 out = html_dir /
"annotated.html"
295 "Fallback structure list generated from C headers.",
300 print(f
"[fallback] wrote {out}")
304 parser = argparse.ArgumentParser()
305 parser.add_argument(
"--repo-root", required=
True, type=Path)
306 parser.add_argument(
"--html-dir", required=
True, type=Path)
307 args = parser.parse_args()
309 repo_root = args.repo_root.resolve()
310 html_dir = args.html_dir.resolve()
315 files_page = html_dir /
"files.html"
316 structs_page = html_dir /
"annotated.html"
325if __name__ ==
"__main__":
326 raise SystemExit(
main())
list[tuple[str, str, str]] collect_all_source_like_files(Path repo_root, Path html_dir)
str doxygen_file_page(str name)
str render_link(str label, str href)
bool needs_files_fallback(Path path)
bool needs_structs_fallback(Path path)
None write_structured_file_index(Path repo_root, Path html_dir)
str doxygen_file_page_with_path(str rel_path)
None write_structured_struct_index(Path repo_root, Path html_dir)
str render_rows(list[tuple[str, str, str]] rows, str empty_msg)
None write_fallback_struct_page(Path repo_root, Path html_dir)
str render_page(str title, str intro, str body_html)
str resolve_doxygen_file_href(Path html_dir, str rel_path)
str make_repo_href(str rel_path)
list[tuple[str, str, str]] collect_struct_rows(Path repo_root, Path html_dir)
set[str] extract_struct_names(str text)
list[tuple[str, str, str]] collect_file_rows(Path repo_root, Path html_dir, str base_dir, set[str] suffixes)
str section_table(str title, str rows_html)
None write_fallback_files_page(Path repo_root, Path html_dir)
str categorize_struct(str name)