Coverage for sphinx_reports / __init__.py: 54%
117 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-16 00:05 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-16 00:05 +0000
1# ==================================================================================================================== #
2# _ _ _ #
3# ___ _ __ | |__ (_)_ __ __ __ _ __ ___ _ __ ___ _ __| |_ ___ #
4# / __| '_ \| '_ \| | '_ \\ \/ /____| '__/ _ \ '_ \ / _ \| '__| __/ __| #
5# \__ \ |_) | | | | | | | |> <_____| | | __/ |_) | (_) | | | |_\__ \ #
6# |___/ .__/|_| |_|_|_| |_/_/\_\ |_| \___| .__/ \___/|_| \__|___/ #
7# |_| |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2023-2026 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32**A Sphinx domain providing directives to add reports to the Sphinx-based documentation.**
34Supported reports:
36* :ref:`UNITTEST`
37* :ref:`DOCCOV`
38* :ref:`CODECOV`
39* :ref:`DEP`
41"""
42__author__ = "Patrick Lehmann"
43__email__ = "Paebbels@gmail.com"
44__copyright__ = "2023-2026, Patrick Lehmann"
45__license__ = "Apache License, Version 2.0"
46__version__ = "0.11.0"
47__keywords__ = [
48 "Python3", "Sphinx", "Extension", "Report", "doc-string", "interrogate", "Code Coverage", "Coverage",
49 "Documentation Coverage", "Unittest", "Dependencies", "Summary"
50]
52from hashlib import md5
53from pathlib import Path
54from typing import TYPE_CHECKING, Any, Tuple, Dict, Optional as Nullable, TypedDict, List, Callable, Type
56from docutils.nodes import Element
57from docutils.transforms import Transform
58from sphinx.addnodes import pending_xref
59from sphinx.application import Sphinx
60from sphinx.builders import Builder
61from sphinx.config import Config
62from sphinx.domains import Domain
63from sphinx.environment import BuildEnvironment
64from sphinx.util.logging import getLogger
65from pyTooling.Decorators import export
66from pyTooling.Common import readResourceFile
68from sphinx_reports import static as ResourcePackage
69from sphinx_reports.Common import ReportExtensionError, visitFunc, departFunc
70from sphinx_reports.Node import Landscape
71from sphinx_reports.Workaround import FixLatexTableWidths
72from sphinx_reports.HTML import translateLandscape as translateLandscapeAsHTML
73from sphinx_reports.LaTeX import translateLandscape as translateLandscapeAsLaTeX
76@export
77class RegisteredNode(TypedDict):
78 """
79 Type information for an entry in :attr:`ReportDomain.nodes`.
80 """
81 name: str #: Name of the new docutils node to register.
82 node: Type[Element] #: The new node class to register.
83 html: Tuple[visitFunc, departFunc] #: A tuple of visit and depart functions rendering the new node in case of HTML output.
84 latex: Tuple[visitFunc, departFunc] #: A tuple of visit and depart functions rendering the new node in case of LaTeX output.
87@export
88class ReportDomain(Domain):
89 """
90 A Sphinx extension providing a ``report`` domain to integrate reports and summaries into a Sphinx-based documentation.
92 .. rubric:: New directives:
94 * :rst:dir:`report:code-coverage`
95 * :rst:dir:`report:code-coverage-legend`
96 * :rst:dir:`report:doc-coverage`
97 * :rst:dir:`report:doc-coverage-legend`
98 * :rst:dir:`report:dependency-table`
99 * :rst:dir:`report:unittest-summary`
101 .. rubric:: New roles:
103 * *None*
105 .. rubric:: New indices:
107 * *None*
109 .. rubric:: Configuration variables
111 All configuration variables in :file:`conf.py` are prefixed with ``report_*``:
113 * ``report_codecov_packages``
114 * ``report_doccov_packages``
115 * ``report_unittest_testsuites``
117 """
119 name = "report" #: The name of this domain
120 label = "rpt" #: The label of this domain
122 dependencies: List[str] = [
123 ] #: A list of other extensions this domain depends on.
125 latexPackages: Tuple[str, ...] = (
126 "pdflscape",
127 )
128 nodes: Tuple[RegisteredNode, ...] = (
129 { "name": "Landscape",
130 "node": Landscape,
131 "html": translateLandscapeAsHTML,
132 "latex": translateLandscapeAsLaTeX
133 },
134 )
135 transformations: Tuple[Type[Transform], ...] = (
136 FixLatexTableWidths,
137 )
139 from sphinx_reports.CodeCoverage import CodeCoverage, CodeCoverageLegend, ModuleCoverage
140 from sphinx_reports.DocCoverage import DocStrCoverage, DocCoverageLegend
141 from sphinx_reports.Dependency import DependencyTable
142 from sphinx_reports.Unittest import UnittestSummary
144 directives = {
145 "code-coverage": CodeCoverage,
146 "code-coverage-legend": CodeCoverageLegend,
147 "module-coverage": ModuleCoverage,
148 "doc-coverage": DocStrCoverage,
149 "doc-coverage-legend": DocCoverageLegend,
150 "dependency-table": DependencyTable,
151 "unittest-summary": UnittestSummary,
152 } #: A dictionary of all directives in this domain.
154 roles = {
155 # "design": DesignRole,
156 } #: A dictionary of all roles in this domain.
158 indices = [
159 # LibraryIndex,
160 ] #: A list of all indices in this domain.
162 from sphinx_reports.CodeCoverage import CodeCoverageBase
163 from sphinx_reports.DocCoverage import DocCoverageBase
164 from sphinx_reports.Dependency import DependencyTable
165 from sphinx_reports.Unittest import UnittestSummary
167 configValues: Dict[str, Tuple[Any, str, Any]] = {
168 **CodeCoverageBase.configValues,
169 **DocCoverageBase.configValues,
170 **UnittestSummary.configValues,
171 **DependencyTable.configValues,
172 } #: A dictionary of all configuration values used by this domain. (name: (default, rebuilt, type))
174 del CodeCoverageBase
175 del CodeCoverage
176 del CodeCoverageLegend
177 del ModuleCoverage
178 del DocCoverageBase
179 del DocStrCoverage
180 del DocCoverageLegend
181 del DependencyTable
182 del UnittestSummary
184 initial_data = {
185 # "reports": {}
186 } #: A dictionary of all global data fields used by this domain.
188 # @property
189 # def Reports(self) -> Dict[str, Any]:
190 # return self.data["reports"]
192 @staticmethod
193 def CheckConfigurationVariables(sphinxApplication: Sphinx, config: Config) -> None:
194 """
195 Call back for Sphinx ``config-inited`` event.
197 This callback will verify configuration variables used by that domain.
199 .. seealso::
201 Sphinx *builder-inited* event
202 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events
204 :param sphinxApplication: The Sphinx application.
205 :param config: Sphinx configuration parsed from ``conf.py``.
206 """
207 from sphinx_reports.CodeCoverage import CodeCoverageBase
208 from sphinx_reports.DocCoverage import DocCoverageBase
209 from sphinx_reports.Unittest import UnittestSummary
211 checkConfigurations = (
212 CodeCoverageBase.CheckConfiguration,
213 DocCoverageBase.CheckConfiguration,
214 UnittestSummary.CheckConfiguration,
215 )
217 for checkConfiguration in checkConfigurations:
218 try:
219 checkConfiguration(sphinxApplication, config)
220 except ReportExtensionError as ex:
221 logger = getLogger(__name__)
222 logger.error(f"Caught {ex.__class__.__name__} when checking configuration variables.\n {ex}")
224 @staticmethod
225 def AddCSSFiles(sphinxApplication: Sphinx) -> None:
226 """
227 Call back for Sphinx ``builder-inited`` event.
229 This callback will copy the CSS file(s) to the build directory.
231 .. seealso::
233 Sphinx *builder-inited* event
234 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events
236 :param sphinxApplication: The Sphinx application.
237 """
238 # Create a new static path for this extension
239 staticDirectory = (Path(sphinxApplication.outdir) / "_report_static").resolve()
240 staticDirectory.mkdir(exist_ok=True)
241 sphinxApplication.config.html_static_path.append(str(staticDirectory))
243 # Read the CSS content from package resources and hash it
244 cssFilename = "sphinx-reports.css"
245 cssContent = readResourceFile(ResourcePackage, cssFilename)
247 # Compute md5 hash of CSS file
248 hash = md5(cssContent.encode("utf8")).hexdigest() # nosec B324
250 # Write the CSS file into output directory
251 cssFile = staticDirectory / f"sphinx-reports.{hash}.css"
252 sphinxApplication.add_css_file(cssFile.name)
254 if not cssFile.exists():
255 # Purge old CSS files
256 for file in staticDirectory.glob("*.css"):
257 file.unlink()
259 # Write CSS content
260 cssFile.write_text(cssContent, encoding="utf8")
262 @staticmethod
263 def ReadReports(sphinxApplication: Sphinx) -> None:
264 """
265 Call back for Sphinx ``builder-inited`` event.
267 This callback will read the linked report files
269 .. seealso::
271 Sphinx *builder-inited* event
272 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events
274 :param sphinxApplication: The Sphinx application.
275 """
276 from sphinx_reports.CodeCoverage import CodeCoverageBase
277 from sphinx_reports.Unittest import UnittestSummary
279 CodeCoverageBase.ReadReports(sphinxApplication)
280 UnittestSummary.ReadReports(sphinxApplication)
282 callbacks: Dict[str, List[Callable]] = {
283 "config-inited": [CheckConfigurationVariables], # (app, config)
284 "builder-inited": [AddCSSFiles, ReadReports], # (app)
285 } #: A dictionary of all events/callbacks <https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events>`__ used by this domain.
287 def resolve_xref(
288 self,
289 env: BuildEnvironment,
290 fromdocname: str,
291 builder: Builder,
292 typ: str,
293 target: str,
294 node: pending_xref,
295 contnode: Element
296 ) -> Nullable[Element]:
297 raise NotImplementedError()
300if TYPE_CHECKING: 300 ↛ 301line 300 didn't jump to line 301 because the condition on line 300 was never true
301 class setup_ReturnType(TypedDict):
302 version: str
303 env_version: int
304 parallel_read_safe: bool
305 parallel_write_safe: bool
308@export
309def setup(sphinxApplication: Sphinx) -> "setup_ReturnType":
310 """
311 Extension setup function registering the ``report`` domain in Sphinx.
313 It will execute these steps:
315 * register domains, directives and roles.
316 * connect events (register callbacks)
317 * register configuration variables for :file:`conf.py`
319 :param sphinxApplication: The Sphinx application.
320 :return: Dictionary containing the extension version and some properties.
321 """
322 sphinxApplication.add_domain(ReportDomain)
324 # Request new LaTeX package dependencies
325 for latexPackage in ReportDomain.latexPackages:
326 sphinxApplication.add_latex_package(latexPackage)
328 # Register new docutil nodes.
329 for newNode in ReportDomain.nodes:
330 sphinxApplication.add_node(newNode["node"], html=newNode["html"], latex=newNode["latex"])
332 # Register transformations
333 for transformation in ReportDomain.transformations:
334 sphinxApplication.add_post_transform(transformation)
336 # Register callbacks
337 for eventName, callbacks in ReportDomain.callbacks.items():
338 for callback in callbacks:
339 sphinxApplication.connect(eventName, callback)
341 # Register configuration options supported/needed in Sphinx's 'conf.py'
342 for configName, (configDefault, configRebuilt, configTypes) in ReportDomain.configValues.items():
343 sphinxApplication.add_config_value(f"{ReportDomain.name}_{configName}", configDefault, configRebuilt, configTypes)
345 return {
346 "version": __version__, # version of the extension
347 "env_version": int(__version__.split(".")[0]), # version of the data structure stored in the environment
348 'parallel_read_safe': True, # TODO: Not yet evaluated
349 'parallel_write_safe': True, # Internal data structure is used read-only, thus no problems will occur by parallel writing.
350 }