8Stamp a project's Doxyfile from the bundled template, so that giving a
9subproject Doxygen documentation is one command rather than a hand-copied
12The Doxyfile's presence at ``docs/doxygen_work_dir/Doxyfile`` is the whole
13declaration: the workspace fan-out builds Doxygen for a subproject only if
14that file exists, the docs hub shows a Doxygen card on the same condition,
15and ``oct docs --clean`` sweeps its output regardless. Since 2026-09-28 the
16workspace manifest's ``html_docs: {"doxygen": false}`` can keep a declared
17tier off the docs site (the fan-out skips it and the hub shows no card);
18it never puts one there without the file. This module owns that path, so
19every consumer agrees on where the declaration lives.
21Before this module the two Doxyfiles in the workspace were full annotated
22copies that had already drifted apart (one still ingested markdown, one had
23symlink following on, one lacked three exclusions). The template lists only
24the settings that differ from Doxygen's defaults - what ``doxygen -x``
25prints - so it fits on a screen and drift is visible in a diff.
29- Own :data:`DOXYFILE_REL`, the one path every consumer resolves.
30- :func:`has_doxyfile` - the declaration test.
31- :func:`write_doxyfile` - render the template for a project, never
32 overwriting an existing file unless told to.
38 L2 - write entry / exit
39 L3 - decision (written / kept / overwritten)
40 L4 - template resolution
44- Writing is idempotent: an existing Doxyfile is kept and reported, not
45 replaced, unless ``force=True``. A project's own edits survive a re-run.
46- The only substitution is ``{{PROJECT_NAME}}``. ``INPUT`` is left as a
47 placeholder because :mod:`oct.docs.oct_docs` overwrites it at build time,
48 which is what keeps the file portable across clones.
49- Missing template is an error, not a silent skip: a caller asked for a
50 Doxyfile and did not get one.
54- oct.core.diagnostics (_dbg structured logger)
57from __future__
import annotations
59from pathlib
import Path
61from oct.core.diagnostics
import _dbg
67DOXYFILE_REL: Path = Path(
"docs") /
"doxygen_work_dir" /
"Doxyfile"
70_TEMPLATE_PATH: Path = (
71 Path(__file__).resolve().parents[1] /
"templates" /
"doxygen" /
"Doxyfile.template"
76 """The bundled Doxyfile template is not where the package expects it."""
80 """Return True when *project_dir* declares Doxygen documentation."""
81 return (Path(project_dir) / DOXYFILE_REL).is_file()
85 """Return the Doxyfile text for *project_name*.
89 DoxyfileTemplateMissing
90 When the bundled template cannot be read.
92 func =
"render_doxyfile"
93 if not _TEMPLATE_PATH.is_file():
95 f
"bundled Doxyfile template not found at {_TEMPLATE_PATH}",
97 _dbg(
"OCT-DOCS", func, f
"template={_TEMPLATE_PATH}", 4)
98 template = _TEMPLATE_PATH.read_text(encoding=
"utf-8")
99 return template.replace(
"{{PROJECT_NAME}}", project_name)
107) -> tuple[Path, bool]:
108 """Write the Doxyfile for *project_dir*, creating ``docs/doxygen_work_dir/``.
113 Project root. The file lands at ``project_dir / DOXYFILE_REL``.
115 Substituted for ``{{PROJECT_NAME}}`` - the title Doxygen shows.
117 Overwrite an existing Doxyfile. Off by default so a re-run of
118 ``oct scaffold`` or ``oct docs --init-doxygen`` never discards a
124 ``(path, written)``. *written* is False when the file already
125 existed and *force* was not given; the existing file is untouched.
129 DoxyfileTemplateMissing
130 When the bundled template is absent.
132 func =
"write_doxyfile"
133 target = Path(project_dir) / DOXYFILE_REL
134 _dbg(
"OCT-DOCS", func, f
"target={target} force={force}", 2)
136 if target.exists()
and not force:
137 _dbg(
"OCT-DOCS", func,
"exists; kept", 3)
141 target.parent.mkdir(parents=
True, exist_ok=
True)
142 target.write_text(content, encoding=
"utf-8")
143 _dbg(
"OCT-DOCS", func,
"overwritten" if force
else "written", 3)
149 "DoxyfileTemplateMissing",
str render_doxyfile(str project_name)
bool has_doxyfile(Path project_dir)
tuple[Path, bool] write_doxyfile(Path project_dir, str project_name, *, bool force=False)