feat: implement RendererManager and WatcherManager

This commit is contained in:
Bastien Riviere 2023-12-16 00:35:48 +01:00
parent dbf6ff32ab
commit 0262bc51cd
Signed by: babariviere
GPG key ID: 4E5F0839249F162E
3 changed files with 189 additions and 0 deletions

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module forge.babariviere.com/babariviere/render
go 1.21.5

129
render.go Normal file
View file

@ -0,0 +1,129 @@
package render
import (
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"path/filepath"
"slices"
"strings"
)
type Manager interface {
GetPage(page string, username string) Renderer
GetBlock(page string, block string) Renderer
FromRequest(r *http.Request, page string, username string) Renderer
}
type RendererManager struct {
pages map[string]Renderer
}
func ParseTemplates(f fs.FS) (*RendererManager, error) {
rm := RendererManager{
pages: make(map[string]Renderer),
}
var components []string
err := fs.WalkDir(f, ".", func(path string, d fs.DirEntry, err error) error {
if strings.HasPrefix(path, "pages") {
return fs.SkipDir
}
if strings.HasSuffix(path, ".gohtml") {
components = append(components, path)
}
return nil
})
if err != nil {
return nil, err
}
err = fs.WalkDir(f, "pages", func(path string, d fs.DirEntry, err error) error {
if strings.HasSuffix(path, ".gohtml") {
var cs []string
parent := strings.Split(filepath.Dir(path), "/")
for _, c := range components {
if !strings.HasPrefix(c, "components") {
fmt.Println("not component", c)
cs = append(cs, c)
continue
}
if strings.Count(c, "/") == 1 {
fmt.Println("root component", c)
cs = append(cs, c)
continue
}
parts := strings.Split(filepath.Dir(c), "/")
if slices.Equal(parent[1:], parts[1:]) {
fmt.Println("same prefix", parent, parts)
cs = append(cs, c)
}
}
fmt.Println(path, cs)
tmpl, err := template.ParseFS(f, append(cs, path)...)
if err != nil {
return err
}
name := strings.TrimPrefix(strings.TrimSuffix(path, ".gohtml"), "pages/")
rm.pages[name] = Renderer{
tmpl: tmpl,
}
}
return err
})
if err != nil {
return nil, err
}
return &rm, nil
}
func (rm RendererManager) GetPage(page string, username string) Renderer {
p := rm.pages[page]
p.username = username
return p
}
func (rm RendererManager) GetBlock(page string, block string) Renderer {
p := rm.pages[page]
p.target = block
return p
}
func (rm RendererManager) FromRequest(r *http.Request, page string, username string) Renderer {
p := rm.pages[page]
if r.Header.Get("HX-Boosted") == "true" {
p.target = "content"
} else if target := r.Header.Get("HX-Target"); target != "" {
p.target = target
}
return p
}
type Renderer struct {
tmpl *template.Template
target string
username string
}
func (r Renderer) Render(w io.Writer, data any) error {
if r.target != "" {
// TODO: allow client to use it's own template for auth
// I use it like this since this is what I do for all my projects for now. (and it's not the best)
return r.tmpl.ExecuteTemplate(w, r.target, layoutData{Data: data, Username: r.username})
}
return r.tmpl.Execute(w, data)
}
type layoutData struct {
Username string
Data any
}

57
watcher.go Normal file
View file

@ -0,0 +1,57 @@
package render
import (
"net/http"
"os"
"sync"
)
var _ Manager = (*WatcherManager)(nil)
type WatcherManager struct {
mu sync.RWMutex
dir string
manager *RendererManager
}
func NewWatcher(dir string) (*WatcherManager, error) {
renderer, err := ParseTemplates(os.DirFS(dir))
if err != nil {
return nil, err
}
return &WatcherManager{
dir: dir,
manager: renderer,
}, nil
}
func (wm *WatcherManager) Refresh() error {
wm.mu.Lock()
defer wm.mu.Unlock()
var err error
wm.manager, err = ParseTemplates(os.DirFS(wm.dir))
return err
}
func (wm *WatcherManager) GetPage(page string, username string) Renderer {
wm.mu.RLock()
defer wm.mu.RUnlock()
return wm.manager.GetPage(page, username)
}
func (wm *WatcherManager) GetBlock(page string, block string) Renderer {
wm.mu.RLock()
defer wm.mu.RUnlock()
return wm.manager.GetBlock(page, block)
}
func (wm *WatcherManager) FromRequest(r *http.Request, page string, username string) Renderer {
wm.mu.RLock()
defer wm.mu.RUnlock()
return wm.manager.FromRequest(r, page, username)
}