From 0d3c1ad355833c31810abd3fcd4e63e9f6726b06 Mon Sep 17 00:00:00 2001 From: Simon Martens Date: Thu, 14 Nov 2024 22:18:36 +0100 Subject: [PATCH] Added template engine --- templating/engine.go | 80 +++++++++++++++++++++++++++++++++ templating/layout_registry.go | 32 +++++-------- templating/template_registry.go | 33 +++++--------- 3 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 templating/engine.go diff --git a/templating/engine.go b/templating/engine.go new file mode 100644 index 0000000..af54f45 --- /dev/null +++ b/templating/engine.go @@ -0,0 +1,80 @@ +package templating + +import ( + "html/template" + "io" + "io/fs" + "sync" +) + +type Engine struct { + // NOTE: LayoutRegistry and TemplateRegistry have their own syncronization and do not require a mutex here + LayoutRegistry *LayoutRegistry + TemplateRegistry *TemplateRegistry + + mu *sync.Mutex + FuncMap template.FuncMap +} + +func NewEngine(layouts, templates *fs.FS) *Engine { + return &Engine{ + mu: &sync.Mutex{}, + LayoutRegistry: NewLayoutRegistry(*layouts), + TemplateRegistry: NewTemplateRegistry(*templates), + FuncMap: template.FuncMap{}, + } +} + +func (e *Engine) Load() error { + wg := sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + e.LayoutRegistry.Load() + }() + + go func() { + defer wg.Done() + e.TemplateRegistry.Load() + }() + + wg.Wait() + return nil +} + +func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...string) error { + // TODO: check if a reload is needed if files on disk have changed + + var l *template.Template + if layout == nil || len(layout) == 0 { + lay, err := e.LayoutRegistry.Default() + if err != nil { + return err + } + l = lay + } else { + lay, err := e.LayoutRegistry.Layout(layout[0]) + if err != nil { + return err + } + l = lay + } + + lay, err := l.Clone() + if err != nil { + return err + } + + err = e.TemplateRegistry.Add(path, lay) + if err != nil { + return err + } + + err = lay.Execute(out, data) + if err != nil { + return err + } + + return nil +} diff --git a/templating/layout_registry.go b/templating/layout_registry.go index db1b83f..7a6bf78 100644 --- a/templating/layout_registry.go +++ b/templating/layout_registry.go @@ -20,17 +20,11 @@ type LayoutRegistry struct { layouts map[string]TemplateContext // WARNING: maybe this is too early for caching? cache sync.Map - funcs template.FuncMap } func NewLayoutRegistry(routes fs.FS) *LayoutRegistry { return &LayoutRegistry{ layoutsFS: routes, - funcs: template.FuncMap{ - "safe": func(s string) template.HTML { - return template.HTML(s) - }, - }, } } @@ -39,14 +33,19 @@ func (r *LayoutRegistry) Register(fs fs.FS) *LayoutRegistry { return NewLayoutRegistry(merged_fs.MergeMultiple(fs, r.layoutsFS)) } -// TODO: Funcs are not used in executing the templates yet -func (r *LayoutRegistry) RegisterFuncs(funcs template.FuncMap) { - for k, v := range funcs { - r.funcs[k] = v - } +func (r *LayoutRegistry) Load() error { + r.once.Do(func() { + err := r.load() + if err != nil { + fmt.Println(err) + panic(-1) + } + }) + + return nil } -func (r *LayoutRegistry) Parse() error { +func (r *LayoutRegistry) load() error { layouts := make(map[string]TemplateContext) rootcontext := NewTemplateContext(".") err := rootcontext.Parse(r.layoutsFS) @@ -85,14 +84,7 @@ func (r *LayoutRegistry) Layout(name string) (*template.Template, error) { } // TODO: What todo on errors? - r.once.Do(func() { - err := r.Parse() - if err != nil { - fmt.Println(err) - panic(-1) - } - }) - + r.Load() context, ok := r.layouts[name] if !ok { return nil, NewError(NoTemplateError, name) diff --git a/templating/template_registry.go b/templating/template_registry.go index bd883df..3b2e1fe 100644 --- a/templating/template_registry.go +++ b/templating/template_registry.go @@ -16,21 +16,14 @@ type TemplateRegistry struct { routesFS fs.FS once sync.Once // INFO: Template & cache keys are directory routing paths, with '/' as root - // INFO: we don't need a mutex here since this is set in Parse() and never changed. - // Parse() is called only once in a thread-safe manner + // INFO: we don't need a mutex here since this is set in Load() protected by Once(). templates map[string]TemplateContext cache sync.Map - funcs template.FuncMap } func NewTemplateRegistry(routes fs.FS) *TemplateRegistry { return &TemplateRegistry{ routesFS: routes, - funcs: template.FuncMap{ - "safe": func(s string) template.HTML { - return template.HTML(s) - }, - }, } } @@ -40,16 +33,21 @@ func (r *TemplateRegistry) Register(path string, fs fs.FS) *TemplateRegistry { return NewTemplateRegistry(merged_fs.MergeMultiple(fs, r.routesFS)) } -func (r *TemplateRegistry) RegisterFuncs(funcs template.FuncMap) { - for k, v := range funcs { - r.funcs[k] = v - } +func (r *TemplateRegistry) Load() error { + r.once.Do(func() { + err := r.load() + if err != nil { + fmt.Println(err) + panic(-1) + } + }) + return nil } // TODO: Throw errors // TODO: what if there is no template in the directory above? // What if a certain path is or should uncallable since it has no index or body? -func (r *TemplateRegistry) Parse() error { +func (r *TemplateRegistry) load() error { // INFO: Parse setrs r.templates, which is why you need to make sure to call Parse() once templates := make(map[string]TemplateContext) fs.WalkDir(r.routesFS, ".", func(path string, d fs.DirEntry, err error) error { @@ -87,14 +85,7 @@ func (r *TemplateRegistry) Parse() error { func (r *TemplateRegistry) Add(path string, t *template.Template) error { temp, ok := r.cache.Load(path) if !ok { - // INFO: What todo on errors? - r.once.Do(func() { - err := r.Parse() - if err != nil { - fmt.Println(err) - panic(-1) - } - }) + r.Load() tc, ok := r.templates[path] if !ok { return NewError(NoTemplateError, path)