Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/pages: parse fragments in tree

this allows fragments to reference each other.

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
0599bb6f 4ff05477

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