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

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.** 

33 

34Supported reports: 

35 

36* :ref:`UNITTEST` 

37* :ref:`DOCCOV` 

38* :ref:`CODECOV` 

39* :ref:`DEP` 

40 

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] 

51 

52from hashlib import md5 

53from pathlib import Path 

54from typing import TYPE_CHECKING, Any, Tuple, Dict, Optional as Nullable, TypedDict, List, Callable, Type 

55 

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 

67 

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 

74 

75 

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. 

85 

86 

87@export 

88class ReportDomain(Domain): 

89 """ 

90 A Sphinx extension providing a ``report`` domain to integrate reports and summaries into a Sphinx-based documentation. 

91 

92 .. rubric:: New directives: 

93 

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` 

100 

101 .. rubric:: New roles: 

102 

103 * *None* 

104 

105 .. rubric:: New indices: 

106 

107 * *None* 

108 

109 .. rubric:: Configuration variables 

110 

111 All configuration variables in :file:`conf.py` are prefixed with ``report_*``: 

112 

113 * ``report_codecov_packages`` 

114 * ``report_doccov_packages`` 

115 * ``report_unittest_testsuites`` 

116 

117 """ 

118 

119 name = "report" #: The name of this domain 

120 label = "rpt" #: The label of this domain 

121 

122 dependencies: List[str] = [ 

123 ] #: A list of other extensions this domain depends on. 

124 

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 ) 

138 

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 

143 

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. 

153 

154 roles = { 

155 # "design": DesignRole, 

156 } #: A dictionary of all roles in this domain. 

157 

158 indices = [ 

159 # LibraryIndex, 

160 ] #: A list of all indices in this domain. 

161 

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 

166 

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)) 

173 

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 

183 

184 initial_data = { 

185 # "reports": {} 

186 } #: A dictionary of all global data fields used by this domain. 

187 

188 # @property 

189 # def Reports(self) -> Dict[str, Any]: 

190 # return self.data["reports"] 

191 

192 @staticmethod 

193 def CheckConfigurationVariables(sphinxApplication: Sphinx, config: Config) -> None: 

194 """ 

195 Call back for Sphinx ``config-inited`` event. 

196 

197 This callback will verify configuration variables used by that domain. 

198 

199 .. seealso:: 

200 

201 Sphinx *builder-inited* event 

202 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events 

203 

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 

210 

211 checkConfigurations = ( 

212 CodeCoverageBase.CheckConfiguration, 

213 DocCoverageBase.CheckConfiguration, 

214 UnittestSummary.CheckConfiguration, 

215 ) 

216 

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}") 

223 

224 @staticmethod 

225 def AddCSSFiles(sphinxApplication: Sphinx) -> None: 

226 """ 

227 Call back for Sphinx ``builder-inited`` event. 

228 

229 This callback will copy the CSS file(s) to the build directory. 

230 

231 .. seealso:: 

232 

233 Sphinx *builder-inited* event 

234 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events 

235 

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)) 

242 

243 # Read the CSS content from package resources and hash it 

244 cssFilename = "sphinx-reports.css" 

245 cssContent = readResourceFile(ResourcePackage, cssFilename) 

246 

247 # Compute md5 hash of CSS file 

248 hash = md5(cssContent.encode("utf8")).hexdigest() # nosec B324 

249 

250 # Write the CSS file into output directory 

251 cssFile = staticDirectory / f"sphinx-reports.{hash}.css" 

252 sphinxApplication.add_css_file(cssFile.name) 

253 

254 if not cssFile.exists(): 

255 # Purge old CSS files 

256 for file in staticDirectory.glob("*.css"): 

257 file.unlink() 

258 

259 # Write CSS content 

260 cssFile.write_text(cssContent, encoding="utf8") 

261 

262 @staticmethod 

263 def ReadReports(sphinxApplication: Sphinx) -> None: 

264 """ 

265 Call back for Sphinx ``builder-inited`` event. 

266 

267 This callback will read the linked report files 

268 

269 .. seealso:: 

270 

271 Sphinx *builder-inited* event 

272 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events 

273 

274 :param sphinxApplication: The Sphinx application. 

275 """ 

276 from sphinx_reports.CodeCoverage import CodeCoverageBase 

277 from sphinx_reports.Unittest import UnittestSummary 

278 

279 CodeCoverageBase.ReadReports(sphinxApplication) 

280 UnittestSummary.ReadReports(sphinxApplication) 

281 

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. 

286 

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() 

298 

299 

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 

306 

307 

308@export 

309def setup(sphinxApplication: Sphinx) -> "setup_ReturnType": 

310 """ 

311 Extension setup function registering the ``report`` domain in Sphinx. 

312 

313 It will execute these steps: 

314 

315 * register domains, directives and roles. 

316 * connect events (register callbacks) 

317 * register configuration variables for :file:`conf.py` 

318 

319 :param sphinxApplication: The Sphinx application. 

320 :return: Dictionary containing the extension version and some properties. 

321 """ 

322 sphinxApplication.add_domain(ReportDomain) 

323 

324 # Request new LaTeX package dependencies 

325 for latexPackage in ReportDomain.latexPackages: 

326 sphinxApplication.add_latex_package(latexPackage) 

327 

328 # Register new docutil nodes. 

329 for newNode in ReportDomain.nodes: 

330 sphinxApplication.add_node(newNode["node"], html=newNode["html"], latex=newNode["latex"]) 

331 

332 # Register transformations 

333 for transformation in ReportDomain.transformations: 

334 sphinxApplication.add_post_transform(transformation) 

335 

336 # Register callbacks 

337 for eventName, callbacks in ReportDomain.callbacks.items(): 

338 for callback in callbacks: 

339 sphinxApplication.connect(eventName, callback) 

340 

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) 

344 

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 }