Coverage for src / ezcompiler / assets / templates / template_loader.py: 17.65%

80 statements  

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

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": 

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

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

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'], 'setup': ['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 template = self.load_template("setup", format_type) 

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

206 

207 # //////////////////////////////////////////////// 

208 # FILE GENERATION METHODS 

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

210 

211 def create_file_from_template( 

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

213 ) -> None: 

214 """ 

215 Create a file from a template. 

216 

217 Args: 

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

219 format_type: Format of the template 

220 output_path: Path where to save the file 

221 **kwargs: Additional arguments for template processing 

222 

223 Raises: 

224 ValueError: If template_type is unknown 

225 

226 Note: 

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

228 """ 

229 if template_type == "config": 

230 content = self.process_config_template( 

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

232 ) 

233 elif template_type == "version": 

234 content = self.process_version_template( 

235 format_type, 

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

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

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

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

240 ) 

241 elif template_type == "setup": 

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

243 else: 

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

245 

246 # Ensure output directory exists 

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

248 

249 # Write the processed template 

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

251 f.write(content) 

252 

253 def generate_template_with_mockup( 

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

255 ) -> None: 

256 """ 

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

258 

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

260 edited manually with real values. 

261 

262 Args: 

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

264 format_type: Format of the template 

265 output_path: Path where to save the file 

266 

267 Raises: 

268 ValueError: If template_type is unknown 

269 Exception: If generation fails 

270 """ 

271 try: 

272 # Load the template 

273 template = self.load_template(template_type, format_type) 

274 

275 # Process with mockup values 

276 if template_type == "config": 

277 content = self._processor.process_template_with_mockup(template) 

278 elif template_type == "version": 

279 mockup_config = self._processor.create_mockup_config() 

280 content = self._processor.process_version_template( 

281 template, 

282 mockup_config["version"], 

283 mockup_config["company_name"], 

284 mockup_config["project_description"], 

285 mockup_config["project_name"], 

286 ) 

287 elif template_type == "setup": 

288 mockup_config = self._processor.create_mockup_config() 

289 content = self._processor.process_setup_template( 

290 template, mockup_config 

291 ) 

292 else: 

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

294 

295 # Ensure output directory exists 

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

297 

298 # Write the processed template with mockup values 

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

300 f.write(content) 

301 

302 except Exception as e: 

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

304 

305 def generate_raw_template( 

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

307 ) -> None: 

308 """ 

309 Generate a raw template file with placeholders. 

310 

311 Creates a template file with placeholders that can be processed 

312 later with real values. 

313 

314 Args: 

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

316 format_type: Format of the template 

317 output_path: Path where to save the file 

318 

319 Raises: 

320 Exception: If generation fails 

321 """ 

322 try: 

323 # Load the template 

324 template = self.load_template(template_type, format_type) 

325 

326 # Ensure output directory exists 

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

328 

329 # Write the raw template with placeholders 

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

331 f.write(template) 

332 

333 except Exception as e: 

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

335 

336 # //////////////////////////////////////////////// 

337 # VALIDATION METHODS 

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

339 

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

341 """ 

342 Validate a template file. 

343 

344 Args: 

345 template_type: Type of template 

346 format_type: Format of the template 

347 

348 Returns: 

349 bool: True if template is valid, False otherwise 

350 

351 Note: 

352 Validates template syntax by loading and checking it. 

353 """ 

354 try: 

355 template = self.load_template(template_type, format_type) 

356 return self._processor.validate_template(template) 

357 except Exception: 

358 return False