Coverage for src/ezcompiler/assets/templates/_template_loader.py: 29.41%

80 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-16 16:37 +0000

1# /////////////////////////////////////////////////////////////// 

2# TEMPLATE_LOADER - Template loading and resource access 

3# Project: ezcompiler 

4# /////////////////////////////////////////////////////////////// 

5 

6""" 

7Template loader - Template loading and resource access for EzCompiler. 

8 

9This module provides functionality for loading template files from the 

10assets directory. It serves as the resource access layer for templates, 

11delegating processing to TemplateProcessor in utils. 

12""" 

13 

14from __future__ import annotations 

15 

16# /////////////////////////////////////////////////////////////// 

17# IMPORTS 

18# /////////////////////////////////////////////////////////////// 

19# Standard library imports 

20from pathlib import Path 

21from typing import Any 

22 

23# Local imports 

24from ...utils import TemplateProcessor 

25 

26# /////////////////////////////////////////////////////////////// 

27# CLASSES 

28# /////////////////////////////////////////////////////////////// 

29 

30 

31class TemplateLoader: 

32 """ 

33 Loader for template resource files. 

34 

35 Provides methods to load templates from .template files and process 

36 them with variable substitution for configuration, version info, 

37 and setup scripts. 

38 

39 Attributes: 

40 _templates_dir: Base directory containing template subdirectories 

41 _processor: TemplateProcessor instance for variable substitution 

42 

43 Example: 

44 >>> loader = TemplateLoader() 

45 >>> template = loader.load_template("config", "yaml") 

46 >>> content = loader.process_config_template("yaml", config_dict) 

47 """ 

48 

49 # //////////////////////////////////////////////// 

50 # INITIALIZATION 

51 # //////////////////////////////////////////////// 

52 

53 def __init__(self) -> None: 

54 """ 

55 Initialize the template loader. 

56 

57 Note: 

58 Automatically detects templates_dir from module location. 

59 """ 

60 self._templates_dir = Path(__file__).parent 

61 self._processor = TemplateProcessor() 

62 

63 # //////////////////////////////////////////////// 

64 # PATH AND LOADING METHODS 

65 # //////////////////////////////////////////////// 

66 

67 def _get_template_path(self, template_type: str, format_type: str) -> Path: 

68 """ 

69 Get the path to a specific template file. 

70 

71 Args: 

72 template_type: Type of template (config, version, build) 

73 format_type: Format of the template (yaml, json, txt, py) 

74 

75 Returns: 

76 Path: Path to the template file 

77 

78 Note: 

79 Version templates have special naming: version_info.{format}.template 

80 Other templates follow: {type}.{format}.template 

81 """ 

82 if template_type == "version": 82 ↛ 84line 82 didn't jump to line 84 because the condition on line 82 was never true

83 # Special case for version templates 

84 return ( 

85 self._templates_dir 

86 / template_type 

87 / f"version_info.{format_type}.template" 

88 ) 

89 else: 

90 return ( 

91 self._templates_dir 

92 / template_type 

93 / f"{template_type}.{format_type}.template" 

94 ) 

95 

96 def load_template(self, template_type: str, format_type: str) -> str: 

97 """ 

98 Load a template file. 

99 

100 Args: 

101 template_type: Type of template (config, version, build) 

102 format_type: Format of the template (yaml, json, txt, py) 

103 

104 Returns: 

105 str: Template content as string 

106 

107 Raises: 

108 FileNotFoundError: If template file doesn't exist 

109 """ 

110 template_path = self._get_template_path(template_type, format_type) 

111 

112 if not template_path.exists(): 112 ↛ 113line 112 didn't jump to line 113 because the condition on line 112 was never true

113 raise FileNotFoundError(f"Template not found: {template_path}") 

114 

115 with open(template_path, encoding="utf-8") as f: 

116 return f.read() 

117 

118 def list_available_templates(self) -> dict[str, list[str]]: 

119 """ 

120 List all available templates. 

121 

122 Returns: 

123 dict[str, list[str]]: Dictionary mapping template types to available formats 

124 

125 Example: 

126 >>> loader.list_available_templates() 

127 {'config': ['yaml', 'json'], 'version': ['txt'], 'build': ['py']} 

128 """ 

129 templates: dict[str, list[str]] = {} 

130 

131 for template_dir in self._templates_dir.iterdir(): 

132 if template_dir.is_dir(): 

133 template_type = template_dir.name 

134 formats = [] 

135 

136 for template_file in template_dir.glob("*.template"): 

137 # Extract format from filename (e.g., "config.yaml.template" -> "yaml") 

138 format_type = template_file.stem.split(".")[-1] 

139 formats.append(format_type) 

140 

141 templates[template_type] = formats 

142 

143 return templates 

144 

145 # //////////////////////////////////////////////// 

146 # TEMPLATE PROCESSING METHODS 

147 # //////////////////////////////////////////////// 

148 

149 def process_config_template(self, format_type: str, config: dict[str, Any]) -> str: 

150 """ 

151 Process a configuration template. 

152 

153 Args: 

154 format_type: Format of the template (yaml, json) 

155 config: Configuration values dictionary 

156 

157 Returns: 

158 str: Processed template string 

159 

160 Example: 

161 >>> config = {"version": "1.0.0", "project_name": "MyApp"} 

162 >>> content = loader.process_config_template("yaml", config) 

163 """ 

164 template = self.load_template("config", format_type) 

165 return self._processor.process_config_template(template, config) 

166 

167 def process_version_template( 

168 self, 

169 format_type: str, 

170 version: str, 

171 company_name: str, 

172 project_description: str, 

173 project_name: str, 

174 ) -> str: 

175 """ 

176 Process a version template. 

177 

178 Args: 

179 format_type: Format of the template (txt) 

180 version: Project version (e.g., "1.0.0") 

181 company_name: Company name 

182 project_description: Project description 

183 project_name: Project name 

184 

185 Returns: 

186 str: Processed template string 

187 """ 

188 template = self.load_template("version", format_type) 

189 return self._processor.process_version_template( 

190 template, version, company_name, project_description, project_name 

191 ) 

192 

193 def process_setup_template(self, format_type: str, config: dict[str, Any]) -> str: 

194 """ 

195 Process a setup template. 

196 

197 Args: 

198 format_type: Format of the template (py) 

199 config: Configuration values dictionary 

200 

201 Returns: 

202 str: Processed template string 

203 """ 

204 # The on-disk template type is "build"; the method name is kept 

205 # for API compatibility. 

206 template = self.load_template("build", format_type) 

207 return self._processor.process_setup_template(template, config) 

208 

209 # //////////////////////////////////////////////// 

210 # FILE GENERATION METHODS 

211 # //////////////////////////////////////////////// 

212 

213 def create_file_from_template( 

214 self, template_type: str, format_type: str, output_path: Path, **kwargs: Any 

215 ) -> None: 

216 """ 

217 Create a file from a template. 

218 

219 Args: 

220 template_type: Type of template (config, version, build) 

221 format_type: Format of the template 

222 output_path: Path where to save the file 

223 **kwargs: Additional arguments for template processing 

224 

225 Raises: 

226 ValueError: If template_type is unknown 

227 

228 Note: 

229 Automatically creates parent directories if they don't exist. 

230 """ 

231 if template_type == "config": 

232 content = self.process_config_template( 

233 format_type, kwargs.get("config", {}) 

234 ) 

235 elif template_type == "version": 

236 content = self.process_version_template( 

237 format_type, 

238 kwargs.get("version", "1.0.0"), 

239 kwargs.get("company_name", "Company"), 

240 kwargs.get("project_description", "Description"), 

241 kwargs.get("project_name", "Project"), 

242 ) 

243 elif template_type == "build": 

244 content = self.process_setup_template(format_type, kwargs.get("config", {})) 

245 else: 

246 raise ValueError(f"Unknown template type: {template_type}") 

247 

248 # Ensure output directory exists 

249 output_path.parent.mkdir(parents=True, exist_ok=True) 

250 

251 # Write the processed template 

252 with open(output_path, "w", encoding="utf-8") as f: 

253 f.write(content) 

254 

255 def generate_template_with_mockup( 

256 self, template_type: str, format_type: str, output_path: Path 

257 ) -> None: 

258 """ 

259 Generate a template file with mockup values instead of placeholders. 

260 

261 Creates a valid file that can be used as a starting point and 

262 edited manually with real values. 

263 

264 Args: 

265 template_type: Type of template (config, version, build) 

266 format_type: Format of the template 

267 output_path: Path where to save the file 

268 

269 Raises: 

270 ValueError: If template_type is unknown 

271 Exception: If generation fails 

272 """ 

273 try: 

274 # Load the template 

275 template = self.load_template(template_type, format_type) 

276 

277 # Process with mockup values 

278 if template_type == "config": 

279 content = self._processor.process_template_with_mockup(template) 

280 elif template_type == "version": 

281 mockup_config = self._processor.create_mockup_config() 

282 content = self._processor.process_version_template( 

283 template, 

284 mockup_config["version"], 

285 mockup_config["company_name"], 

286 mockup_config["project_description"], 

287 mockup_config["project_name"], 

288 ) 

289 elif template_type == "build": 

290 mockup_config = self._processor.create_mockup_config() 

291 content = self._processor.process_setup_template( 

292 template, mockup_config 

293 ) 

294 else: 

295 raise ValueError(f"Unknown template type: {template_type}") 

296 

297 # Ensure output directory exists 

298 output_path.parent.mkdir(parents=True, exist_ok=True) 

299 

300 # Write the processed template with mockup values 

301 with open(output_path, "w", encoding="utf-8") as f: 

302 f.write(content) 

303 

304 except Exception as e: 

305 raise Exception(f"Failed to generate template with mockup: {e}") from e 

306 

307 def generate_raw_template( 

308 self, template_type: str, format_type: str, output_path: Path 

309 ) -> None: 

310 """ 

311 Generate a raw template file with placeholders. 

312 

313 Creates a template file with placeholders that can be processed 

314 later with real values. 

315 

316 Args: 

317 template_type: Type of template (config, version, build) 

318 format_type: Format of the template 

319 output_path: Path where to save the file 

320 

321 Raises: 

322 Exception: If generation fails 

323 """ 

324 try: 

325 # Load the template 

326 template = self.load_template(template_type, format_type) 

327 

328 # Ensure output directory exists 

329 output_path.parent.mkdir(parents=True, exist_ok=True) 

330 

331 # Write the raw template with placeholders 

332 with open(output_path, "w", encoding="utf-8") as f: 

333 f.write(template) 

334 

335 except Exception as e: 

336 raise Exception(f"Failed to generate raw template: {e}") from e 

337 

338 # //////////////////////////////////////////////// 

339 # VALIDATION METHODS 

340 # //////////////////////////////////////////////// 

341 

342 def validate_template(self, template_type: str, format_type: str) -> bool: 

343 """ 

344 Validate a template file. 

345 

346 Args: 

347 template_type: Type of template 

348 format_type: Format of the template 

349 

350 Returns: 

351 bool: True if template is valid, False otherwise 

352 

353 Note: 

354 Validates template syntax by loading and checking it. 

355 """ 

356 try: 

357 template = self.load_template(template_type, format_type) 

358 return self._processor.validate_template(template) 

359 except Exception: 

360 return False