tangled
alpha
login
or
join now
back
round
0
view raw
appview/pages: parse fragments in tree
#534
merged
opened by
oppi.li
4 months ago
targeting
master
from
push-mvmrzuxwmzvs
this allows fragments to reference each other.
Signed-off-by: oppiliappan
me@oppi.li
options
unified
split
Changed files
+51
-94
appview
pages
pages.go
+51
-94
appview/pages/pages.go
···
9
9
"html/template"
10
10
"io"
11
11
"io/fs"
12
12
-
"log"
12
12
+
"log/slog"
13
13
"net/http"
14
14
"os"
15
15
"path/filepath"
···
48
48
avatar config.AvatarConfig
49
49
resolver *idresolver.Resolver
50
50
dev bool
51
51
-
embedFS embed.FS
51
51
+
embedFS fs.FS
52
52
templateDir string // Path to templates on disk for dev mode
53
53
rctx *markup.RenderContext
54
54
+
logger *slog.Logger
54
55
}
55
56
56
57
func NewPages(config *config.Config, res *idresolver.Resolver) *Pages {
···
67
68
t: make(map[string]*template.Template),
68
69
dev: config.Core.Dev,
69
70
avatar: config.Avatar,
70
70
-
embedFS: Files,
71
71
rctx: rctx,
72
72
resolver: res,
73
73
templateDir: "appview/pages",
74
74
+
logger: slog.Default().With("component", "pages"),
74
75
}
75
76
76
77
// Initial load of all templates
···
79
80
return p
80
81
}
81
82
82
82
-
func (p *Pages) loadAllTemplates() {
83
83
-
templates := make(map[string]*template.Template)
83
83
+
func (p *Pages) fragmentPaths() ([]string, error) {
84
84
var fragmentPaths []string
85
85
-
86
86
-
// Use embedded FS for initial loading
87
87
-
// First, collect all fragment paths
88
85
err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
89
86
if err != nil {
90
87
return err
···
98
95
if !strings.Contains(path, "fragments/") {
99
96
return nil
100
97
}
101
101
-
name := strings.TrimPrefix(path, "templates/")
102
102
-
name = strings.TrimSuffix(name, ".html")
103
103
-
tmpl, err := template.New(name).
104
104
-
Funcs(p.funcMap()).
105
105
-
ParseFS(p.embedFS, path)
106
106
-
if err != nil {
107
107
-
log.Fatalf("setting up fragment: %v", err)
108
108
-
}
109
109
-
templates[name] = tmpl
110
98
fragmentPaths = append(fragmentPaths, path)
111
111
-
log.Printf("loaded fragment: %s", name)
112
99
return nil
113
100
})
114
101
if err != nil {
115
115
-
log.Fatalf("walking template dir for fragments: %v", err)
102
102
+
return nil, err
103
103
+
}
104
104
+
105
105
+
return fragmentPaths, nil
106
106
+
}
107
107
+
108
108
+
func (p *Pages) loadAllTemplates() {
109
109
+
if p.dev {
110
110
+
p.embedFS = os.DirFS(p.templateDir)
111
111
+
} else {
112
112
+
p.embedFS = Files
116
113
}
117
114
115
115
+
l := p.logger.With("handler", "loadAllTemplates")
116
116
+
templates := make(map[string]*template.Template)
117
117
+
fragmentPaths, err := p.fragmentPaths()
118
118
+
if err != nil {
119
119
+
l.Error("failed to collect fragments", "err", err)
120
120
+
return
121
121
+
}
122
122
+
123
123
+
// parse all fragments together
124
124
+
allFragments := template.New("").Funcs(p.funcMap())
125
125
+
for _, f := range fragmentPaths {
126
126
+
name := strings.TrimPrefix(f, "templates/")
127
127
+
name = strings.TrimSuffix(name, ".html")
128
128
+
pf, err := template.New(name).Funcs(p.funcMap()).ParseFS(p.embedFS, f)
129
129
+
if err != nil {
130
130
+
l.Error("failed to parse fragment", "name", name, "path", f)
131
131
+
return
132
132
+
}
133
133
+
allFragments, err = allFragments.AddParseTree(name, pf.Tree)
134
134
+
if err != nil {
135
135
+
l.Error("failed to add parse tree", "name", name, "path", f)
136
136
+
return
137
137
+
}
138
138
+
templates[name] = allFragments.Lookup(name)
139
139
+
}
118
140
// Then walk through and setup the rest of the templates
119
141
err = fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
120
142
if err != nil {
···
148
170
return fmt.Errorf("setting up template: %w", err)
149
171
}
150
172
templates[name] = tmpl
151
151
-
log.Printf("loaded template: %s", name)
173
173
+
l.Debug("loaded all templates")
152
174
return nil
153
175
})
154
176
if err != nil {
155
155
-
log.Fatalf("walking template dir: %v", err)
177
177
+
l.Error("walking template dir", "err", err)
178
178
+
panic(err)
156
179
}
157
180
158
158
-
log.Printf("total templates loaded: %d", len(templates))
181
181
+
l.Info("loaded all templates", "total", len(templates))
159
182
p.mu.Lock()
160
183
defer p.mu.Unlock()
161
184
p.t = templates
162
185
}
163
186
164
164
-
// loadTemplateFromDisk loads a template from the filesystem in dev mode
165
165
-
func (p *Pages) loadTemplateFromDisk(name string) error {
166
166
-
if !p.dev {
167
167
-
return nil
168
168
-
}
169
169
-
170
170
-
log.Printf("reloading template from disk: %s", name)
171
171
-
172
172
-
// Find all fragments first
173
173
-
var fragmentPaths []string
174
174
-
err := filepath.WalkDir(filepath.Join(p.templateDir, "templates"), func(path string, d fs.DirEntry, err error) error {
175
175
-
if err != nil {
176
176
-
return err
177
177
-
}
178
178
-
if d.IsDir() {
179
179
-
return nil
180
180
-
}
181
181
-
if !strings.HasSuffix(path, ".html") {
182
182
-
return nil
183
183
-
}
184
184
-
if !strings.Contains(path, "fragments/") {
185
185
-
return nil
186
186
-
}
187
187
-
fragmentPaths = append(fragmentPaths, path)
188
188
-
return nil
189
189
-
})
190
190
-
if err != nil {
191
191
-
return fmt.Errorf("walking disk template dir for fragments: %w", err)
192
192
-
}
193
193
-
194
194
-
// Find the template path on disk
195
195
-
templatePath := filepath.Join(p.templateDir, "templates", name+".html")
196
196
-
if _, err := os.Stat(templatePath); os.IsNotExist(err) {
197
197
-
return fmt.Errorf("template not found on disk: %s", name)
198
198
-
}
199
199
-
200
200
-
// Create a new template
201
201
-
tmpl := template.New(name).Funcs(p.funcMap())
202
202
-
203
203
-
// Parse layouts
204
204
-
layoutGlob := filepath.Join(p.templateDir, "templates", "layouts", "*.html")
205
205
-
layouts, err := filepath.Glob(layoutGlob)
206
206
-
if err != nil {
207
207
-
return fmt.Errorf("finding layout templates: %w", err)
208
208
-
}
209
209
-
210
210
-
// Create paths for parsing
211
211
-
allFiles := append(layouts, fragmentPaths...)
212
212
-
allFiles = append(allFiles, templatePath)
213
213
-
214
214
-
// Parse all templates
215
215
-
tmpl, err = tmpl.ParseFiles(allFiles...)
216
216
-
if err != nil {
217
217
-
return fmt.Errorf("parsing template files: %w", err)
218
218
-
}
219
219
-
220
220
-
// Update the template in the map
221
221
-
p.mu.Lock()
222
222
-
defer p.mu.Unlock()
223
223
-
p.t[name] = tmpl
224
224
-
log.Printf("template reloaded from disk: %s", name)
225
225
-
return nil
226
226
-
}
227
227
-
228
187
func (p *Pages) executeOrReload(templateName string, w io.Writer, base string, params any) error {
229
229
-
// In dev mode, reload the template from disk before executing
188
188
+
// In dev mode, reparse templates from disk before executing
230
189
if p.dev {
231
231
-
if err := p.loadTemplateFromDisk(templateName); err != nil {
232
232
-
log.Printf("warning: failed to reload template %s from disk: %v", templateName, err)
233
233
-
// Continue with the existing template
234
234
-
}
190
190
+
p.loadAllTemplates()
235
191
}
236
192
237
193
p.mu.RLock()
···
1269
1225
1270
1226
sub, err := fs.Sub(Files, "static")
1271
1227
if err != nil {
1272
1272
-
log.Fatalf("no static dir found? that's crazy: %v", err)
1228
1228
+
p.logger.Error("no static dir found? that's crazy", "err", err)
1229
1229
+
panic(err)
1273
1230
}
1274
1231
// Custom handler to apply Cache-Control headers for font files
1275
1232
return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub))))
···
1292
1249
func CssContentHash() string {
1293
1250
cssFile, err := Files.Open("static/tw.css")
1294
1251
if err != nil {
1295
1295
-
log.Printf("Error opening CSS file: %v", err)
1252
1252
+
slog.Debug("Error opening CSS file", "err", err)
1296
1253
return ""
1297
1254
}
1298
1255
defer cssFile.Close()
1299
1256
1300
1257
hasher := sha256.New()
1301
1258
if _, err := io.Copy(hasher, cssFile); err != nil {
1302
1302
-
log.Printf("Error hashing CSS file: %v", err)
1259
1259
+
slog.Debug("Error hashing CSS file", "err", err)
1303
1260
return ""
1304
1261
}
1305
1262