import json import uuid from typing import Dict, List, Any, Optional, Callable, Union from datetime import datetime import re from utils.logging import setup_logger from utils.error_handling import handle_exceptions, AutomationError from utils.storage import load_data, save_data # Initialize logger logger = setup_logger(__name__) class Template: """Template for auto-applying patterns""" def __init__(self, name: str, template_type: str, content: Dict[str, Any], description: Optional[str] = None): """Initialize a template Args: name: Template name template_type: Type of template (task, note, goal, project, etc.) content: Template content description: Template description (optional) """ self.id = str(uuid.uuid4()) self.name = name self.description = description or "" self.template_type = template_type self.content = content self.created_at = datetime.now().isoformat() self.updated_at = self.created_at self.usage_count = 0 self.last_used = None self.is_default = False self.tags = [] self.variables = self._extract_variables() def _extract_variables(self) -> List[str]: """Extract variables from template content Returns: List of variable names """ variables = [] # Convert content to string for searching content_str = json.dumps(self.content) # Find all {{variable}} patterns pattern = r'\{\{([^\}]+)\}\}' matches = re.findall(pattern, content_str) # Add unique variables for match in matches: var_name = match.strip() if var_name and var_name not in variables: variables.append(var_name) return variables @handle_exceptions def apply(self, variables: Dict[str, Any] = None) -> Dict[str, Any]: """Apply template with variables Args: variables: Dictionary of variable values (optional) Returns: Applied template content """ variables = variables or {} result = self._replace_variables(self.content, variables) # Update usage stats self.usage_count += 1 self.last_used = datetime.now().isoformat() self.updated_at = self.last_used return result def _replace_variables(self, content: Any, variables: Dict[str, Any]) -> Any: """Replace variables in content recursively Args: content: Content to process variables: Dictionary of variable values Returns: Processed content """ if isinstance(content, str): # Replace variables in string result = content for var_name, var_value in variables.items(): pattern = r"\{\{\s*" + var_name + r"\s*\}\}" result = re.sub(pattern, str(var_value), result) return result elif isinstance(content, dict): # Process dictionary recursively result = {} for key, value in content.items(): # Process both key and value new_key = self._replace_variables(key, variables) new_value = self._replace_variables(value, variables) result[new_key] = new_value return result elif isinstance(content, list): # Process list recursively result = [] for item in content: result.append(self._replace_variables(item, variables)) return result else: # Return other types unchanged return content @handle_exceptions def set_as_default(self, is_default: bool = True) -> None: """Set template as default Args: is_default: Whether template is default """ self.is_default = is_default self.updated_at = datetime.now().isoformat() @handle_exceptions def add_tag(self, tag: str) -> None: """Add tag to template Args: tag: Tag to add """ if tag not in self.tags: self.tags.append(tag) self.updated_at = datetime.now().isoformat() @handle_exceptions def remove_tag(self, tag: str) -> None: """Remove tag from template Args: tag: Tag to remove """ if tag in self.tags: self.tags.remove(tag) self.updated_at = datetime.now().isoformat() @handle_exceptions def to_dict(self) -> Dict[str, Any]: """Convert template to dictionary Returns: Template as dictionary """ return { "id": self.id, "name": self.name, "description": self.description, "template_type": self.template_type, "content": self.content, "created_at": self.created_at, "updated_at": self.updated_at, "usage_count": self.usage_count, "last_used": self.last_used, "is_default": self.is_default, "tags": self.tags, "variables": self.variables } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'Template': """Create template from dictionary Args: data: Template data Returns: Template instance """ template = cls( data["name"], data["template_type"], data["content"], data.get("description", "") ) template.id = data["id"] template.created_at = data["created_at"] template.updated_at = data["updated_at"] template.usage_count = data.get("usage_count", 0) template.last_used = data.get("last_used") template.is_default = data.get("is_default", False) template.tags = data.get("tags", []) template.variables = data.get("variables", []) return template class TemplateManager: """Manager for templates""" def __init__(self): """Initialize template manager""" self.templates = {} self.load_templates() self._ensure_default_templates() @handle_exceptions def load_templates(self) -> None: """Load templates from storage""" try: templates_data = load_data("templates", default=[]) for template_data in templates_data: template = Template.from_dict(template_data) self.templates[template.id] = template logger.info(f"Loaded {len(self.templates)} templates") except Exception as e: logger.error(f"Failed to load templates: {str(e)}") @handle_exceptions def save_templates(self) -> None: """Save templates to storage""" try: templates_data = [template.to_dict() for template in self.templates.values()] save_data("templates", templates_data) logger.info(f"Saved {len(self.templates)} templates") except Exception as e: logger.error(f"Failed to save templates: {str(e)}") def _ensure_default_templates(self) -> None: """Ensure default templates exist""" # Check if we need to create default templates if not self.get_templates_by_type("task"): self._create_default_task_template() if not self.get_templates_by_type("note"): self._create_default_note_template() if not self.get_templates_by_type("goal"): self._create_default_goal_template() if not self.get_templates_by_type("project"): self._create_default_project_template() def _create_default_task_template(self) -> None: """Create default task template""" content = { "title": "{{title}}", "description": "{{description}}", "status": "pending", "priority": "{{priority|normal}}", "due_date": "{{due_date}}", "tags": ["{{tag}}"], "category": "{{category|general}}" } template = Template("Default Task", "task", content, "Default template for tasks") template.set_as_default(True) template.add_tag("default") self.templates[template.id] = template self.save_templates() def _create_default_note_template(self) -> None: """Create default note template""" content = { "title": "{{title}}", "content": "{{content}}", "tags": ["{{tag}}"], "category": "{{category|general}}" } template = Template("Default Note", "note", content, "Default template for notes") template.set_as_default(True) template.add_tag("default") self.templates[template.id] = template self.save_templates() def _create_default_goal_template(self) -> None: """Create default goal template""" content = { "title": "{{title}}", "description": "{{description}}", "target_date": "{{target_date}}", "status": "in_progress", "progress": 0, "tags": ["{{tag}}"], "category": "{{category|personal}}" } template = Template("Default Goal", "goal", content, "Default template for goals") template.set_as_default(True) template.add_tag("default") self.templates[template.id] = template self.save_templates() def _create_default_project_template(self) -> None: """Create default project template""" content = { "title": "{{title}}", "description": "{{description}}", "start_date": "{{start_date}}", "end_date": "{{end_date}}", "status": "planning", "progress": 0, "tags": ["{{tag}}"], "category": "{{category|work}}", "tasks": [] } template = Template("Default Project", "project", content, "Default template for projects") template.set_as_default(True) template.add_tag("default") self.templates[template.id] = template self.save_templates() @handle_exceptions def create_template(self, name: str, template_type: str, content: Dict[str, Any], description: Optional[str] = None) -> Template: """Create a new template Args: name: Template name template_type: Type of template content: Template content description: Template description (optional) Returns: Created template """ template = Template(name, template_type, content, description) self.templates[template.id] = template self.save_templates() return template @handle_exceptions def get_template(self, template_id: str) -> Optional[Template]: """Get template by ID Args: template_id: Template ID Returns: Template if found, None otherwise """ return self.templates.get(template_id) @handle_exceptions def update_template(self, template: Template) -> None: """Update template Args: template: Template to update """ if template.id in self.templates: template.updated_at = datetime.now().isoformat() self.templates[template.id] = template self.save_templates() else: raise AutomationError(f"Template not found: {template.id}") @handle_exceptions def delete_template(self, template_id: str) -> None: """Delete template Args: template_id: Template ID """ if template_id in self.templates: del self.templates[template_id] self.save_templates() else: raise AutomationError(f"Template not found: {template_id}") @handle_exceptions def get_all_templates(self) -> List[Template]: """Get all templates Returns: List of all templates """ return list(self.templates.values()) @handle_exceptions def get_templates_by_type(self, template_type: str) -> List[Template]: """Get templates by type Args: template_type: Template type Returns: List of templates of the specified type """ return [ template for template in self.templates.values() if template.template_type == template_type ] @handle_exceptions def get_templates_by_tag(self, tag: str) -> List[Template]: """Get templates by tag Args: tag: Tag to filter by Returns: List of templates with the specified tag """ return [ template for template in self.templates.values() if tag in template.tags ] @handle_exceptions def get_default_template(self, template_type: str) -> Optional[Template]: """Get default template for a type Args: template_type: Template type Returns: Default template if found, None otherwise """ templates = self.get_templates_by_type(template_type) for template in templates: if template.is_default: return template # If no default template found, return the first one return templates[0] if templates else None @handle_exceptions def set_default_template(self, template_id: str) -> None: """Set a template as default for its type Args: template_id: Template ID """ template = self.get_template(template_id) if not template: raise AutomationError(f"Template not found: {template_id}") # Clear default flag for all templates of this type for t in self.get_templates_by_type(template.template_type): if t.is_default: t.set_as_default(False) self.update_template(t) # Set this template as default template.set_as_default(True) self.update_template(template) @handle_exceptions def apply_template(self, template_id: str, variables: Dict[str, Any] = None) -> Dict[str, Any]: """Apply a template with variables Args: template_id: Template ID variables: Dictionary of variable values (optional) Returns: Applied template content """ template = self.get_template(template_id) if not template: raise AutomationError(f"Template not found: {template_id}") result = template.apply(variables) self.update_template(template) # Update usage stats return result @handle_exceptions def apply_default_template(self, template_type: str, variables: Dict[str, Any] = None) -> Dict[str, Any]: """Apply the default template for a type Args: template_type: Template type variables: Dictionary of variable values (optional) Returns: Applied template content """ template = self.get_default_template(template_type) if not template: raise AutomationError(f"No template found for type: {template_type}") return self.apply_template(template.id, variables) @handle_exceptions def duplicate_template(self, template_id: str, new_name: Optional[str] = None) -> Template: """Duplicate a template Args: template_id: Template ID new_name: New template name (optional) Returns: Duplicated template """ template = self.get_template(template_id) if not template: raise AutomationError(f"Template not found: {template_id}") # Create new name if not provided if not new_name: new_name = f"Copy of {template.name}" # Create new template new_template = Template( new_name, template.template_type, template.content.copy(), template.description ) # Copy tags for tag in template.tags: new_template.add_tag(tag) # Add to templates self.templates[new_template.id] = new_template self.save_templates() return new_template @handle_exceptions def import_templates(self, templates_data: List[Dict[str, Any]], overwrite_existing: bool = False) -> int: """Import templates from data Args: templates_data: List of template data overwrite_existing: Whether to overwrite existing templates Returns: Number of templates imported """ imported_count = 0 for template_data in templates_data: try: template = Template.from_dict(template_data) # Check if template with same ID exists if template.id in self.templates: if overwrite_existing: self.templates[template.id] = template imported_count += 1 else: self.templates[template.id] = template imported_count += 1 except Exception as e: logger.error(f"Failed to import template: {str(e)}") if imported_count > 0: self.save_templates() return imported_count @handle_exceptions def export_templates(self, template_ids: Optional[List[str]] = None) -> List[Dict[str, Any]]: """Export templates to data Args: template_ids: List of template IDs to export (optional, exports all if None) Returns: List of template data """ if template_ids: # Export specific templates templates = [] for template_id in template_ids: template = self.get_template(template_id) if template: templates.append(template) else: # Export all templates templates = self.get_all_templates() return [template.to_dict() for template in templates] # Create a global instance of the template manager template_manager = TemplateManager()