|
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 |
|
|
|
|
|
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 = [] |
|
|
|
|
|
content_str = json.dumps(self.content) |
|
|
|
|
|
pattern = r'\{\{([^\}]+)\}\}' |
|
matches = re.findall(pattern, content_str) |
|
|
|
|
|
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) |
|
|
|
|
|
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): |
|
|
|
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): |
|
|
|
result = {} |
|
for key, value in content.items(): |
|
|
|
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): |
|
|
|
result = [] |
|
for item in content: |
|
result.append(self._replace_variables(item, variables)) |
|
return result |
|
|
|
else: |
|
|
|
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""" |
|
|
|
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 |
|
|
|
|
|
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}") |
|
|
|
|
|
for t in self.get_templates_by_type(template.template_type): |
|
if t.is_default: |
|
t.set_as_default(False) |
|
self.update_template(t) |
|
|
|
|
|
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) |
|
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}") |
|
|
|
|
|
if not new_name: |
|
new_name = f"Copy of {template.name}" |
|
|
|
|
|
new_template = Template( |
|
new_name, |
|
template.template_type, |
|
template.content.copy(), |
|
template.description |
|
) |
|
|
|
|
|
for tag in template.tags: |
|
new_template.add_tag(tag) |
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
|
templates = [] |
|
for template_id in template_ids: |
|
template = self.get_template(template_id) |
|
if template: |
|
templates.append(template) |
|
else: |
|
|
|
templates = self.get_all_templates() |
|
|
|
return [template.to_dict() for template in templates] |
|
|
|
|
|
|
|
template_manager = TemplateManager() |