A go template renderer based on Perl's Template Toolkit
at main 3.0 kB view raw
1package gott 2 3import ( 4 "crypto/sha256" 5 "encoding/gob" 6 "encoding/hex" 7 "os" 8 "path/filepath" 9) 10 11func init() { 12 // Register all AST types with gob for serialization. 13 // Gob requires concrete types to be registered when encoding/decoding interfaces. 14 15 // Root and text nodes 16 gob.Register(&Template{}) 17 gob.Register(&TextNode{}) 18 19 // Expression nodes 20 gob.Register(&IdentExpr{}) 21 gob.Register(&LiteralExpr{}) 22 gob.Register(&BinaryExpr{}) 23 gob.Register(&UnaryExpr{}) 24 gob.Register(&CallExpr{}) 25 gob.Register(&FilterExpr{}) 26 gob.Register(&DefaultExpr{}) 27 28 // Statement nodes 29 gob.Register(&OutputStmt{}) 30 gob.Register(&IfStmt{}) 31 gob.Register(&UnlessStmt{}) 32 gob.Register(&ForeachStmt{}) 33 gob.Register(&BlockStmt{}) 34 gob.Register(&IncludeStmt{}) 35 gob.Register(&WrapperStmt{}) 36 gob.Register(&SetStmt{}) 37 gob.Register(&TryStmt{}) 38 39 // Supporting types 40 gob.Register(&ElsIfClause{}) 41 gob.Register(PathPart{}) 42} 43 44// diskCache persists compiled templates to disk using gob encoding. 45type diskCache struct { 46 path string // directory for cache files 47} 48 49// newDiskCache creates a new disk cache at the specified path. 50// Creates the directory if it doesn't exist. 51func newDiskCache(path string) (*diskCache, error) { 52 if err := os.MkdirAll(path, 0755); err != nil { 53 return nil, err 54 } 55 return &diskCache{path: path}, nil 56} 57 58// hashKey creates a safe filename from a cache key using SHA256. 59func hashKey(key string) string { 60 h := sha256.Sum256([]byte(key)) 61 return hex.EncodeToString(h[:]) + ".gob" 62} 63 64// Get retrieves a cached template from disk. 65// Returns nil and no error if the file doesn't exist. 66func (d *diskCache) Get(key string) (*Template, error) { 67 filename := filepath.Join(d.path, hashKey(key)) 68 69 file, err := os.Open(filename) 70 if err != nil { 71 if os.IsNotExist(err) { 72 return nil, nil 73 } 74 return nil, err 75 } 76 defer file.Close() 77 78 var tmpl Template 79 decoder := gob.NewDecoder(file) 80 if err := decoder.Decode(&tmpl); err != nil { 81 // Corrupted cache file - remove it 82 os.Remove(filename) 83 return nil, nil 84 } 85 86 return &tmpl, nil 87} 88 89// Put stores a template to disk. 90func (d *diskCache) Put(key string, tmpl *Template) error { 91 filename := filepath.Join(d.path, hashKey(key)) 92 93 file, err := os.Create(filename) 94 if err != nil { 95 return err 96 } 97 defer file.Close() 98 99 encoder := gob.NewEncoder(file) 100 return encoder.Encode(tmpl) 101} 102 103// Clear removes all cached templates from disk. 104func (d *diskCache) Clear() error { 105 entries, err := os.ReadDir(d.path) 106 if err != nil { 107 return err 108 } 109 110 for _, entry := range entries { 111 if entry.IsDir() { 112 continue 113 } 114 // Only remove .gob files to avoid deleting unrelated files 115 if filepath.Ext(entry.Name()) == ".gob" { 116 os.Remove(filepath.Join(d.path, entry.Name())) 117 } 118 } 119 return nil 120}