Initial upload: man-card CLI tool with PDF/PNG generation, templates, and tests
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
193
man_card/templates.py
Normal file
193
man_card/templates.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user