Coverage for src / ezcompiler / services / template_service.py: 83.87%

85 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-01 00:22 +0000

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

2# TEMPLATE_SERVICE - Template processing and file generation service 

3# Project: ezcompiler 

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

5 

6""" 

7Template service - Template processing and file generation service for EzCompiler. 

8 

9This module provides the TemplateService class that orchestrates template 

10processing and file generation (setup.py, version_info.txt, config files). 

11 

12Services layer can use WARNING and ERROR log levels. 

13""" 

14 

15from __future__ import annotations 

16 

17# /////////////////////////////////////////////////////////////// 

18# IMPORTS 

19# /////////////////////////////////////////////////////////////// 

20# Standard library imports 

21from pathlib import Path 

22from typing import Any 

23 

24# Local imports 

25from ..adapters import BaseFileWriter 

26from ..adapters._disk_file_writer import DiskFileWriter 

27from ..assets import TemplateLoader 

28from ..shared.exceptions import TemplateError, VersionError 

29from ..utils import FileUtils, TemplateProcessor 

30 

31# /////////////////////////////////////////////////////////////// 

32# CLASSES 

33# /////////////////////////////////////////////////////////////// 

34 

35 

36class TemplateService: 

37 """ 

38 Template processing and file generation service. 

39 

40 Orchestrates template loading, processing, and file generation for 

41 configuration files, setup.py, and version information files. 

42 

43 Attributes: 

44 _template_loader: TemplateLoader instance for template operations 

45 _processor: TemplateProcessor instance for variable substitution 

46 

47 Example: 

48 >>> service = TemplateService() 

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

50 >>> service.generate_setup_file(config, Path("setup.py")) 

51 >>> service.generate_version_file(config, Path("version_info.txt")) 

52 """ 

53 

54 # //////////////////////////////////////////////// 

55 # INITIALIZATION 

56 # //////////////////////////////////////////////// 

57 

58 def __init__(self, file_writer: BaseFileWriter | None = None) -> None: 

59 """ 

60 Initialize the template service. 

61 

62 Creates TemplateLoader and TemplateProcessor instances for 

63 template operations. 

64 """ 

65 if TemplateLoader is None: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true

66 raise TemplateError("TemplateLoader is not available") 

67 if TemplateProcessor is None: 67 ↛ 68line 67 didn't jump to line 68 because the condition on line 67 was never true

68 raise TemplateError("TemplateProcessor is not available") 

69 

70 self._template_loader = TemplateLoader() 

71 self._processor = TemplateProcessor() 

72 self._file_writer = file_writer or DiskFileWriter() 

73 

74 # //////////////////////////////////////////////// 

75 # CONFIG FILE GENERATION 

76 # //////////////////////////////////////////////// 

77 

78 def generate_config_file( 

79 self, 

80 config: dict[str, Any], 

81 output_path: Path, 

82 format_type: str = "yaml", 

83 ) -> None: 

84 """ 

85 Generate a configuration file (YAML or JSON) from template. 

86 

87 Args: 

88 config: Project configuration dictionary 

89 output_path: Path where to save the config file 

90 format_type: Format type ("yaml" or "json") 

91 

92 Raises: 

93 TemplateError: If generation fails 

94 

95 Example: 

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

97 >>> service.generate_config_file(config, Path("ezcompiler.yaml"), "yaml") 

98 """ 

99 try: 

100 # Ensure output directory exists 

101 FileUtils.ensure_parent_directory_exists(output_path) 

102 

103 # Process template 

104 content = self._template_loader.process_config_template(format_type, config) 

105 

106 # Write file 

107 self._file_writer.write_text(output_path, content, encoding="utf-8") 

108 

109 except TemplateError: 

110 raise 

111 except Exception as e: 

112 raise TemplateError(f"Failed to generate config file: {str(e)}") from e 

113 

114 # //////////////////////////////////////////////// 

115 # SETUP FILE GENERATION 

116 # //////////////////////////////////////////////// 

117 

118 def generate_setup_file( 

119 self, 

120 config: dict[str, Any], 

121 output_path: Path | None = None, 

122 output_dir: Path | None = None, 

123 ) -> Path: 

124 """ 

125 Generate a setup.py file from template. 

126 

127 Args: 

128 config: Project configuration dictionary 

129 output_path: Direct path to setup.py file (optional) 

130 output_dir: Directory where to save setup.py (optional, defaults to current dir) 

131 

132 Returns: 

133 Path: Path to the generated setup.py file 

134 

135 Raises: 

136 TemplateError: If generation fails 

137 

138 Example: 

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

140 >>> setup_path = service.generate_setup_file(config, output_dir=Path("build")) 

141 """ 

142 try: 

143 # Determine output path 

144 final_path: Path 

145 if output_path is not None: 

146 final_path = Path(output_path) 

147 FileUtils.ensure_parent_directory_exists(final_path) 

148 else: 

149 target_dir = output_dir if output_dir is not None else Path.cwd() 

150 target_dir = Path(target_dir) 

151 target_dir.mkdir(parents=True, exist_ok=True) 

152 final_path = target_dir / "setup.py" 

153 

154 # Process template 

155 content = self._template_loader.process_setup_template("py", config) 

156 

157 # Write file 

158 self._file_writer.write_text(final_path, content, encoding="utf-8") 

159 

160 return final_path 

161 

162 except TemplateError: 

163 raise 

164 except Exception as e: 

165 raise TemplateError(f"Failed to generate setup file: {str(e)}") from e 

166 

167 # //////////////////////////////////////////////// 

168 # VERSION FILE GENERATION 

169 # //////////////////////////////////////////////// 

170 

171 def generate_version_file( 

172 self, 

173 config: dict[str, Any], 

174 output_path: Path | None = None, 

175 format_type: str = "txt", 

176 ) -> Path: 

177 """ 

178 Generate a version information file from template. 

179 

180 Args: 

181 config: Project configuration dictionary 

182 output_path: Path where to save the version file (optional) 

183 format_type: Template format type (default: "txt") 

184 

185 Returns: 

186 Path: Path to the generated version file 

187 

188 Raises: 

189 VersionError: If generation fails 

190 

191 Note: 

192 If output_path is None, uses version_filename and output_folder from config. 

193 

194 Example: 

195 >>> config = { 

196 ... "version": "1.0.0", 

197 ... "project_name": "MyApp", 

198 ... "version_filename": "version_info.txt", 

199 ... "output_folder": "dist" 

200 ... } 

201 >>> version_path = service.generate_version_file(config) 

202 """ 

203 try: 

204 # Determine output path 

205 final_path: Path 

206 if output_path is not None: 

207 final_path = Path(output_path) 

208 else: 

209 version_file = config.get("version_filename", "version_info.txt") 

210 output_folder = Path(config.get("output_folder", "dist")) 

211 final_path = output_folder / version_file 

212 

213 # Ensure output directory exists 

214 FileUtils.ensure_parent_directory_exists(final_path) 

215 

216 # Extract required fields with defaults 

217 version = config.get("version", "1.0.0") 

218 company_name = config.get("company_name", "") 

219 project_description = config.get("project_description", "") 

220 project_name = config.get("project_name", "MyProject") 

221 

222 # Process template 

223 content = self._template_loader.process_version_template( 

224 format_type=format_type, 

225 version=version, 

226 company_name=company_name, 

227 project_description=project_description, 

228 project_name=project_name, 

229 ) 

230 

231 # Write file 

232 self._file_writer.write_text(final_path, content, encoding="utf-8") 

233 

234 return final_path 

235 

236 except VersionError: 

237 raise 

238 except Exception as e: 

239 raise VersionError(f"Failed to generate version file: {str(e)}") from e 

240 

241 # //////////////////////////////////////////////// 

242 # TEMPLATE UTILITIES 

243 # //////////////////////////////////////////////// 

244 

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

246 """ 

247 Process a configuration template with values. 

248 

249 Args: 

250 format_type: Format type ("yaml" or "json") 

251 config: Configuration dictionary 

252 

253 Returns: 

254 str: Processed template content 

255 

256 Example: 

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

258 >>> content = service.process_config_template("yaml", config) 

259 """ 

260 return self._template_loader.process_config_template(format_type, config) 

261 

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

263 """ 

264 Process a setup template with values. 

265 

266 Args: 

267 config: Configuration dictionary 

268 

269 Returns: 

270 str: Processed template content 

271 

272 Example: 

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

274 >>> content = service.process_setup_template(config) 

275 """ 

276 return self._template_loader.process_setup_template("py", config) 

277 

278 def process_version_template( 

279 self, 

280 version: str, 

281 company_name: str, 

282 project_description: str, 

283 project_name: str, 

284 format_type: str = "txt", 

285 ) -> str: 

286 """ 

287 Process a version template with values. 

288 

289 Args: 

290 version: Project version 

291 company_name: Company name 

292 project_description: Project description 

293 project_name: Project name 

294 format_type: Format type (default: "txt") 

295 

296 Returns: 

297 str: Processed template content 

298 

299 Example: 

300 >>> content = service.process_version_template( 

301 ... "1.0.0", "MyCompany", "Description", "MyApp" 

302 ... ) 

303 """ 

304 return self._template_loader.process_version_template( 

305 format_type, version, company_name, project_description, project_name 

306 ) 

307 

308 # //////////////////////////////////////////////// 

309 # TEMPLATE MANAGEMENT 

310 # //////////////////////////////////////////////// 

311 

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

313 """ 

314 List all available templates. 

315 

316 Returns: 

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

318 

319 Example: 

320 >>> templates = service.list_available_templates() 

321 >>> print(templates) 

322 {'config': ['yaml', 'json'], 'version': ['txt'], 'setup': ['py']} 

323 """ 

324 return self._template_loader.list_available_templates() 

325 

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

327 """ 

328 Validate a template file. 

329 

330 Args: 

331 template_type: Type of template (config, version, setup) 

332 format_type: Format of the template 

333 

334 Returns: 

335 bool: True if template is valid, False otherwise 

336 

337 Example: 

338 >>> is_valid = service.validate_template("config", "yaml") 

339 """ 

340 return self._template_loader.validate_template(template_type, format_type) 

341 

342 # //////////////////////////////////////////////// 

343 # RAW TEMPLATE GENERATION 

344 # //////////////////////////////////////////////// 

345 

346 def generate_mockup_template( 

347 self, 

348 template_type: str, 

349 format_type: str, 

350 output_path: Path, 

351 ) -> None: 

352 """ 

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

354 

355 Args: 

356 template_type: Type of template (config, version, setup) 

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

358 output_path: Path where to save the generated file 

359 

360 Raises: 

361 TemplateError: If generation fails 

362 

363 Example: 

364 >>> service.generate_mockup_template("config", "yaml", Path("ezcompiler.yaml")) 

365 """ 

366 try: 

367 self._template_loader.generate_template_with_mockup( 

368 template_type, format_type, output_path 

369 ) 

370 except TemplateError: 

371 raise 

372 except Exception as e: 

373 raise TemplateError(f"Failed to generate mockup template: {str(e)}") from e 

374 

375 def generate_raw_template( 

376 self, 

377 template_type: str, 

378 format_type: str, 

379 output_path: Path, 

380 ) -> None: 

381 """ 

382 Generate a raw template file with placeholders. 

383 

384 Args: 

385 template_type: Type of template (config, version, setup) 

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

387 output_path: Path where to save the template file 

388 

389 Raises: 

390 TemplateError: If generation fails 

391 

392 Example: 

393 >>> service.generate_raw_template("config", "yaml", Path("template.yaml")) 

394 """ 

395 try: 

396 self._template_loader.generate_raw_template( 

397 template_type, format_type, output_path 

398 ) 

399 except TemplateError: 

400 raise 

401 except Exception as e: 

402 raise TemplateError(f"Failed to generate raw template: {str(e)}") from e