Coverage for /builds/svesch/cikit/src/cikit/macros/sphinx.py: 97%

60 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-10-09 14:17 +0000

1# SPDX-License-Identifier: GPL-3.0-or-later 

2"""Sphinx macro helpers for cikit. 

3 

4This module prepares generated CI artifacts for Sphinx documentation 

5builds. It copies generated files into the documentation tree and 

6provides helpers for stable links inside generated reStructuredText 

7files. 

8 

9Author: 

10 Sven Schlender 

11Date: 

12 2026-03-10 

13""" 

14 

15import logging 

16import os 

17import shutil 

18from pathlib import Path 

19from tempfile import TemporaryDirectory 

20 

21from cikit.artifacts import Artifact, ArtifactType 

22from cikit.command import Command, run_command 

23from cikit.env import write_file 

24from cikit.errors import CiToolsError 

25from cikit.rst_tools import get_sphinx_paths 

26 

27 

28LOGGER = logging.getLogger(__name__) 

29 

30 

31def generate_badges_include(env, docs_dir: Path, output_file: Path = Path("badges.inc")) -> Path: 

32 """Generate an include file containing badge image directives. 

33 

34 Args: 

35 env: Resolved CI run environment. 

36 docs_dir: Sphinx source directory. 

37 output_file: Target include file path relative to ``docs_dir``. 

38 

39 Returns: 

40 Path to the generated include file. 

41 

42 Raises: 

43 CiToolsError: If path resolution or file writing fails. 

44 """ 

45 docs_dir = env.resolve_path(docs_dir) 

46 output_file = docs_dir / output_file 

47 output_dir = output_file.parent.relative_to(docs_dir) 

48 include_base_dir = docs_dir / output_dir 

49 static_dir = docs_dir / "_static" 

50 paths = get_sphinx_paths() 

51 

52 lines = [] 

53 for artifact in env.get_artifacts(artifact_type=ArtifactType.BADGE): 

54 target_path = static_dir / paths.artifact_path(artifact) 

55 rel_path = os.path.relpath(target_path, start=include_base_dir) 

56 

57 lines.append(f".. image:: {Path(rel_path).as_posix()}") 

58 lines.append(f" :alt: {Path(artifact.target_name).stem}") 

59 lines.append(" :height: 20") 

60 lines.append("") 

61 

62 write_file(output_file, "\n".join(lines)) 

63 return output_file 

64 

65 

66def _copy_artifact(artifact: Artifact, target: Path): 

67 """Copy one registered artifact to its Sphinx target. 

68 

69 Args: 

70 artifact: Registered artifact to copy. 

71 target: Destination path in the Sphinx source tree. 

72 

73 Raises: 

74 OSError: If the artifact cannot be copied. 

75 """ 

76 target.parent.mkdir(parents=True, exist_ok=True) 

77 

78 if artifact.path.is_dir(): 

79 shutil.copytree(artifact.path, target, dirs_exist_ok=True) 

80 else: 

81 shutil.copy2(artifact.path, target) 

82 

83 

84def prepare(env, docs_dir: Path): 

85 """Prepare the Sphinx source tree for documentation build. 

86 

87 Args: 

88 env: Resolved CI run environment. 

89 docs_dir: Sphinx source directory. 

90 

91 Raises: 

92 CiToolsError: If cleaning or copying artifacts fails. 

93 """ 

94 docs_dir = env.resolve_path(docs_dir) 

95 static_dir = docs_dir / "_static" 

96 badges_dir = static_dir / "badges" 

97 artifacts_dir = static_dir / "artifacts" 

98 generated_dir = docs_dir / "_generated" 

99 paths = get_sphinx_paths() 

100 

101 try: 

102 for path in (badges_dir, artifacts_dir, generated_dir): 

103 if path.exists(): 

104 shutil.rmtree(path) 

105 

106 for artifact in env.get_artifacts(): 

107 if artifact.artifact_type == ArtifactType.RST: 

108 target = generated_dir / artifact.target_name 

109 else: 

110 target = static_dir / paths.artifact_path(artifact) 

111 

112 _copy_artifact(artifact, target) 

113 except OSError as exp: 

114 raise CiToolsError(f"Error while preparing Sphinx input: {exp}") from exp 

115 

116 

117def sphinx(env, docs_dir: Path) -> Artifact: 

118 """Build and register HTML documentation. 

119 

120 Args: 

121 env: Resolved CI run environment. 

122 docs_dir: Sphinx source directory. 

123 

124 Returns: 

125 Registered HTML documentation folder artifact. 

126 """ 

127 docs_dir = env.resolve_path(docs_dir) 

128 

129 with TemporaryDirectory() as temp_dir: 

130 output_dir = Path(temp_dir) / "html" 

131 

132 cmd = Command("sphinx-build", use_exec_tool=True) 

133 cmd.add_subcommand("build", ["-b", "html", "-D", "cikit_data_dir=${CIKIT_DATA_DIR}", 

134 str(docs_dir), str(output_dir)]) 

135 argv = env.argv(cmd, "build") 

136 

137 run_command(argv, live_output=True, cwd=env["PROJECT_ROOT"]) 

138 

139 return env.copy_artifact(output_dir, ArtifactType.FOLDER)