oct
Loading...
Searching...
No Matches
doxyfile_init.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oct/docs/doxyfile_init.py
4
5"""
6Purpose
7-------
8Stamp a project's Doxyfile from the bundled template, so that giving a
9subproject Doxygen documentation is one command rather than a hand-copied
103,000-line file.
11
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.
20
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.
26
27Responsibilities
28----------------
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.
33
34Diagnostics
35-----------
36Domain: OCT-DOCS
37Levels:
38 L2 - write entry / exit
39 L3 - decision (written / kept / overwritten)
40 L4 - template resolution
41
42Contracts
43---------
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.
51
52Dependencies
53------------
54- oct.core.diagnostics (_dbg structured logger)
55"""
56
57from __future__ import annotations
58
59from pathlib import Path
60
61from oct.core.diagnostics import _dbg
62
63
64#: Where a project declares Doxygen, relative to the project root. Every
65#: consumer - the single-project build, the workspace fan-out, the hub card
66#: filter, the scaffold - resolves this one constant.
67DOXYFILE_REL: Path = Path("docs") / "doxygen_work_dir" / "Doxyfile"
68
69#: Bundled template, relative to the installed ``oct`` package.
70_TEMPLATE_PATH: Path = (
71 Path(__file__).resolve().parents[1] / "templates" / "doxygen" / "Doxyfile.template"
72)
73
74
75class DoxyfileTemplateMissing(FileNotFoundError):
76 """The bundled Doxyfile template is not where the package expects it."""
77
78
79def has_doxyfile(project_dir: Path) -> bool:
80 """Return True when *project_dir* declares Doxygen documentation."""
81 return (Path(project_dir) / DOXYFILE_REL).is_file()
82
83
84def render_doxyfile(project_name: str) -> str:
85 """Return the Doxyfile text for *project_name*.
86
87 Raises
88 ------
89 DoxyfileTemplateMissing
90 When the bundled template cannot be read.
91 """
92 func = "render_doxyfile"
93 if not _TEMPLATE_PATH.is_file():
95 f"bundled Doxyfile template not found at {_TEMPLATE_PATH}",
96 )
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)
100
101
103 project_dir: Path,
104 project_name: str,
105 *,
106 force: bool = False,
107) -> tuple[Path, bool]:
108 """Write the Doxyfile for *project_dir*, creating ``docs/doxygen_work_dir/``.
109
110 Parameters
111 ----------
112 project_dir
113 Project root. The file lands at ``project_dir / DOXYFILE_REL``.
114 project_name
115 Substituted for ``{{PROJECT_NAME}}`` - the title Doxygen shows.
116 force
117 Overwrite an existing Doxyfile. Off by default so a re-run of
118 ``oct scaffold`` or ``oct docs --init-doxygen`` never discards a
119 project's own edits.
120
121 Returns
122 -------
123 tuple[Path, bool]
124 ``(path, written)``. *written* is False when the file already
125 existed and *force* was not given; the existing file is untouched.
126
127 Raises
128 ------
129 DoxyfileTemplateMissing
130 When the bundled template is absent.
131 """
132 func = "write_doxyfile"
133 target = Path(project_dir) / DOXYFILE_REL
134 _dbg("OCT-DOCS", func, f"target={target} force={force}", 2)
135
136 if target.exists() and not force:
137 _dbg("OCT-DOCS", func, "exists; kept", 3)
138 return target, False
139
140 content = render_doxyfile(project_name)
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)
144 return target, True
145
146
147__all__ = [
148 "DOXYFILE_REL",
149 "DoxyfileTemplateMissing",
150 "has_doxyfile",
151 "render_doxyfile",
152 "write_doxyfile",
153]
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)