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*;")
24 @brief Perform doxygen file page.
25 @param[in] name Argument passed to `doxygen_file_page()`.
26 @return Value returned by `doxygen_file_page()`.
28 return name.replace(
"_",
"__").replace(
".",
"_8") +
".html"
33 @brief Perform doxygen file page with path.
34 @param[in] rel_path Argument passed to `doxygen_file_page_with_path()`.
35 @return Value returned by `doxygen_file_page_with_path()`.
37 return rel_path.replace(
"_",
"__").replace(
"/",
"_2").replace(
".",
"_8") +
".html"
42 @brief Perform needs files fallback.
43 @param[in] path Filesystem path argument passed to `needs_files_fallback()`.
44 @return Value returned by `needs_files_fallback()`.
48 text = path.read_text(encoding=
"utf-8", errors=
"ignore")
49 return "Detailed file index was not generated in this build." in text
54 @brief Perform needs structs fallback.
55 @param[in] path Filesystem path argument passed to `needs_structs_fallback()`.
56 @return Value returned by `needs_structs_fallback()`.
60 text = path.read_text(encoding=
"utf-8", errors=
"ignore")
61 return "Detailed structure index was not generated in this build." in text
66 @brief Resolve doxygen file href.
67 @param[in] html_dir Argument passed to `resolve_doxygen_file_href()`.
68 @param[in] rel_path Argument passed to `resolve_doxygen_file_href()`.
69 @return Value returned by `resolve_doxygen_file_href()`.
71 name = Path(rel_path).name
73 if (html_dir / candidate).exists():
76 if (html_dir / candidate_path).exists():
83 @brief Perform make repo href.
84 @param[in] rel_path Argument passed to `make_repo_href()`.
85 @return Value returned by `make_repo_href()`.
87 return REPO_BLOB_URL + rel_path
90def collect_file_rows(repo_root: Path, html_dir: Path, base_dir: str, suffixes: set[str]) -> list[tuple[str, str, str]]:
92 @brief Collect file rows.
93 @param[in] repo_root Argument passed to `collect_file_rows()`.
94 @param[in] html_dir Argument passed to `collect_file_rows()`.
95 @param[in] base_dir Argument passed to `collect_file_rows()`.
96 @param[in] suffixes Argument passed to `collect_file_rows()`.
97 @return Value returned by `collect_file_rows()`.
99 rows: list[tuple[str, str, str]] = []
100 root = repo_root / base_dir
101 if not root.exists():
103 for path
in sorted(root.rglob(
"*")):
104 if not path.is_file():
106 if suffixes
and path.suffix.lower()
not in suffixes:
108 rel = path.relative_to(repo_root).as_posix()
110 rows.append((path.name, rel, href))
116 @brief Collect all source like files.
117 @param[in] repo_root Argument passed to `collect_all_source_like_files()`.
118 @param[in] html_dir Argument passed to `collect_all_source_like_files()`.
119 @return Value returned by `collect_all_source_like_files()`.
121 rows: list[tuple[str, str, str]] = []
124 rows.extend(
collect_file_rows(repo_root, html_dir,
"picurv_cli", SCRIPT_SUFFIXES))
126 rows.extend(
collect_file_rows(repo_root, html_dir,
"tests/tooling", SCRIPT_SUFFIXES))
132 @brief Collect struct rows.
133 @param[in] repo_root Argument passed to `collect_struct_rows()`.
134 @param[in] html_dir Argument passed to `collect_struct_rows()`.
135 @return Value returned by `collect_struct_rows()`.
137 struct_to_header: dict[str, str] = {}
138 include_dir = repo_root /
"include"
139 if not include_dir.exists():
142 for header
in sorted(include_dir.rglob(
"*.h")):
143 text = header.read_text(encoding=
"utf-8", errors=
"ignore")
144 header_rel = header.relative_to(repo_root).as_posix()
147 struct_to_header.setdefault(name, header_rel)
149 rows: list[tuple[str, str, str]] = []
150 for name, header_rel
in sorted(struct_to_header.items(), key=
lambda item: item[0].lower()):
151 if name
in IGNORED_STRUCT_NAMES:
153 struct_page = f
"struct{name}.html"
154 if (html_dir / struct_page).exists():
158 rows.append((name, header_rel, href))
164 @brief Extract struct names.
165 @param[in] text Argument passed to `extract_struct_names()`.
166 @return Value returned by `extract_struct_names()`.
168 names: set[str] = set()
170 for match
in NAMED_STRUCT_RE.finditer(text):
171 names.add(match.group(1))
173 in_typedef_struct =
False
175 typedef_tag_name: str |
None =
None
176 for line
in text.splitlines():
177 if not in_typedef_struct:
178 start = TYPEDEF_START_RE.search(line)
181 in_typedef_struct =
True
182 typedef_tag_name = start.group(1)
184 names.add(typedef_tag_name)
185 brace_depth = line.count(
"{") - line.count(
"}")
187 in_typedef_struct =
False
188 typedef_tag_name =
None
191 brace_depth += line.count(
"{") - line.count(
"}")
192 end = TYPEDEF_END_RE.search(line)
194 names.add(end.group(1))
196 in_typedef_struct =
False
197 typedef_tag_name =
None
204 @brief Categorize struct.
205 @param[in] name Argument passed to `categorize_struct()`.
206 @return Value returned by `categorize_struct()`.
208 if name.startswith(
"BC")
or "Boundary" in name
or name ==
"FlowWave":
209 return "Boundary Condition System"
210 if name.startswith(
"IBM")
or name
in {
"FSInfo",
"SurfElmtInfo",
"Cstart"}:
211 return "Immersed Boundary and FSI"
212 if name.startswith(
"Particle")
or name
in {
"MigrationInfo"}:
213 return "Particle Transport and Statistics"
214 if name
in {
"SimCtx",
"UserCtx",
"UserMG",
"MGCtx",
"ScalingCtx",
"DualMonitorCtx",
"ProfiledFunction"}:
215 return "Runtime Control and Solver Orchestration"
216 if name.startswith(
"VTK")
or name ==
"PostProcessParams":
217 return "I/O and Postprocessing"
218 if name
in {
"BoundingBox",
"Cell",
"Cmpnts",
"Cmpnts2",
"Cpt2D",
"RankCellInfo",
"RankNeighbors"}:
219 return "Grid and Geometry"
220 return "Generic Containers and Utilities"
226 @param[in] label Argument passed to `render_link()`.
227 @param[in] href Argument passed to `render_link()`.
228 @return Value returned by `render_link()`.
230 label_esc = html.escape(label)
231 href_esc = html.escape(href)
232 if href.startswith(
"http"):
233 return f
"<a class='el' href='{href_esc}' target='_blank' rel='noopener'>{label_esc}</a>"
234 return f
"<a class='el' href='{href_esc}'>{label_esc}</a>"
237def render_rows(rows: list[tuple[str, str, str]], empty_msg: str) -> str:
240 @param[in] rows Argument passed to `render_rows()`.
241 @param[in] empty_msg Argument passed to `render_rows()`.
242 @return Value returned by `render_rows()`.
245 return f
"<tr><td colspan='2'>{html.escape(empty_msg)}</td></tr>"
247 for name, rel, href
in rows:
250 f
"<td class='indexkey'>{render_link(name, href)}</td>"
251 f
"<td class='indexvalue'><code>{html.escape(rel)}</code></td>"
254 return "\n".join(out)
259 @brief Perform section table.
260 @param[in] title Argument passed to `section_table()`.
261 @param[in] rows_html Argument passed to `section_table()`.
262 @return Value returned by `section_table()`.
265 f
"<h2>{html.escape(title)}</h2>\n"
266 "<table class='doxtable'>\n"
267 "<thead><tr><th>Name</th><th>Location</th></tr></thead>\n"
268 f
"<tbody>\n{rows_html}\n</tbody>\n"
276 @param[in] title Argument passed to `render_page()`.
277 @param[in] intro Argument passed to `render_page()`.
278 @param[in] body_html Argument passed to `render_page()`.
279 @return Value returned by `render_page()`.
281 return f
"""<!DOCTYPE html>
284 <meta charset="utf-8" />
285 <meta name="viewport" content="width=device-width, initial-scale=1" />
286 <title>PICurv: {html.escape(title)}</title>
287 <link href="doxygen.css" rel="stylesheet" />
288 <link href="custom.css" rel="stylesheet" />
289 <script type="text/javascript" src="theme-sync.js"></script>
293 <div class="headertitle"><div class="title">{html.escape(title)}</div></div>
295 <div class="contents">
296 <p>{html.escape(intro)}</p>
298 <p>See <a href="Documentation_Map.html">Documentation Map</a> for structural navigation.</p>
307 @brief Write structured file index.
308 @param[in] repo_root Argument passed to `write_structured_file_index()`.
309 @param[in] html_dir Argument passed to `write_structured_file_index()`.
313 runtime_python =
collect_file_rows(repo_root, html_dir,
"picurv_cli", SCRIPT_SUFFIXES)
314 tooling =
collect_file_rows(repo_root, html_dir,
"tests/tooling", SCRIPT_SUFFIXES)
321 out = html_dir /
"files_structured.html"
324 "File List (Structured)",
325 "Organized by file role: headers, source files, Python runtime, and tooling.",
330 print(f
"[index] wrote {out}")
335 @brief Write structured struct index.
336 @param[in] repo_root Argument passed to `write_structured_struct_index()`.
337 @param[in] html_dir Argument passed to `write_structured_struct_index()`.
340 grouped: dict[str, list[tuple[str, str, str]]] = {}
345 "Runtime Control and Solver Orchestration",
347 "Boundary Condition System",
348 "Particle Transport and Statistics",
349 "Immersed Boundary and FSI",
350 "I/O and Postprocessing",
351 "Generic Containers and Utilities",
353 body_parts: list[str] = []
354 for section
in ordered_sections:
358 render_rows(grouped.get(section, []), f
"No structures found for section: {section}."),
361 out = html_dir /
"annotated_structured.html"
364 "Data Structures (By Module)",
365 "Grouped by major solver modules and responsibilities.",
366 "\n".join(body_parts),
370 print(f
"[index] wrote {out}")
375 @brief Write fallback files page.
376 @param[in] repo_root Argument passed to `write_fallback_files_page()`.
377 @param[in] html_dir Argument passed to `write_fallback_files_page()`.
381 out = html_dir /
"files.html"
385 "Fallback file list generated from runtime and tooling source directories.",
390 print(f
"[fallback] wrote {out}")
395 @brief Write fallback struct page.
396 @param[in] repo_root Argument passed to `write_fallback_struct_page()`.
397 @param[in] html_dir Argument passed to `write_fallback_struct_page()`.
401 out = html_dir /
"annotated.html"
405 "Fallback structure list generated from C headers.",
410 print(f
"[fallback] wrote {out}")
415 @brief Entry point for this script.
416 @return Value returned by `main()`.
418 parser = argparse.ArgumentParser(
420 "Generate structured Doxygen index pages and fallback replacements when\n"
421 "files.html or annotated.html are missing/empty after doc generation."
423 formatter_class=argparse.RawTextHelpFormatter,
426 " python3 tests/tooling/generate_doxygen_fallback_indexes.py \\\n"
427 " --repo-root . --html-dir docs_build/html\n"
428 " python3 tests/tooling/generate_doxygen_fallback_indexes.py \\\n"
429 " --repo-root /path/to/repo --html-dir /path/to/repo/docs_build/html"
436 help=
"Repository root used to scan runtime, generator, tooling, and header sources.",
442 help=
"Doxygen HTML output directory (where files.html/annotated.html live).",
444 args = parser.parse_args()
446 repo_root = args.repo_root.resolve()
447 html_dir = args.html_dir.resolve()
452 files_page = html_dir /
"files.html"
453 structs_page = html_dir /
"annotated.html"
462if __name__ ==
"__main__":
463 raise SystemExit(
main())
list[tuple[str, str, str]] collect_all_source_like_files(Path repo_root, Path html_dir)
Collect all source like files.
str doxygen_file_page(str name)
Perform doxygen file page.
str render_link(str label, str href)
Render link.
bool needs_files_fallback(Path path)
Perform needs files fallback.
bool needs_structs_fallback(Path path)
Perform needs structs fallback.
None write_structured_file_index(Path repo_root, Path html_dir)
Write structured file index.
int main()
Entry point for this script.
str doxygen_file_page_with_path(str rel_path)
Perform doxygen file page with path.
None write_structured_struct_index(Path repo_root, Path html_dir)
Write structured struct index.
str render_rows(list[tuple[str, str, str]] rows, str empty_msg)
Render rows.
None write_fallback_struct_page(Path repo_root, Path html_dir)
Write fallback struct page.
str render_page(str title, str intro, str body_html)
Render page.
str resolve_doxygen_file_href(Path html_dir, str rel_path)
Resolve doxygen file href.
str make_repo_href(str rel_path)
Perform make repo href.
list[tuple[str, str, str]] collect_struct_rows(Path repo_root, Path html_dir)
Collect struct rows.
set[str] extract_struct_names(str text)
Extract struct names.
list[tuple[str, str, str]] collect_file_rows(Path repo_root, Path html_dir, str base_dir, set[str] suffixes)
Collect file rows.
str section_table(str title, str rows_html)
Perform section table.
None write_fallback_files_page(Path repo_root, Path html_dir)
Write fallback files page.
str categorize_struct(str name)
Categorize struct.