""" Template service for applying HTML/CSS templates to generated content """ import json import random from pathlib import Path from typing import Optional, Dict from src.core.config import get_config from src.database.repositories import GeneratedContentRepository class TemplateService: """Service for loading, selecting, and applying HTML templates""" def __init__(self, content_repo: Optional[GeneratedContentRepository] = None): self.templates_dir = Path(__file__).parent / "templates" self._template_cache: Dict[str, str] = {} self.content_repo = content_repo def get_available_templates(self) -> list[str]: """ Get list of available template names Returns: List of template names without .html extension """ templates = [] for template_file in self.templates_dir.glob("*.html"): templates.append(template_file.stem) return sorted(templates) def load_template(self, template_name: str) -> str: """ Load a template file by name Args: template_name: Name of template (without .html extension) Returns: Template content as string Raises: FileNotFoundError: If template doesn't exist """ if template_name in self._template_cache: return self._template_cache[template_name] template_path = self.templates_dir / f"{template_name}.html" if not template_path.exists(): available = self.get_available_templates() raise FileNotFoundError( f"Template '{template_name}' not found. Available templates: {', '.join(available)}" ) with open(template_path, 'r', encoding='utf-8') as f: template_content = f.read() self._template_cache[template_name] = template_content return template_content def select_template_for_content( self, site_deployment_id: Optional[int] = None, site_deployment_repo=None ) -> str: """ Select appropriate template based on site deployment Args: site_deployment_id: Optional site deployment ID site_deployment_repo: Optional repository for querying site deployments Returns: Template name to use Logic: 1. If site_deployment_id exists: - Query SiteDeployment and return site.template_name - If template not set, default to 'basic' 2. If site_deployment_id is null: randomly select (don't persist) """ if site_deployment_id and site_deployment_repo: site_deployment = site_deployment_repo.get_by_id(site_deployment_id) if site_deployment: return site_deployment.template_name or "basic" return self._select_random_template() def _select_random_template(self) -> str: """ Randomly select a template from available templates Returns: Template name """ available = self.get_available_templates() if not available: return "basic" return random.choice(available) def format_content( self, content: str, title: str, meta_description: str, template_name: str ) -> str: """ Format content using specified template Args: content: Raw HTML content (h2, h3, p tags) title: Article title meta_description: Meta description for SEO template_name: Name of template to use Returns: Complete formatted HTML document Raises: FileNotFoundError: If template doesn't exist """ config = get_config() try: template = self.load_template(template_name) except FileNotFoundError: fallback_template = config.templates.default print(f"Warning: Template '{template_name}' not found, using '{fallback_template}'") template = self.load_template(fallback_template) formatted_html = template.replace("{{ title }}", self._escape_html(title)) formatted_html = formatted_html.replace("{{ meta_description }}", self._escape_html(meta_description)) formatted_html = formatted_html.replace("{{ content }}", content) return formatted_html def _escape_html(self, text: str) -> str: """ Escape HTML special characters in text Args: text: Text to escape Returns: Escaped text """ replacements = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } for char, escaped in replacements.items(): text = text.replace(char, escaped) return text