2025-10-20 08:54:21 +01:00
|
|
|
package templates
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"html/template"
|
|
|
|
|
"log"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"github.com/juanatsap/cv-site/internal/config"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Manager handles template parsing and rendering
|
|
|
|
|
type Manager struct {
|
|
|
|
|
templates *template.Template
|
|
|
|
|
config *config.TemplateConfig
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewManager creates a new template manager
|
|
|
|
|
func NewManager(cfg *config.TemplateConfig) (*Manager, error) {
|
|
|
|
|
m := &Manager{
|
|
|
|
|
config: cfg,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := m.loadTemplates(); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to load templates: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// loadTemplates parses all templates from the configured directory
|
|
|
|
|
func (m *Manager) loadTemplates() error {
|
|
|
|
|
m.mu.Lock()
|
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
// Create template with custom functions
|
|
|
|
|
funcMap := template.FuncMap{
|
|
|
|
|
"iterate": func(count int) []int {
|
|
|
|
|
var result []int
|
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
|
result = append(result, i)
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
},
|
|
|
|
|
"eq": func(a, b string) bool {
|
|
|
|
|
return a == b
|
|
|
|
|
},
|
2025-11-11 13:53:14 +00:00
|
|
|
// Security: safeHTML function removed to prevent XSS attacks
|
|
|
|
|
// Go's html/template package automatically escapes HTML by default
|
|
|
|
|
// If you need to render HTML, sanitize it first with a proper library
|
2025-10-20 08:54:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse main templates
|
|
|
|
|
pattern := filepath.Join(m.config.Dir, "*.html")
|
|
|
|
|
tmpl, err := template.New("").Funcs(funcMap).ParseGlob(pattern)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error parsing templates from %s: %w", pattern, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to parse partials if they exist
|
|
|
|
|
partialsPattern := filepath.Join(m.config.PartialsDir, "*.html")
|
|
|
|
|
partials, _ := filepath.Glob(partialsPattern)
|
|
|
|
|
if len(partials) > 0 {
|
|
|
|
|
tmpl, err = tmpl.ParseGlob(partialsPattern)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Warning: error parsing partials: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m.templates = tmpl
|
|
|
|
|
log.Printf("✓ Templates loaded successfully from %s", m.config.Dir)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reload reloads all templates (useful for hot-reload in development)
|
|
|
|
|
func (m *Manager) Reload() error {
|
|
|
|
|
return m.loadTemplates()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render executes a template with the given data
|
|
|
|
|
func (m *Manager) Render(name string) (*template.Template, error) {
|
|
|
|
|
// Hot reload in development mode
|
|
|
|
|
if m.config.HotReload {
|
|
|
|
|
if err := m.Reload(); err != nil {
|
|
|
|
|
log.Printf("Warning: template reload failed: %v", err)
|
|
|
|
|
// Continue with cached templates
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m.mu.RLock()
|
|
|
|
|
defer m.mu.RUnlock()
|
|
|
|
|
|
|
|
|
|
tmpl := m.templates.Lookup(name)
|
|
|
|
|
if tmpl == nil {
|
|
|
|
|
return nil, fmt.Errorf("template %q not found", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tmpl, nil
|
|
|
|
|
}
|