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

86 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-27 06:49 +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 

24from ..adapters.base_file_writer import BaseFileWriter 

25from ..adapters.disk_file_writer import DiskFileWriter 

26 

27# Local imports 

28from ..assets.templates.template_loader import TemplateLoader 

29from ..shared.exceptions import TemplateError, VersionError 

30from ..utils.file_utils import FileUtils 

31from ..utils.template_utils import TemplateProcessor 

32 

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

34# CLASSES 

35# /////////////////////////////////////////////////////////////// 

36 

37 

38class TemplateService: 

39 """ 

40 Template processing and file generation service. 

41 

42 Orchestrates template loading, processing, and file generation for 

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

44 

45 Attributes: 

46 _template_loader: TemplateLoader instance for template operations 

47 _processor: TemplateProcessor instance for variable substitution 

48 

49 Example: 

50 >>> service = TemplateService() 

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

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

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

54 """ 

55 

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

57 # INITIALIZATION 

58 # //////////////////////////////////////////////// 

59 

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

61 """ 

62 Initialize the template service. 

63 

64 Creates TemplateLoader and TemplateProcessor instances for 

65 template operations. 

66 """ 

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

68 raise TemplateError("TemplateLoader is not available") 

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

70 raise TemplateError("TemplateProcessor is not available") 

71 

72 self._template_loader = TemplateLoader() 

73 self._processor = TemplateProcessor() 

74 self._file_writer = file_writer or DiskFileWriter() 

75 

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

77 # CONFIG FILE GENERATION 

78 # //////////////////////////////////////////////// 

79 

80 def generate_config_file( 

81 self, 

82 config: dict[str, Any], 

83 output_path: Path, 

84 format_type: str = "yaml", 

85 ) -> None: 

86 """ 

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

88 

89 Args: 

90 config: Project configuration dictionary 

91 output_path: Path where to save the config file 

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

93 

94 Raises: 

95 TemplateError: If generation fails 

96 

97 Example: 

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

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

100 """ 

101 try: 

102 # Ensure output directory exists 

103 FileUtils.ensure_parent_directory_exists(output_path) 

104 

105 # Process template 

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

107 

108 # Write file 

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

110 

111 except TemplateError: 

112 raise 

113 except Exception as e: 

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

115 

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

117 # SETUP FILE GENERATION 

118 # //////////////////////////////////////////////// 

119 

120 def generate_setup_file( 

121 self, 

122 config: dict[str, Any], 

123 output_path: Path | None = None, 

124 output_dir: Path | None = None, 

125 ) -> Path: 

126 """ 

127 Generate a setup.py file from template. 

128 

129 Args: 

130 config: Project configuration dictionary 

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

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

133 

134 Returns: 

135 Path: Path to the generated setup.py file 

136 

137 Raises: 

138 TemplateError: If generation fails 

139 

140 Example: 

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

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

143 """ 

144 try: 

145 # Determine output path 

146 final_path: Path 

147 if output_path is not None: 

148 final_path = Path(output_path) 

149 FileUtils.ensure_parent_directory_exists(final_path) 

150 else: 

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

152 target_dir = Path(target_dir) 

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

154 final_path = target_dir / "setup.py" 

155 

156 # Process template 

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

158 

159 # Write file 

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

161 

162 return final_path 

163 

164 except TemplateError: 

165 raise 

166 except Exception as e: 

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

168 

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

170 # VERSION FILE GENERATION 

171 # //////////////////////////////////////////////// 

172 

173 def generate_version_file( 

174 self, 

175 config: dict[str, Any], 

176 output_path: Path | None = None, 

177 format_type: str = "txt", 

178 ) -> Path: 

179 """ 

180 Generate a version information file from template. 

181 

182 Args: 

183 config: Project configuration dictionary 

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

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

186 

187 Returns: 

188 Path: Path to the generated version file 

189 

190 Raises: 

191 VersionError: If generation fails 

192 

193 Note: 

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

195 

196 Example: 

197 >>> config = { 

198 ... "version": "1.0.0", 

199 ... "project_name": "MyApp", 

200 ... "version_filename": "version_info.txt", 

201 ... "output_folder": "dist" 

202 ... } 

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

204 """ 

205 try: 

206 # Determine output path 

207 final_path: Path 

208 if output_path is not None: 

209 final_path = Path(output_path) 

210 else: 

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

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

213 final_path = output_folder / version_file 

214 

215 # Ensure output directory exists 

216 FileUtils.ensure_parent_directory_exists(final_path) 

217 

218 # Extract required fields with defaults 

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

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

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

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

223 

224 # Process template 

225 content = self._template_loader.process_version_template( 

226 format_type=format_type, 

227 version=version, 

228 company_name=company_name, 

229 project_description=project_description, 

230 project_name=project_name, 

231 ) 

232 

233 # Write file 

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

235 

236 return final_path 

237 

238 except VersionError: 

239 raise 

240 except Exception as e: 

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

242 

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

244 # TEMPLATE UTILITIES 

245 # //////////////////////////////////////////////// 

246 

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

248 """ 

249 Process a configuration template with values. 

250 

251 Args: 

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

253 config: Configuration dictionary 

254 

255 Returns: 

256 str: Processed template content 

257 

258 Example: 

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

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

261 """ 

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

263 

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

265 """ 

266 Process a setup template with values. 

267 

268 Args: 

269 config: Configuration dictionary 

270 

271 Returns: 

272 str: Processed template content 

273 

274 Example: 

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

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

277 """ 

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

279 

280 def process_version_template( 

281 self, 

282 version: str, 

283 company_name: str, 

284 project_description: str, 

285 project_name: str, 

286 format_type: str = "txt", 

287 ) -> str: 

288 """ 

289 Process a version template with values. 

290 

291 Args: 

292 version: Project version 

293 company_name: Company name 

294 project_description: Project description 

295 project_name: Project name 

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

297 

298 Returns: 

299 str: Processed template content 

300 

301 Example: 

302 >>> content = service.process_version_template( 

303 ... "1.0.0", "MyCompany", "Description", "MyApp" 

304 ... ) 

305 """ 

306 return self._template_loader.process_version_template( 

307 format_type, version, company_name, project_description, project_name 

308 ) 

309 

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

311 # TEMPLATE MANAGEMENT 

312 # //////////////////////////////////////////////// 

313 

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

315 """ 

316 List all available templates. 

317 

318 Returns: 

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

320 

321 Example: 

322 >>> templates = service.list_available_templates() 

323 >>> print(templates) 

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

325 """ 

326 return self._template_loader.list_available_templates() 

327 

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

329 """ 

330 Validate a template file. 

331 

332 Args: 

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

334 format_type: Format of the template 

335 

336 Returns: 

337 bool: True if template is valid, False otherwise 

338 

339 Example: 

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

341 """ 

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

343 

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

345 # RAW TEMPLATE GENERATION 

346 # //////////////////////////////////////////////// 

347 

348 def generate_mockup_template( 

349 self, 

350 template_type: str, 

351 format_type: str, 

352 output_path: Path, 

353 ) -> None: 

354 """ 

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

356 

357 Args: 

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

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

360 output_path: Path where to save the generated file 

361 

362 Raises: 

363 TemplateError: If generation fails 

364 

365 Example: 

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

367 """ 

368 try: 

369 self._template_loader.generate_template_with_mockup( 

370 template_type, format_type, output_path 

371 ) 

372 except TemplateError: 

373 raise 

374 except Exception as e: 

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

376 

377 def generate_raw_template( 

378 self, 

379 template_type: str, 

380 format_type: str, 

381 output_path: Path, 

382 ) -> None: 

383 """ 

384 Generate a raw template file with placeholders. 

385 

386 Args: 

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

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

389 output_path: Path where to save the template file 

390 

391 Raises: 

392 TemplateError: If generation fails 

393 

394 Example: 

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

396 """ 

397 try: 

398 self._template_loader.generate_raw_template( 

399 template_type, format_type, output_path 

400 ) 

401 except TemplateError: 

402 raise 

403 except Exception as e: 

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