Initial commit
This commit is contained in:
+38
@@ -0,0 +1,38 @@
|
|||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Code coverage profiles and other test artifacts
|
||||||
|
*.out
|
||||||
|
coverage.*
|
||||||
|
*.coverprofile
|
||||||
|
profile.cov
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Editor/IDE
|
||||||
|
# .idea/
|
||||||
|
# .vscode/
|
||||||
|
|
||||||
|
__debug*
|
||||||
|
tmp
|
||||||
|
*.db
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module rndr
|
||||||
|
|
||||||
|
go 1.27.1
|
||||||
|
|
||||||
|
require gitlab.com/gdulai/simpleloglvl v0.0.0-20260811205046-ece654ff6b31
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
gitlab.com/gdulai/simpleloglvl v0.0.0-20260811205046-ece654ff6b31 h1:vLRJpOsFrdXFjRHyhw2BluMslKv63jVvngc5HE/u/xM=
|
||||||
|
gitlab.com/gdulai/simpleloglvl v0.0.0-20260811205046-ece654ff6b31/go.mod h1:H7XPunUrSyAvPa9nx8UbKnThEQJDmj3mdt+9ZtqDth4=
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
package rndr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
log "gitlab.com/gdulai/simpleloglvl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ComponentRenderer handles the associations between templates and models, and renders the template files with the appropriate backing model.
|
||||||
|
type ComponentRenderer struct {
|
||||||
|
templates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRenderer creates returns the pointer of newly created ComponentRenderer, or an error.
|
||||||
|
func NewRenderer(root string) (*ComponentRenderer, error) {
|
||||||
|
templates, err := loadTemplates(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ComponentRenderer{templates: templates}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render, renders a template html file with a backing model.
|
||||||
|
// The template file name is derived from the model type via reflection.
|
||||||
|
func (renderer *ComponentRenderer) Render(w http.ResponseWriter, r *http.Request, model any) {
|
||||||
|
requestId := r.Context().Value("requestId")
|
||||||
|
modelTyp := reflect.TypeOf(model)
|
||||||
|
for modelTyp.Kind() == reflect.Pointer {
|
||||||
|
modelTyp = modelTyp.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if modelTyp == nil {
|
||||||
|
log.LogError("Could not determine model type for %s (%s)", model, requestId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
templateName := toKebabCase(modelTyp.Name()) + ".html"
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
err := renderer.templates.ExecuteTemplate(w, templateName, model)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.LogError("Error while serving html teplate: %s (%s)", err, requestId)
|
||||||
|
http.Error(w, "Component render error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderLayout, renders layout template file, without any backing model
|
||||||
|
func (renderer *ComponentRenderer) RenderLayout(w http.ResponseWriter, r *http.Request, templateName string) {
|
||||||
|
requestId := r.Context().Value("requestId")
|
||||||
|
err := renderer.templates.ExecuteTemplate(w, templateName, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.LogError("Error while serving html teplate: %s (%s)", err, requestId)
|
||||||
|
http.Error(w, "Component render error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplates(root string) (*template.Template, error) {
|
||||||
|
var files []string
|
||||||
|
|
||||||
|
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.IsDir() && filepath.Ext(path) == ".html" {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.ParseFiles(files...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toKebabCase(s string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
for i, r := range s {
|
||||||
|
if i > 0 && unicode.IsUpper(r) {
|
||||||
|
prev := rune(s[i-1])
|
||||||
|
if unicode.IsLower(prev) ||
|
||||||
|
(i+1 < len(s) && unicode.IsLower(rune(s[i+1]))) {
|
||||||
|
b.WriteByte('-')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteRune(unicode.ToLower(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user