"""Metadata template manager with variable substitution.""" import json from pathlib import Path from typing import Dict, List, Optional from datetime import datetime from .utils import get_logger logger = get_logger(__name__) class TemplateManager: """Manage metadata templates with variable substitution.""" # Available variables for substitution AVAILABLE_VARIABLES = { '{filename}': 'Original filename without extension', '{date}': 'Current date (YYYY-MM-DD)', '{datetime}': 'Current date and time', '{user}': 'Current username', '{year}': 'Current year', '{month}': 'Current month', '{day}': 'Current day' } def __init__(self, templates_path: Optional[str] = None): """ Initialize template manager. Args: templates_path: Path to JSON file for storing templates """ self.templates_path = templates_path or 'metadata_templates.json' def create_template( self, name: str, title_template: str, subject_template: str, keywords_template: str, description: str = '' ) -> Dict: """ Create a new metadata template. Args: name: Template name title_template: Title template with variables (e.g., "{filename} - Product Guide") subject_template: Subject template with variables keywords_template: Keywords template with variables description: Optional description of template usage Returns: Template dictionary """ template = { 'name': name, 'description': description, 'title': title_template, 'subject': subject_template, 'keywords': keywords_template, 'created_at': self._get_timestamp(), 'updated_at': self._get_timestamp() } # Validate template validation = self.validate_template(template) if validation['invalid']: logger.warning(f"Template '{name}' has invalid variables: {validation['invalid']}") return template def save_template(self, template: Dict) -> bool: """ Save template to storage. Args: template: Template dictionary Returns: True if successful """ try: templates = self._load_templates() template['updated_at'] = self._get_timestamp() templates[template['name']] = template with open(self.templates_path, 'w', encoding='utf-8') as f: json.dump(templates, f, indent=2, ensure_ascii=False) logger.info(f"Saved template: {template['name']}") return True except Exception as e: logger.error(f"Failed to save template '{template['name']}': {e}") return False def load_template(self, name: str) -> Optional[Dict]: """ Load template by name. Args: name: Template name Returns: Template dictionary or None if not found """ templates = self._load_templates() template = templates.get(name) if template: logger.info(f"Loaded template: {name}") else: logger.warning(f"Template not found: {name}") return template def list_templates(self) -> List[Dict]: """ List all available templates. Returns: List of template summaries """ templates = self._load_templates() return [ { 'name': name, 'description': data.get('description', ''), 'created_at': data.get('created_at', ''), 'updated_at': data.get('updated_at', ''), 'variables_used': self._extract_variables(data) } for name, data in templates.items() ] def delete_template(self, name: str) -> bool: """ Delete a template. Args: name: Template name Returns: True if deleted, False if not found """ templates = self._load_templates() if name in templates: del templates[name] try: with open(self.templates_path, 'w', encoding='utf-8') as f: json.dump(templates, f, indent=2, ensure_ascii=False) logger.info(f"Deleted template: {name}") return True except Exception as e: logger.error(f"Failed to delete template '{name}': {e}") return False logger.warning(f"Template not found: {name}") return False def apply_template( self, template: Dict, filename: str, user: str = 'Unknown', custom_vars: Optional[Dict[str, str]] = None ) -> Dict[str, str]: """ Apply template to generate metadata for a file. Args: template: Template dictionary filename: Filename to process user: Username for {user} variable custom_vars: Additional custom variables (e.g., {'product_line': 'Dental'}) Returns: Dictionary with title, subject, keywords """ # Build variable substitution map variables = self._build_variable_map(filename, user, custom_vars) # Apply substitutions metadata = { 'title': self._substitute_variables(template.get('title', ''), variables), 'subject': self._substitute_variables(template.get('subject', ''), variables), 'keywords': self._substitute_variables(template.get('keywords', ''), variables) } logger.info(f"Applied template '{template['name']}' to {filename}") return metadata def validate_template(self, template: Dict) -> Dict[str, List[str]]: """ Validate template for correct variable usage. Args: template: Template dictionary Returns: Dictionary with 'valid' and 'invalid' variable lists """ result = { 'valid': [], 'invalid': [] } # Extract all variables from template all_text = ( template.get('title', '') + template.get('subject', '') + template.get('keywords', '') ) # Find all {variable} patterns import re variables = re.findall(r'\{[^}]+\}', all_text) for var in variables: if var in self.AVAILABLE_VARIABLES: if var not in result['valid']: result['valid'].append(var) else: if var not in result['invalid']: result['invalid'].append(var) return result def _load_templates(self) -> Dict: """Load all templates from file.""" if Path(self.templates_path).exists(): try: with open(self.templates_path, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: logger.error(f"Failed to load templates: {e}") return {} return {} def _get_timestamp(self) -> str: """Get current timestamp as ISO format string.""" return datetime.now().isoformat() def _build_variable_map( self, filename: str, user: str, custom_vars: Optional[Dict[str, str]] ) -> Dict[str, str]: """ Build variable substitution map. Args: filename: Filename (with or without extension) user: Username custom_vars: Custom variables Returns: Dictionary mapping variable names to values """ # Get filename without extension filename_stem = Path(filename).stem # Current date/time now = datetime.now() variables = { '{filename}': filename_stem, '{date}': now.strftime('%Y-%m-%d'), '{datetime}': now.strftime('%Y-%m-%d %H:%M:%S'), '{user}': user, '{year}': str(now.year), '{month}': now.strftime('%m'), '{day}': now.strftime('%d') } # Add custom variables if custom_vars: for key, value in custom_vars.items(): # Ensure custom variables are wrapped in {} var_key = f'{{{key}}}' if not key.startswith('{') else key variables[var_key] = value return variables def _substitute_variables(self, template_text: str, variables: Dict[str, str]) -> str: """ Substitute variables in template text. Args: template_text: Text with {variable} placeholders variables: Variable substitution map Returns: Text with variables replaced """ result = template_text for var, value in variables.items(): result = result.replace(var, value) return result def _extract_variables(self, template: Dict) -> List[str]: """ Extract all variables used in a template. Args: template: Template dictionary Returns: List of variable names (e.g., ['{filename}', '{date}']) """ import re all_text = ( template.get('title', '') + template.get('subject', '') + template.get('keywords', '') ) variables = re.findall(r'\{[^}]+\}', all_text) return list(set(variables)) def get_available_variables(self) -> Dict[str, str]: """ Get list of available variables with descriptions. Returns: Dictionary mapping variable names to descriptions """ return self.AVAILABLE_VARIABLES.copy() def preview_template( self, template: Dict, sample_filename: str = 'example.pdf', user: str = 'User', custom_vars: Optional[Dict[str, str]] = None ) -> Dict[str, str]: """ Preview template output with sample data. Args: template: Template dictionary sample_filename: Sample filename for preview user: Sample username custom_vars: Sample custom variables Returns: Preview metadata """ return self.apply_template(template, sample_filename, user, custom_vars) def export_template(self, name: str, export_path: str) -> bool: """ Export single template to JSON file. Args: name: Template name export_path: Path to save template Returns: True if successful """ template = self.load_template(name) if not template: return False try: with open(export_path, 'w', encoding='utf-8') as f: json.dump(template, f, indent=2, ensure_ascii=False) logger.info(f"Exported template '{name}' to {export_path}") return True except Exception as e: logger.error(f"Failed to export template '{name}': {e}") return False def import_template(self, import_path: str) -> Optional[Dict]: """ Import template from JSON file. Args: import_path: Path to template JSON file Returns: Imported template dictionary or None """ try: with open(import_path, 'r', encoding='utf-8') as f: template = json.load(f) # Validate required fields required_fields = ['name', 'title', 'subject', 'keywords'] if not all(field in template for field in required_fields): logger.error(f"Invalid template file: missing required fields") return None logger.info(f"Imported template from {import_path}") return template except Exception as e: logger.error(f"Failed to import template: {e}") return None