miniflux-v2/internal/template/engine.go

140 lines
3.3 KiB
Go

// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package template // import "miniflux.app/v2/internal/template"
import (
"bytes"
"embed"
"html/template"
"log/slog"
"strings"
"time"
"miniflux.app/v2/internal/locale"
"github.com/gorilla/mux"
)
//go:embed templates/common/*.html
var commonTemplateFiles embed.FS
//go:embed templates/views/*.html
var viewTemplateFiles embed.FS
//go:embed templates/standalone/*.html
var standaloneTemplateFiles embed.FS
// Engine handles the templating system.
type Engine struct {
templates map[string]*template.Template
funcMap *funcMap
}
// NewEngine returns a new template engine.
func NewEngine(router *mux.Router) *Engine {
return &Engine{
templates: make(map[string]*template.Template),
funcMap: &funcMap{router},
}
}
// ParseTemplates parses template files embed into the application.
func (e *Engine) ParseTemplates() error {
var commonTemplateContents strings.Builder
dirEntries, err := commonTemplateFiles.ReadDir("templates/common")
if err != nil {
return err
}
for _, dirEntry := range dirEntries {
fileData, err := commonTemplateFiles.ReadFile("templates/common/" + dirEntry.Name())
if err != nil {
return err
}
commonTemplateContents.Write(fileData)
}
dirEntries, err = viewTemplateFiles.ReadDir("templates/views")
if err != nil {
return err
}
for _, dirEntry := range dirEntries {
templateName := dirEntry.Name()
fileData, err := viewTemplateFiles.ReadFile("templates/views/" + dirEntry.Name())
if err != nil {
return err
}
var templateContents strings.Builder
templateContents.WriteString(commonTemplateContents.String())
templateContents.Write(fileData)
slog.Debug("Parsing template",
slog.String("template_name", templateName),
)
e.templates[templateName] = template.Must(template.New("main").Funcs(e.funcMap.Map()).Parse(templateContents.String()))
}
dirEntries, err = standaloneTemplateFiles.ReadDir("templates/standalone")
if err != nil {
return err
}
for _, dirEntry := range dirEntries {
templateName := dirEntry.Name()
fileData, err := standaloneTemplateFiles.ReadFile("templates/standalone/" + dirEntry.Name())
if err != nil {
return err
}
slog.Debug("Parsing template",
slog.String("template_name", templateName),
)
e.templates[templateName] = template.Must(template.New("base").Funcs(e.funcMap.Map()).Parse(string(fileData)))
}
return nil
}
// Render process a template.
func (e *Engine) Render(name string, data map[string]interface{}) []byte {
tpl, ok := e.templates[name]
if !ok {
panic("This template does not exists: " + name)
}
printer := locale.NewPrinter(data["language"].(string))
// Functions that need to be declared at runtime.
tpl.Funcs(template.FuncMap{
"elapsed": func(timezone string, t time.Time) string {
return elapsedTime(printer, timezone, t)
},
"t": func(key interface{}, args ...interface{}) string {
switch k := key.(type) {
case string:
return printer.Printf(k, args...)
case error:
return k.Error()
default:
return ""
}
},
"plural": func(key string, n int, args ...interface{}) string {
return printer.Plural(key, n, args...)
},
})
var b bytes.Buffer
err := tpl.ExecuteTemplate(&b, "base", data)
if err != nil {
panic(err)
}
return b.Bytes()
}