Big-Link-Man/src/templating/service.py

164 lines
5.1 KiB
Python

"""
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 = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}
for char, escaped in replacements.items():
text = text.replace(char, escaped)
return text