major refactor of render
- split many parts of RenderTemplate into their own functions so they can be used individually - move web apps into their own page, add projects page, both generated by executing links.tmpl with their own yaml data - handle / specially, generating LinkMap from Handlers
This commit is contained in:
parent
e29bc93f35
commit
3dbd5a86e2
|
@ -3,11 +3,14 @@
|
|||
Code for the [burning.moe](https://burning.moe) homepage
|
||||
|
||||
## technologies used
|
||||
- [go](https://go.dev)
|
||||
- written in [go](https://go.dev) using:
|
||||
- [chi](https://github.com/go-chi/chi)
|
||||
- [cleanenv](https://github.com/ilyakaznacheev/cleanenv)
|
||||
- [log](https://github.com/charmbracelet/log)
|
||||
- [mage](https://github.com/magefile/mage)
|
||||
- written using [helix](https://helix-editor.com/)
|
||||
- tested on [void linux](https://voidlinux.org/)
|
||||
- hosted on [debian linux](https://www.debian.org/)
|
||||
|
||||
## why?
|
||||
|
||||
|
|
|
@ -27,5 +27,8 @@ func routes(app *config.AppConfig) http.Handler {
|
|||
mux.Get(handler.Handles, handler.Handler)
|
||||
}
|
||||
|
||||
// Setup home handler
|
||||
mux.Get("/", handlers.HomeHandler)
|
||||
|
||||
return mux
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.burning.moe/celediel/burning.moe/internal/config"
|
||||
"git.burning.moe/celediel/burning.moe/internal/models"
|
||||
"git.burning.moe/celediel/burning.moe/internal/render"
|
||||
"git.burning.moe/celediel/burning.moe/internal/td"
|
||||
)
|
||||
|
||||
// Handler holds data required for handlers.
|
||||
|
@ -17,15 +23,17 @@ var app *config.AppConfig
|
|||
|
||||
// The actual handlers
|
||||
var Handlers = []Handler{
|
||||
// /about
|
||||
{
|
||||
Handles: "/about",
|
||||
Handler: makeBasicHandler("about"),
|
||||
},
|
||||
// / comes last
|
||||
{
|
||||
Handles: "/",
|
||||
Handler: makeBasicHandler("home"),
|
||||
Handles: "/projects",
|
||||
Handler: makeLinksHandler("projects"),
|
||||
},
|
||||
{
|
||||
Handles: "/apps",
|
||||
Handler: makeLinksHandler("apps"),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -34,6 +42,26 @@ func Initialise(a *config.AppConfig) {
|
|||
app = a
|
||||
}
|
||||
|
||||
// HomeHandler handles /, generating data from Handlers
|
||||
func HomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
app.Logger.Info("Got request for homepage")
|
||||
d := td.MakeBasicTemplateData(time.Now())
|
||||
var pages []models.Link = []models.Link{}
|
||||
|
||||
for _, handler := range Handlers {
|
||||
href := strings.TrimPrefix(handler.Handles, "/")
|
||||
pages = append(pages, models.Link{
|
||||
Href: template.URL(href),
|
||||
Text: href,
|
||||
})
|
||||
}
|
||||
|
||||
d.LinkMap = make(map[string][]models.Link)
|
||||
d.LinkMap["Pages"] = pages
|
||||
app.Logger.Debug("handling home with some data", "data", &d)
|
||||
render.RenderTemplateWithData(w, "home.page.tmpl", &d)
|
||||
}
|
||||
|
||||
// makeBasicHandler returns a simple handler that renders a template from `name`.page.tmpl
|
||||
func makeBasicHandler(name string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -42,3 +70,25 @@ func makeBasicHandler(name string) func(w http.ResponseWriter, r *http.Request)
|
|||
render.RenderTemplate(w, pageName)
|
||||
}
|
||||
}
|
||||
|
||||
// makeLinksHandler returns a handler for links.tmpl with template data from `name`
|
||||
func makeLinksHandler(name string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
page := "links.tmpl"
|
||||
template, err := render.GetTemplateFromCache(page)
|
||||
if err != nil {
|
||||
app.Logger.Error(fmt.Sprintf("couldn't get %s from cache", page), "err", err)
|
||||
}
|
||||
|
||||
app.Logger.Infof("Got request for %s links page", name)
|
||||
data, err := td.LoadTemplateData(name)
|
||||
if err != nil {
|
||||
app.Logger.Fatal("couldn't load template data for "+name, "err", err)
|
||||
} else {
|
||||
data.StringMap["GeneratedAt"] = template.GeneratedAt.Format(time.UnixDate)
|
||||
}
|
||||
|
||||
app.Logger.Debug("handling a links page", "data", &data)
|
||||
render.RenderTemplateWithData(w, page, &data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -16,3 +18,19 @@ type TemplateCacheItem struct {
|
|||
Template *template.Template
|
||||
GeneratedAt time.Time
|
||||
}
|
||||
|
||||
// Execute writes the template to the supplied writer using the supplied data.
|
||||
func (self *TemplateCacheItem) Execute(d *TemplateData, w http.ResponseWriter) error {
|
||||
buf := new(bytes.Buffer)
|
||||
err := self.Template.Execute(buf, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.WriteTo(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
@ -16,21 +16,21 @@ import (
|
|||
const (
|
||||
templatesDir string = "./templates/"
|
||||
layoutGlob string = "*.layout.tmpl"
|
||||
pageGlob string = "*.page.tmpl"
|
||||
pageGlob string = "*.tmpl"
|
||||
)
|
||||
|
||||
var app *config.AppConfig
|
||||
|
||||
// Initialise the render package.
|
||||
func Initialise(a *config.AppConfig) {
|
||||
var err error
|
||||
app = a
|
||||
if app.UseCache {
|
||||
var err error
|
||||
app.TemplateCache, err = GenerateNewTemplateCache()
|
||||
}
|
||||
if err != nil {
|
||||
app.Logger.Fatal("Error generating template cache, bailing out!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateNewTemplateCache generates a new template cache.
|
||||
|
@ -83,30 +83,73 @@ func GenerateNewTemplateCache() (models.TemplateCache, error) {
|
|||
|
||||
// RenderTemplate renders requested template (t), pulling from cache.
|
||||
func RenderTemplate(w http.ResponseWriter, filename string) {
|
||||
// TODO: implement this better
|
||||
if !app.UseCache {
|
||||
RegenerateTemplateCache()
|
||||
}
|
||||
|
||||
template, err := GetTemplateFromCache(filename)
|
||||
if err != nil {
|
||||
app.Logger.Fatalf("Tried loading %s from the cache, but %s!", filename, err)
|
||||
}
|
||||
|
||||
data, err := GetOrGenerateTemplateData(filename)
|
||||
if err != nil {
|
||||
app.Logger.Error(err)
|
||||
}
|
||||
|
||||
app.Logger.Debug(fmt.Sprintf("Executing template %s", filename), "data", &data)
|
||||
err = template.Execute(data, w)
|
||||
if err != nil {
|
||||
app.Logger.Fatalf("Failed to execute template %s: %s", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
func RenderTemplateWithData(w http.ResponseWriter, filename string, data *models.TemplateData) {
|
||||
if !app.UseCache {
|
||||
RegenerateTemplateCache()
|
||||
}
|
||||
|
||||
template, err := GetTemplateFromCache(filename)
|
||||
if err != nil {
|
||||
app.Logger.Fatalf("Tried loading %s from the cache, but %s!", filename, err)
|
||||
}
|
||||
|
||||
app.Logger.Debug(fmt.Sprintf("Executing template %s", filename), "data", &data)
|
||||
err = template.Execute(data, w)
|
||||
if err != nil {
|
||||
app.Logger.Fatalf("Failed to execute template %s: %s", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
func RegenerateTemplateCache() {
|
||||
c, err := GenerateNewTemplateCache()
|
||||
if err != nil {
|
||||
app.Logger.Fatal("Error generating template cache, bailing out!")
|
||||
}
|
||||
app.TemplateCache = c
|
||||
}
|
||||
|
||||
// Get templates from cache
|
||||
template, ok := app.TemplateCache.Cache[filename]
|
||||
if !ok {
|
||||
app.Logger.Errorf("Couldn't get %s from template cache, dunno what happened, but we're gonna generate a new one", filename)
|
||||
c, err := GenerateNewTemplateCache()
|
||||
}
|
||||
|
||||
// GetTemplateFromCache gets templates from cache
|
||||
func GetTemplateFromCache(filename string) (*models.TemplateCacheItem, error) {
|
||||
if template, ok := app.TemplateCache.Cache[filename]; ok {
|
||||
return &template, nil
|
||||
} else {
|
||||
return &models.TemplateCacheItem{}, errors.New("Couldn't load template from cache")
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrGenerateTemplateData gets template data from file, or generate simple
|
||||
func GetOrGenerateTemplateData(filename string) (*models.TemplateData, error) {
|
||||
template, err := GetTemplateFromCache(filename)
|
||||
if err != nil {
|
||||
app.Logger.Fatal("Error generating template cache, bailing out!")
|
||||
}
|
||||
app.TemplateCache = c
|
||||
template = app.TemplateCache.Cache[filename]
|
||||
return &models.TemplateData{}, err
|
||||
}
|
||||
|
||||
// Get template data from file, or generate simple
|
||||
data, err := td.LoadTemplateData(filename)
|
||||
if err == nil {
|
||||
app.Logger.Debug(fmt.Sprintf("Loaded data for template %s.", filename), "data", data)
|
||||
app.Logger.Debug(fmt.Sprintf("Loaded data for template %s.", filename), "data", &data)
|
||||
if _, ok := data.StringMap["GeneratedAt"]; !ok {
|
||||
data.StringMap["GeneratedAt"] = template.GeneratedAt.Format(time.UnixDate)
|
||||
}
|
||||
|
@ -115,15 +158,5 @@ func RenderTemplate(w http.ResponseWriter, filename string) {
|
|||
data = td.MakeBasicTemplateData(template.GeneratedAt)
|
||||
}
|
||||
|
||||
// Execute templates in a new buffer
|
||||
buf := new(bytes.Buffer)
|
||||
err = template.Template.Execute(buf, data)
|
||||
if err != nil {
|
||||
app.Logger.Fatal(fmt.Sprintf("Error executing template %s! Goodbye!", filename), "err", err)
|
||||
}
|
||||
|
||||
_, err = buf.WriteTo(w)
|
||||
if err != nil {
|
||||
app.Logger.Error(fmt.Sprintf("Error writing template %s!", filename), "err", err)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ const dataDir string = "./templates/data/"
|
|||
// in order of precedence
|
||||
var dataExtensions = [4]string{"yml", "yaml", "toml", "json"}
|
||||
|
||||
// makeBasicTemplateData creates a blank TemplateData containing only the
|
||||
// time the related template was generated
|
||||
// makeBasicTemplateData creates a blank TemplateData
|
||||
// containing only the time the template was generated
|
||||
func MakeBasicTemplateData(when time.Time) models.TemplateData {
|
||||
strMap := map[string]string{
|
||||
"GeneratedAt": when.Format(time.UnixDate),
|
||||
|
@ -33,13 +33,16 @@ func MakeBasicTemplateData(when time.Time) models.TemplateData {
|
|||
// fails, it returns an empty TemplateData and an error
|
||||
func LoadTemplateData(page string) (models.TemplateData, error) {
|
||||
var data models.TemplateData
|
||||
output := dataDir + strings.ReplaceAll(page, "tmpl", "")
|
||||
output := dataDir + strings.ReplaceAll(page, ".tmpl", "")
|
||||
|
||||
for _, extension := range dataExtensions {
|
||||
if info, err := os.Stat(output + extension); err == nil && !info.IsDir() {
|
||||
err = cleanenv.ReadConfig(output+extension, &data)
|
||||
if info, err := os.Stat(output + "." + extension); err == nil && !info.IsDir() {
|
||||
err = cleanenv.ReadConfig(output+"."+extension, &data)
|
||||
if err == nil {
|
||||
// don't try anymore files
|
||||
// don't try anymore files, but do setup an empty StringMap
|
||||
if len(data.StringMap) == 0 {
|
||||
data.StringMap = make(map[string]string)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Gloria+Hallelujah&display=swap');
|
||||
/*
|
||||
TODO: update this to be a bit more modern
|
||||
maybe use SASS or something
|
||||
|
||||
the real TODO here is finding an image that doesn't require a white background
|
||||
|
||||
also TODO: proper mobile layout
|
||||
*/
|
||||
|
||||
body {
|
||||
background: #FFF;
|
||||
|
|
|
@ -9,16 +9,7 @@
|
|||
|
||||
<!-- personal links -->
|
||||
<h4>links</h4>
|
||||
{{ range (index .LinkMap "Personal") }}
|
||||
<a href="{{ .Href }}">
|
||||
<span class="iconify" data-icon="{{ .Icon }}"></span>
|
||||
{{ .Text }}
|
||||
</a><br />
|
||||
{{ end }}
|
||||
|
||||
<!-- web app links -->
|
||||
<h4>hosted apps</h4>
|
||||
{{ range (index .LinkMap "HostedApps") }}
|
||||
{{- range (index .LinkMap "Personal") }}
|
||||
<a href="{{ .Href }}">
|
||||
<span class="iconify" data-icon="{{ .Icon }}"></span>
|
||||
{{ .Text }}
|
||||
|
|
|
@ -14,13 +14,3 @@ LinkMap:
|
|||
- href: https://github.com/celediel
|
||||
text: github
|
||||
icon: bi:github
|
||||
HostedApps:
|
||||
- href: https://git.burning.moe/
|
||||
text: self-hosted git
|
||||
icon: mdi:git
|
||||
- href: https://bin.burning.moe/
|
||||
text: wastebin
|
||||
icon: fluent:bin-recycle-20-filled
|
||||
- href: https://gist.burning.moe/
|
||||
text: gist
|
||||
icon: carbon:paste
|
||||
|
|
11
templates/data/apps.yaml
Normal file
11
templates/data/apps.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
LinkMap:
|
||||
hosted apps:
|
||||
- href: https://git.burning.moe/
|
||||
text: self-hosted git
|
||||
icon: mdi:git
|
||||
- href: https://bin.burning.moe/
|
||||
text: wastebin
|
||||
icon: fluent:bin-recycle-20-filled
|
||||
- href: https://gist.burning.moe/
|
||||
text: gist
|
||||
icon: carbon:paste
|
25
templates/data/projects.yaml
Normal file
25
templates/data/projects.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
LinkMap:
|
||||
morrowind lua mods:
|
||||
- href: https://git.burning.moe/celediel/morrowind-a-sinking-feeling
|
||||
text: a sinking feeling - armour causes sinking in water
|
||||
icon: simple-icons:lua
|
||||
- href: https://git.burning.moe/celediel/morrowind-no-more-friendly-fire
|
||||
text: no more friendly fire - does what it says on the tin
|
||||
icon: simple-icons:lua
|
||||
- href: https://git.burning.moe/celediel/morrowind-door-randomizer
|
||||
text: door randomizer - randomizes destinations of cell change doors
|
||||
icon: simple-icons:lua
|
||||
- href: https://git.burning.moe/celediel/morrowind-more-attentive-guards
|
||||
text: more attentive guards - guards follow sneaking characters and help out when attacked unjustly
|
||||
icon: simple-icons:lua
|
||||
other modding:
|
||||
- href: https://git.burning.moe/celediel/qud-mods
|
||||
text: Assorted mods for Caves of Qud (C#)
|
||||
icon: teenyicons:c-sharp-solid
|
||||
misc:
|
||||
- href: https://git.burning.moe/celediel/poiekolon.nvim
|
||||
text: poiekolon - neovim plugin to add or toggle a char at the end of a line
|
||||
icon: simple-icons:lua
|
||||
- href: https://git.burning.moe/celediel/burning.moe
|
||||
text: burning.moe - this website
|
||||
icon: file-icons:go-old
|
|
@ -11,6 +11,11 @@
|
|||
</a>
|
||||
<br />
|
||||
<span id="words">
|
||||
<!--
|
||||
<a href="about">about</a>
|
||||
-->
|
||||
{{ range (index .LinkMap "Pages")}}
|
||||
<a href="{{ .Href }}">{{ .Text }}</a><br />
|
||||
{{ end }}
|
||||
</span>
|
||||
{{ end -}}
|
||||
|
|
25
templates/links.tmpl
Normal file
25
templates/links.tmpl
Normal file
|
@ -0,0 +1,25 @@
|
|||
{{- template "base" . -}}
|
||||
{{- define "content" }}
|
||||
<span id="words">
|
||||
{{- range $key, $value := .LinkMap }}
|
||||
<h4>{{ $key }}</h4>
|
||||
{{- range $value }}
|
||||
<a href="{{ .Href }}">
|
||||
{{- if (ne .Icon "") }}
|
||||
<span class="iconify" data-icon="{{ .Icon }}"></span>
|
||||
{{ end }}
|
||||
{{- .Text }}
|
||||
</a><br />
|
||||
{{- end }}
|
||||
{{ end -}}
|
||||
<br />
|
||||
<a href="/">
|
||||
<span class="iconify" data-icon="material-symbols:keyboard-arrow-left"></span>
|
||||
back
|
||||
</a>
|
||||
</span>
|
||||
{{ end -}}
|
||||
|
||||
{{- define "js" }}
|
||||
<script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
|
||||
{{ end -}}
|
Loading…
Reference in a new issue