mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55:46 +01:00
- Implement a compiled template format using gob encoding - Add methods to compile templates and load from compiled templates - Create dedicated CompiledLoader for managing compiled templates - Enable auto-reload support for compiled templates - Add comprehensive tests including benchmarks - Create example application for template compilation workflow - Update documentation with compilation features and examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
241 lines
5.9 KiB
Go
241 lines
5.9 KiB
Go
package twig
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Loader defines the interface for template loading
|
|
type Loader interface {
|
|
// Load loads a template by name, returning its source code
|
|
Load(name string) (string, error)
|
|
|
|
// Exists checks if a template exists
|
|
Exists(name string) bool
|
|
}
|
|
|
|
// TimestampAwareLoader is an interface for loaders that can check modification times
|
|
type TimestampAwareLoader interface {
|
|
Loader
|
|
|
|
// GetModifiedTime returns the last modification time of a template
|
|
GetModifiedTime(name string) (int64, error)
|
|
}
|
|
|
|
// FileSystemLoader loads templates from the file system
|
|
type FileSystemLoader struct {
|
|
paths []string
|
|
suffix string
|
|
defaultPaths []string
|
|
// Stores paths for each loaded template to avoid repeatedly searching for the file
|
|
templatePaths map[string]string
|
|
}
|
|
|
|
// ArrayLoader loads templates from an in-memory array
|
|
type ArrayLoader struct {
|
|
templates map[string]string
|
|
}
|
|
|
|
// ChainLoader chains multiple loaders together
|
|
type ChainLoader struct {
|
|
loaders []Loader
|
|
}
|
|
|
|
// NewFileSystemLoader creates a new file system loader
|
|
func NewFileSystemLoader(paths []string) *FileSystemLoader {
|
|
// Add default path
|
|
defaultPaths := []string{"."}
|
|
|
|
// If no paths provided, use default
|
|
if len(paths) == 0 {
|
|
paths = defaultPaths
|
|
}
|
|
|
|
// Normalize paths
|
|
normalizedPaths := make([]string, len(paths))
|
|
for i, path := range paths {
|
|
normalizedPaths[i] = filepath.Clean(path)
|
|
}
|
|
|
|
return &FileSystemLoader{
|
|
paths: normalizedPaths,
|
|
suffix: ".twig",
|
|
defaultPaths: defaultPaths,
|
|
templatePaths: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// Load loads a template from the file system
|
|
func (l *FileSystemLoader) Load(name string) (string, error) {
|
|
// Check if we already know the location of this template
|
|
if filePath, ok := l.templatePaths[name]; ok {
|
|
// Check if file still exists at this path
|
|
if _, err := os.Stat(filePath); err == nil {
|
|
// Read file content
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error reading template %s: %w", name, err)
|
|
}
|
|
|
|
return string(content), nil
|
|
}
|
|
// If file doesn't exist anymore, remove from cache and search again
|
|
delete(l.templatePaths, name)
|
|
}
|
|
|
|
// Check each path for the template
|
|
for _, path := range l.paths {
|
|
filePath := filepath.Join(path, name)
|
|
|
|
// Add suffix if not already present
|
|
if !hasSuffix(filePath, l.suffix) {
|
|
filePath = filePath + l.suffix
|
|
}
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(filePath); err == nil {
|
|
// Save the path for future lookups
|
|
l.templatePaths[name] = filePath
|
|
|
|
// Read file content
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error reading template %s: %w", name, err)
|
|
}
|
|
|
|
return string(content), nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("%w: %s", ErrTemplateNotFound, name)
|
|
}
|
|
|
|
// Exists checks if a template exists in the file system
|
|
func (l *FileSystemLoader) Exists(name string) bool {
|
|
// Check each path for the template
|
|
for _, path := range l.paths {
|
|
filePath := filepath.Join(path, name)
|
|
|
|
// Add suffix if not already present
|
|
if !hasSuffix(filePath, l.suffix) {
|
|
filePath = filePath + l.suffix
|
|
}
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(filePath); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// SetSuffix sets the file suffix for templates
|
|
func (l *FileSystemLoader) SetSuffix(suffix string) {
|
|
l.suffix = suffix
|
|
}
|
|
|
|
// GetModifiedTime returns the last modification time of a template file
|
|
func (l *FileSystemLoader) GetModifiedTime(name string) (int64, error) {
|
|
// If we already know where this template is, check that path directly
|
|
if filePath, ok := l.templatePaths[name]; ok {
|
|
info, err := os.Stat(filePath)
|
|
if err != nil {
|
|
// If file doesn't exist anymore, remove from cache
|
|
if os.IsNotExist(err) {
|
|
delete(l.templatePaths, name)
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
return info.ModTime().Unix(), nil
|
|
}
|
|
|
|
// Otherwise search for the template
|
|
for _, path := range l.paths {
|
|
filePath := filepath.Join(path, name)
|
|
|
|
// Add suffix if not already present
|
|
if !hasSuffix(filePath, l.suffix) {
|
|
filePath = filePath + l.suffix
|
|
}
|
|
|
|
// Check if file exists
|
|
info, err := os.Stat(filePath)
|
|
if err == nil {
|
|
// Save the path for future lookups
|
|
l.templatePaths[name] = filePath
|
|
|
|
return info.ModTime().Unix(), nil
|
|
}
|
|
}
|
|
|
|
return 0, fmt.Errorf("%w: %s", ErrTemplateNotFound, name)
|
|
}
|
|
|
|
// NewArrayLoader creates a new array loader
|
|
func NewArrayLoader(templates map[string]string) *ArrayLoader {
|
|
return &ArrayLoader{
|
|
templates: templates,
|
|
}
|
|
}
|
|
|
|
// Load loads a template from the array
|
|
func (l *ArrayLoader) Load(name string) (string, error) {
|
|
if template, ok := l.templates[name]; ok {
|
|
return template, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("%w: %s", ErrTemplateNotFound, name)
|
|
}
|
|
|
|
// Exists checks if a template exists in the array
|
|
func (l *ArrayLoader) Exists(name string) bool {
|
|
_, ok := l.templates[name]
|
|
return ok
|
|
}
|
|
|
|
// SetTemplate adds or updates a template in the array
|
|
func (l *ArrayLoader) SetTemplate(name, template string) {
|
|
l.templates[name] = template
|
|
}
|
|
|
|
// NewChainLoader creates a new chain loader
|
|
func NewChainLoader(loaders []Loader) *ChainLoader {
|
|
return &ChainLoader{
|
|
loaders: loaders,
|
|
}
|
|
}
|
|
|
|
// Load loads a template from the first loader that has it
|
|
func (l *ChainLoader) Load(name string) (string, error) {
|
|
for _, loader := range l.loaders {
|
|
if loader.Exists(name) {
|
|
return loader.Load(name)
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("%w: %s", ErrTemplateNotFound, name)
|
|
}
|
|
|
|
// Exists checks if a template exists in any of the loaders
|
|
func (l *ChainLoader) Exists(name string) bool {
|
|
for _, loader := range l.loaders {
|
|
if loader.Exists(name) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// AddLoader adds a loader to the chain
|
|
func (l *ChainLoader) AddLoader(loader Loader) {
|
|
l.loaders = append(l.loaders, loader)
|
|
}
|
|
|
|
// Helper function to check if a string has a suffix
|
|
func hasSuffix(s, suffix string) bool {
|
|
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
|
|
}
|