Files
man-card/man_card/templates.py
2026-01-31 21:39:49 +00:00

194 lines
6.5 KiB
Python

"""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