Coverage for sphinx_reports/__init__.py: 55%
112 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-13 18:02 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-13 18:02 +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.1"
47__keywords__ = [
48 "Python3", "Sphinx", "Extension", "Report", "doc-string", "interrogate", "Code Coverage", "Coverage",
49 "Documentation Coverage", "Unittest", "Dependencies", "Summary"
50]
51__project_url__ = "https://github.com/pyTooling/sphinx-reports"
52__documentation_url__ = "https://pyTooling.github.io/sphinx-reports"
53__issue_tracker_url__ = "https://GitHub.com/pyTooling/sphinx-reports/issues"
55from hashlib import md5
56from pathlib import Path
57from typing import TYPE_CHECKING, Any, Tuple, Dict, Optional as Nullable, TypedDict, List, Callable, Type
59from docutils.nodes import Element
60from docutils.transforms import Transform
61from sphinx.addnodes import pending_xref
62from sphinx.application import Sphinx
63from sphinx.builders import Builder
64from sphinx.config import Config
65from sphinx.domains import Domain
66from sphinx.environment import BuildEnvironment
67from sphinx.util.logging import getLogger
68from pyTooling.Decorators import export
69from pyTooling.Common import readResourceFile
71from sphinx_reports import static as ResourcePackage
72from sphinx_reports.Common import ReportExtensionError, visitFunc, departFunc
73from sphinx_reports.Node import Landscape
74from sphinx_reports.Workaround import FixLatexTableWidths
75from sphinx_reports.HTML import translateLandscape as translateLandscapeAsHTML
76from sphinx_reports.LaTeX import translateLandscape as translateLandscapeAsLaTeX
79@export
80class RegisteredNode(TypedDict):
81 """
82 Type information for an entry in :attr:`ReportDomain.nodes`.
83 """
84 name: str #: Name of the new docutils node to register.
85 node: Type[Element] #: The new node class to register.
86 html: Tuple[visitFunc, departFunc] #: A tuple of visit and depart functions rendering the new node in case of HTML output.
87 latex: Tuple[visitFunc, departFunc] #: A tuple of visit and depart functions rendering the new node in case of LaTeX output.
90@export
91class ReportDomain(Domain):
92 """
93 A Sphinx extension providing a ``report`` domain to integrate reports and summaries into a Sphinx-based documentation.
95 .. rubric:: New directives:
97 * :rst:dir:`report:code-coverage`
98 * :rst:dir:`report:code-coverage-legend`
99 * :rst:dir:`report:doc-coverage`
100 * :rst:dir:`report:doc-coverage-legend`
101 * :rst:dir:`report:dependency-table`
102 * :rst:dir:`report:unittest-summary`
104 .. rubric:: New roles:
106 * *None*
108 .. rubric:: New indices:
110 * *None*
112 .. rubric:: Configuration variables
114 All configuration variables in :file:`conf.py` are prefixed with ``report_*``:
116 * ``report_codecov_packages``
117 * ``report_doccov_packages``
118 * ``report_unittest_testsuites``
120 """
122 name = "report" #: The name of this domain
123 label = "rpt" #: The label of this domain
125 dependencies: List[str] = [
126 ] #: A list of other extensions this domain depends on.
128 latexPackages: Tuple[str, ...] = (
129 "pdflscape",
130 )
131 nodes: Tuple[RegisteredNode, ...] = (
132 { "name": "Landscape",
133 "node": Landscape,
134 "html": translateLandscapeAsHTML,
135 "latex": translateLandscapeAsLaTeX
136 },
137 )
138 transformations: Tuple[Type[Transform], ...] = (
139 FixLatexTableWidths,
140 )
142 from sphinx_reports.CodeCoverage import CodeCoverage, CodeCoverageLegend, ModuleCoverage
143 from sphinx_reports.DocCoverage import DocStrCoverage, DocCoverageLegend
144 from sphinx_reports.Dependency import DependencyTable
145 from sphinx_reports.Unittest import UnittestSummary
147 directives = {
148 "code-coverage": CodeCoverage,
149 "code-coverage-legend": CodeCoverageLegend,
150 "module-coverage": ModuleCoverage,
151 "doc-coverage": DocStrCoverage,
152 "doc-coverage-legend": DocCoverageLegend,
153 "dependency-table": DependencyTable,
154 "unittest-summary": UnittestSummary,
155 } #: A dictionary of all directives in this domain.
157 roles = {
158 # "design": DesignRole,
159 } #: A dictionary of all roles in this domain.
161 indices = [
162 # LibraryIndex,
163 ] #: A list of all indices in this domain.
165 from sphinx_reports.CodeCoverage import CodeCoverageBase
166 from sphinx_reports.DocCoverage import DocCoverageBase
167 from sphinx_reports.Dependency import DependencyTable
168 from sphinx_reports.Unittest import UnittestSummary
170 configValues: Dict[str, Tuple[Any, str, Any]] = {
171 **CodeCoverageBase.configValues,
172 **DocCoverageBase.configValues,
173 **UnittestSummary.configValues,
174 **DependencyTable.configValues,
175 } #: A dictionary of all configuration values used by this domain. (name: (default, rebuilt, type))
177 del CodeCoverageBase
178 del CodeCoverage
179 del CodeCoverageLegend
180 del ModuleCoverage
181 del DocCoverageBase
182 del DocStrCoverage
183 del DocCoverageLegend
184 del DependencyTable
185 del UnittestSummary
187 initial_data = {
188 # "reports": {}
189 } #: A dictionary of all global data fields used by this domain.
191 # @property
192 # def Reports(self) -> Dict[str, Any]:
193 # return self.data["reports"]
195 @staticmethod
196 def CheckConfigurationVariables(sphinxApplication: Sphinx, config: Config) -> None:
197 """
198 Call back for Sphinx ``config-inited`` event.
200 This callback will verify configuration variables used by that domain.
202 .. seealso::
204 Sphinx *builder-inited* event
205 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events
207 :param sphinxApplication: The Sphinx application.
208 :param config: Sphinx configuration parsed from ``conf.py``.
209 """
210 from sphinx_reports.CodeCoverage import CodeCoverageBase
211 from sphinx_reports.DocCoverage import DocCoverageBase
212 from sphinx_reports.Unittest import UnittestSummary
214 checkConfigurations = (
215 CodeCoverageBase.CheckConfiguration,
216 DocCoverageBase.CheckConfiguration,
217 UnittestSummary.CheckConfiguration,
218 )
220 for checkConfiguration in checkConfigurations:
221 try:
222 checkConfiguration(sphinxApplication, config)
223 except ReportExtensionError as ex:
224 logger = getLogger(__name__)
225 logger.error(f"Caught {ex.__class__.__name__} when checking configuration variables.\n {ex}")
227 @staticmethod
228 def AddCSSFiles(sphinxApplication: Sphinx) -> None:
229 """
230 Call back for Sphinx ``builder-inited`` event.
232 This callback will copy the CSS file(s) to the build directory.
234 .. seealso::
236 Sphinx *builder-inited* event
237 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events
239 :param sphinxApplication: The Sphinx application.
240 """
241 # Create a new static path for this extension
242 staticDirectory = (Path(sphinxApplication.outdir) / "_report_static").resolve()
243 staticDirectory.mkdir(exist_ok=True)
244 sphinxApplication.config.html_static_path.append(str(staticDirectory))
246 # Read the CSS content from package resources and hash it
247 cssFilename = "sphinx-reports.css"
248 cssContent = readResourceFile(ResourcePackage, cssFilename)
250 # Compute md5 hash of CSS file
251 hash = md5(cssContent.encode("utf8")).hexdigest() # nosec B324
253 # Write the CSS file into output directory
254 cssFile = staticDirectory / f"sphinx-reports.{hash}.css"
255 sphinxApplication.add_css_file(cssFile.name)
257 if not cssFile.exists():
258 # Purge old CSS files
259 for file in staticDirectory.glob("*.css"):
260 file.unlink()
262 # Write CSS content
263 cssFile.write_text(cssContent, encoding="utf8")
265 @staticmethod
266 def ReadReports(sphinxApplication: Sphinx) -> None:
267 """
268 Call back for Sphinx ``builder-inited`` event.
270 This callback will read the linked report files
272 .. seealso::
274 Sphinx *builder-inited* event
275 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events
277 :param sphinxApplication: The Sphinx application.
278 """
279 from sphinx_reports.CodeCoverage import CodeCoverageBase
280 from sphinx_reports.Unittest import UnittestSummary
282 CodeCoverageBase.ReadReports(sphinxApplication)
283 UnittestSummary.ReadReports(sphinxApplication)
285 callbacks: Dict[str, List[Callable]] = {
286 "config-inited": [CheckConfigurationVariables], # (app, config)
287 "builder-inited": [AddCSSFiles, ReadReports], # (app)
288 } #: A dictionary of all events/callbacks <https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events>`__ used by this domain.
290 def resolve_xref(
291 self,
292 env: BuildEnvironment,
293 fromdocname: str,
294 builder: Builder,
295 typ: str,
296 target: str,
297 node: pending_xref,
298 contnode: Element
299 ) -> Nullable[Element]:
300 raise NotImplementedError()
303if TYPE_CHECKING: 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true
304 class setup_ReturnType(TypedDict):
305 version: str
306 env_version: int
307 parallel_read_safe: bool
308 parallel_write_safe: bool
311@export
312def setup(sphinxApplication: Sphinx) -> "setup_ReturnType":
313 """
314 Extension setup function registering the ``report`` domain in Sphinx.
316 It will execute these steps:
318 * register domains, directives and roles.
319 * connect events (register callbacks)
320 * register configuration variables for :file:`conf.py`
322 :param sphinxApplication: The Sphinx application.
323 :return: Dictionary containing the extension version and some properties.
324 """
325 sphinxApplication.add_domain(ReportDomain)
327 # Request new LaTeX package dependencies
328 for latexPackage in ReportDomain.latexPackages:
329 sphinxApplication.add_latex_package(latexPackage)
331 # Register new docutil nodes.
332 for newNode in ReportDomain.nodes:
333 sphinxApplication.add_node(newNode["node"], html=newNode["html"], latex=newNode["latex"])
335 # Register transformations
336 for transformation in ReportDomain.transformations:
337 sphinxApplication.add_post_transform(transformation)
339 # Register callbacks
340 for eventName, callbacks in ReportDomain.callbacks.items():
341 for callback in callbacks:
342 sphinxApplication.connect(eventName, callback)
344 # Register configuration options supported/needed in Sphinx's 'conf.py'
345 for configName, (configDefault, configRebuilt, configTypes) in ReportDomain.configValues.items():
346 sphinxApplication.add_config_value(f"{ReportDomain.name}_{configName}", configDefault, configRebuilt, configTypes)
348 return {
349 "version": __version__, # version of the extension
350 "env_version": int(__version__.split(".")[0]), # version of the data structure stored in the environment
351 'parallel_read_safe': True, # TODO: Not yet evaluated
352 'parallel_write_safe': True, # Internal data structure is used read-only, thus no problems will occur by parallel writing.
353 }