commit 7f9e9ab6d8888ea1820156b648edef87107ae9a8 Author: Gergő Dulai Date: Fri Oct 2 21:04:17 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6da6df5 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c004600 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module rndr + +go 1.27.1 + +require gitlab.com/gdulai/simpleloglvl v0.0.0-20260811205046-ece654ff6b31 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..de4a8ce --- /dev/null +++ b/go.sum @@ -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= diff --git a/renderer.go b/renderer.go new file mode 100644 index 0000000..722cf80 --- /dev/null +++ b/renderer.go @@ -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() +}