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
« 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# ///////////////////////////////////////////////////////////////
6"""
7Template loader - Template loading and resource access for EzCompiler.
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"""
14from __future__ import annotations
16# ///////////////////////////////////////////////////////////////
17# IMPORTS
18# ///////////////////////////////////////////////////////////////
19# Standard library imports
20from pathlib import Path
21from typing import Any
23# Local imports
24from ...utils import TemplateProcessor
26# ///////////////////////////////////////////////////////////////
27# CLASSES
28# ///////////////////////////////////////////////////////////////
31class TemplateLoader:
32 """
33 Loader for template resource files.
35 Provides methods to load templates from .template files and process
36 them with variable substitution for configuration, version info,
37 and setup scripts.
39 Attributes:
40 _templates_dir: Base directory containing template subdirectories
41 _processor: TemplateProcessor instance for variable substitution
43 Example:
44 >>> loader = TemplateLoader()
45 >>> template = loader.load_template("config", "yaml")
46 >>> content = loader.process_config_template("yaml", config_dict)
47 """
49 # ////////////////////////////////////////////////
50 # INITIALIZATION
51 # ////////////////////////////////////////////////
53 def __init__(self) -> None:
54 """
55 Initialize the template loader.
57 Note:
58 Automatically detects templates_dir from module location.
59 """
60 self._templates_dir = Path(__file__).parent
61 self._processor = TemplateProcessor()
63 # ////////////////////////////////////////////////
64 # PATH AND LOADING METHODS
65 # ////////////////////////////////////////////////
67 def _get_template_path(self, template_type: str, format_type: str) -> Path:
68 """
69 Get the path to a specific template file.
71 Args:
72 template_type: Type of template (config, version, build)
73 format_type: Format of the template (yaml, json, txt, py)
75 Returns:
76 Path: Path to the template file
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 )
96 def load_template(self, template_type: str, format_type: str) -> str:
97 """
98 Load a template file.
100 Args:
101 template_type: Type of template (config, version, build)
102 format_type: Format of the template (yaml, json, txt, py)
104 Returns:
105 str: Template content as string
107 Raises:
108 FileNotFoundError: If template file doesn't exist
109 """
110 template_path = self._get_template_path(template_type, format_type)
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}")
115 with open(template_path, encoding="utf-8") as f:
116 return f.read()
118 def list_available_templates(self) -> dict[str, list[str]]:
119 """
120 List all available templates.
122 Returns:
123 dict[str, list[str]]: Dictionary mapping template types to available formats
125 Example:
126 >>> loader.list_available_templates()
127 {'config': ['yaml', 'json'], 'version': ['txt'], 'build': ['py']}
128 """
129 templates: dict[str, list[str]] = {}
131 for template_dir in self._templates_dir.iterdir():
132 if template_dir.is_dir():
133 template_type = template_dir.name
134 formats = []
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)
141 templates[template_type] = formats
143 return templates
145 # ////////////////////////////////////////////////
146 # TEMPLATE PROCESSING METHODS
147 # ////////////////////////////////////////////////
149 def process_config_template(self, format_type: str, config: dict[str, Any]) -> str:
150 """
151 Process a configuration template.
153 Args:
154 format_type: Format of the template (yaml, json)
155 config: Configuration values dictionary
157 Returns:
158 str: Processed template string
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)
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.
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
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 )
193 def process_setup_template(self, format_type: str, config: dict[str, Any]) -> str:
194 """
195 Process a setup template.
197 Args:
198 format_type: Format of the template (py)
199 config: Configuration values dictionary
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)
209 # ////////////////////////////////////////////////
210 # FILE GENERATION METHODS
211 # ////////////////////////////////////////////////
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.
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
225 Raises:
226 ValueError: If template_type is unknown
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}")
248 # Ensure output directory exists
249 output_path.parent.mkdir(parents=True, exist_ok=True)
251 # Write the processed template
252 with open(output_path, "w", encoding="utf-8") as f:
253 f.write(content)
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.
261 Creates a valid file that can be used as a starting point and
262 edited manually with real values.
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
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)
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}")
297 # Ensure output directory exists
298 output_path.parent.mkdir(parents=True, exist_ok=True)
300 # Write the processed template with mockup values
301 with open(output_path, "w", encoding="utf-8") as f:
302 f.write(content)
304 except Exception as e:
305 raise Exception(f"Failed to generate template with mockup: {e}") from e
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.
313 Creates a template file with placeholders that can be processed
314 later with real values.
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
321 Raises:
322 Exception: If generation fails
323 """
324 try:
325 # Load the template
326 template = self.load_template(template_type, format_type)
328 # Ensure output directory exists
329 output_path.parent.mkdir(parents=True, exist_ok=True)
331 # Write the raw template with placeholders
332 with open(output_path, "w", encoding="utf-8") as f:
333 f.write(template)
335 except Exception as e:
336 raise Exception(f"Failed to generate raw template: {e}") from e
338 # ////////////////////////////////////////////////
339 # VALIDATION METHODS
340 # ////////////////////////////////////////////////
342 def validate_template(self, template_type: str, format_type: str) -> bool:
343 """
344 Validate a template file.
346 Args:
347 template_type: Type of template
348 format_type: Format of the template
350 Returns:
351 bool: True if template is valid, False otherwise
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