A go template renderer based on Perl's Template Toolkit
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}