template.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. """
  2. Core template utilities for processing and rendering boilerplate templates.
  3. Provides shared functionality for template cleaning, validation, and rendering
  4. across different module types (compose, ansible, etc.).
  5. """
  6. import re
  7. import logging
  8. from pathlib import Path
  9. from typing import Optional, Tuple
  10. try:
  11. import jinja2
  12. except ImportError:
  13. jinja2 = None
  14. logger = logging.getLogger(__name__)
  15. def clean_template_content(content: str) -> str:
  16. """
  17. Remove template metadata blocks and prepare content for Jinja2 rendering.
  18. Args:
  19. content: Raw template content
  20. Returns:
  21. Cleaned template content with metadata blocks removed
  22. """
  23. # Remove variables block as it's not valid Jinja2 syntax
  24. return re.sub(r"\{%\s*variables\s*%\}(.+?)\{%\s*endvariables\s*%\}\n?", "", content, flags=re.S)
  25. def validate_template(content: str, template_path: Optional[Path] = None) -> Tuple[bool, Optional[str]]:
  26. """
  27. Validate Jinja2 template syntax before rendering.
  28. Args:
  29. content: Template content to validate
  30. template_path: Optional path to template file for error messages
  31. Returns:
  32. Tuple of (is_valid, error_message)
  33. """
  34. if not jinja2:
  35. return False, "Jinja2 is required to render templates. Install it and retry."
  36. try:
  37. env = jinja2.Environment(loader=jinja2.BaseLoader())
  38. env.parse(content)
  39. return True, None
  40. except jinja2.exceptions.TemplateSyntaxError as e:
  41. path_info = f" in '{template_path}'" if template_path else ""
  42. return False, f"Template syntax error{path_info}: {e.message} (line {e.lineno})"
  43. except Exception as e:
  44. path_info = f" '{template_path}'" if template_path else ""
  45. return False, f"Failed to parse template{path_info}: {e}"
  46. def render_template(content: str, values: dict) -> Tuple[bool, str, Optional[str]]:
  47. """
  48. Render a template with the provided values.
  49. Args:
  50. content: Template content to render
  51. values: Dictionary of values to use in rendering
  52. Returns:
  53. Tuple of (success, rendered_content or empty string, error_message or None)
  54. """
  55. if not jinja2:
  56. return False, "", "Jinja2 is required to render templates. Install it and retry."
  57. try:
  58. # Enable whitespace control for cleaner output
  59. env = jinja2.Environment(
  60. loader=jinja2.BaseLoader(),
  61. trim_blocks=True,
  62. lstrip_blocks=True
  63. )
  64. template = env.from_string(content)
  65. rendered = template.render(**values)
  66. return True, rendered, None
  67. except jinja2.exceptions.TemplateError as e:
  68. return False, "", f"Template rendering error: {e}"
  69. except Exception as e:
  70. return False, "", f"Unexpected error while rendering: {e}"