"""Template system for card generation.""" import json import os from pathlib import Path from typing import Any, Optional from dataclasses import dataclass, field @dataclass class Template: """Card template configuration.""" name: str layout: dict[str, Any] = field(default_factory=dict) colors: dict[str, str] = field(default_factory=dict) fonts: dict[str, str] = field(default_factory=dict) spacing: dict[str, int] = field(default_factory=dict) class TemplateLoader: """Loader and validator for card templates.""" DEFAULT_TEMPLATES = { "default": { "layout": { "header_height": 60, "section_spacing": 20, "option_spacing": 10, "margin": 30, "columns": 2 }, "colors": { "header_bg": "#2E3440", "header_text": "#FFFFFF", "section_title": "#88C0D0", "option_flag": "#A3BE8C", "option_desc": "#ECEFF4", "background": "#FFFFFF", "border": "#4C566A" }, "fonts": { "header": ("Helvetica", 24, "B"), "section": ("Helvetica", 14, "B"), "option_flag": ("Courier", 10, "B"), "option_desc": ("Helvetica", 9, ""), "synopsis": ("Courier", 11, ""), "body": ("Helvetica", 10, "") }, "spacing": { "line_height": 14, "margin": 20, "section_padding": 15 } }, "minimal": { "layout": { "header_height": 40, "section_spacing": 15, "option_spacing": 8, "margin": 20, "columns": 2 }, "colors": { "header_bg": "#FFFFFF", "header_text": "#000000", "section_title": "#666666", "option_flag": "#000000", "option_desc": "#333333", "background": "#FFFFFF", "border": "#CCCCCC" }, "fonts": { "header": ("Helvetica", 18, ""), "section": ("Helvetica", 11, "B"), "option_flag": ("Courier", 9, ""), "option_desc": ("Helvetica", 8, ""), "synopsis": ("Courier", 9, ""), "body": ("Helvetica", 8, "") }, "spacing": { "line_height": 12, "margin": 15, "section_padding": 10 } }, "compact": { "layout": { "header_height": 35, "section_spacing": 10, "option_spacing": 5, "margin": 15, "columns": 3 }, "colors": { "header_bg": "#1A1A2E", "header_text": "#FFFFFF", "section_title": "#00D9FF", "option_flag": "#00FF88", "option_desc": "#AAAAAA", "background": "#16213E", "border": "#0F3460" }, "fonts": { "header": ("Helvetica", 14, "B"), "section": ("Helvetica", 10, "B"), "option_flag": ("Courier", 7, "B"), "option_desc": ("Helvetica", 7, ""), "synopsis": ("Courier", 8, ""), "body": ("Helvetica", 7, "") }, "spacing": { "line_height": 10, "margin": 10, "section_padding": 8 } }, "dark": { "layout": { "header_height": 50, "section_spacing": 18, "option_spacing": 12, "margin": 25, "columns": 2 }, "colors": { "header_bg": "#1E1E1E", "header_text": "#61AFEF", "section_title": "#C678DD", "option_flag": "#98C379", "option_desc": "#ABB2BF", "background": "#282C34", "border": "#3E4451" }, "fonts": { "header": ("Helvetica", 20, "B"), "section": ("Helvetica", 12, "B"), "option_flag": ("Courier", 9, "B"), "option_desc": ("Helvetica", 8, ""), "synopsis": ("Courier", 10, ""), "body": ("Helvetica", 9, "") }, "spacing": { "line_height": 13, "margin": 18, "section_padding": 12 } } } def __init__(self, template_dir: Optional[str] = None): self.template_dir = Path(template_dir) if template_dir else Path(__file__).parent.parent / "templates" def list_templates(self) -> list[str]: """List available template names.""" templates = list(self.DEFAULT_TEMPLATES.keys()) if self.template_dir.exists(): for f in self.template_dir.glob("*.json"): if f.stem not in templates: templates.append(f.stem) return sorted(templates) def load(self, name: str) -> Template: """Load a template by name.""" if name in self.DEFAULT_TEMPLATES: data = self.DEFAULT_TEMPLATES[name] return Template(name=name, **data) template_path = self.template_dir / f"{name}.json" if template_path.exists(): with open(template_path, 'r') as f: data = json.load(f) if "fonts" in data: for key in data["fonts"]: if isinstance(data["fonts"][key], list): data["fonts"][key] = tuple(data["fonts"][key]) return Template(name=name, **data) raise ValueError(f"Template '{name}' not found. Available: {', '.join(self.list_templates())}") def load_custom(self, path: str) -> Template: """Load a custom template from file path.""" with open(path, 'r') as f: data = json.load(f) return Template(name=Path(path).stem, **data) def get_template_path(self, name: str) -> Optional[Path]: """Get the file path for a template if it exists.""" if name in self.DEFAULT_TEMPLATES: return None template_path = self.template_dir / f"{name}.json" if template_path.exists(): return template_path return None