[WIP] music platform user data scraper
teal-fm atproto
32
fork

Configure Feed

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

at b65aa00ab64ac10f0a6fd89e8ef6c873f5ada396 140 lines 2.9 kB view raw
1package pages 2 3// inspired from tangled's implementation 4//https://tangled.org/@tangled.org/core/blob/master/appview/pages/pages.go 5 6import ( 7 "embed" 8 "html/template" 9 "io" 10 "io/fs" 11 "strings" 12 "time" 13) 14 15//go:embed templates/* 16var Files embed.FS 17 18type Pages struct { 19 cache *TmplCache[string, *template.Template] 20 templateDir string // Path to templates on disk for dev mode 21 embedFS fs.FS 22} 23 24func NewPages() *Pages { 25 return &Pages{ 26 cache: NewTmplCache[string, *template.Template](), 27 embedFS: Files, 28 } 29} 30 31func (p *Pages) fragmentPaths() ([]string, error) { 32 var fragmentPaths []string 33 // When using os.DirFS("templates"), the FS root is already the templates directory. 34 // Walk from "." and use relative paths (no "templates/" prefix). 35 err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error { 36 if err != nil { 37 return err 38 } 39 if d.IsDir() { 40 return nil 41 } 42 if !strings.HasSuffix(path, ".gohtml") { 43 return nil 44 } 45 //if !strings.Contains(path, "fragments/") { 46 // return nil 47 //} 48 fragmentPaths = append(fragmentPaths, path) 49 return nil 50 }) 51 if err != nil { 52 return nil, err 53 } 54 55 return fragmentPaths, nil 56} 57 58func (p *Pages) pathToName(s string) string { 59 return strings.TrimSuffix(strings.TrimPrefix(s, "templates/"), ".gohtml") 60} 61 62// reverse of pathToName 63func (p *Pages) nameToPath(s string) string { 64 return "templates/" + s + ".gohtml" 65} 66 67// parse without memoization 68func (p *Pages) rawParse(stack ...string) (*template.Template, error) { 69 paths, err := p.fragmentPaths() 70 if err != nil { 71 return nil, err 72 } 73 for _, s := range stack { 74 paths = append(paths, p.nameToPath(s)) 75 } 76 77 funcs := p.funcMap() 78 top := stack[len(stack)-1] 79 parsed, err := template.New(top). 80 Funcs(funcs). 81 ParseFS(p.embedFS, paths...) 82 if err != nil { 83 return nil, err 84 } 85 86 return parsed, nil 87} 88 89func (p *Pages) parse(stack ...string) (*template.Template, error) { 90 key := strings.Join(stack, "|") 91 92 if cached, exists := p.cache.Get(key); exists { 93 return cached, nil 94 } 95 96 result, err := p.rawParse(stack...) 97 if err != nil { 98 return nil, err 99 } 100 101 p.cache.Set(key, result) 102 return result, nil 103} 104 105func (p *Pages) funcMap() template.FuncMap { 106 return template.FuncMap{ 107 "formatTime": func(t time.Time) string { 108 if t.IsZero() { 109 return "N/A" 110 } 111 return t.Format("Jan 02, 2006 15:04") 112 }, 113 } 114} 115 116func (p *Pages) parseBase(top string) (*template.Template, error) { 117 stack := []string{ 118 "layouts/base", 119 top, 120 } 121 return p.parse(stack...) 122} 123 124func (p *Pages) executePlain(name string, w io.Writer, params any) error { 125 tpl, err := p.parse(name) 126 if err != nil { 127 return err 128 } 129 130 return tpl.Execute(w, params) 131} 132 133func (p *Pages) Execute(name string, w io.Writer, params any) error { 134 tpl, err := p.parseBase(name) 135 if err != nil { 136 return err 137 } 138 139 return tpl.ExecuteTemplate(w, "layouts/base", params) 140}