[WIP] music platform user data scraper
teal-fm
atproto
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}