hmn/src/website/fishbowl.go

164 lines
4.2 KiB
Go

package website
import (
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"strings"
"time"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/oops"
"git.handmade.network/hmn/hmn/src/templates"
"git.handmade.network/hmn/hmn/src/utils"
)
// This will skip the common path prefix for fishbowl files.
// We unfortunately need to do this because we want to use http.FileServer,
// but _that_ needs an http.FS, but _that_ needs an fs.FS...
var fishbowlFS = utils.Must1(fs.Sub(templates.FishbowlFS, "src/fishbowls"))
var fishbowlHTTPFS = http.StripPrefix("/fishbowl", http.FileServer(http.FS(fishbowlFS)))
type fishbowlInfo struct {
Slug string
Title string
Month time.Month
Year int
ContentsPath string
}
var fishbowls = [...]fishbowlInfo{
{
Slug: "internet-os",
Title: "The future of operating systems in an Internet world",
Month: time.May, Year: 2020,
},
{
Slug: "metaprogramming",
Title: "Compile-time introspection and metaprogramming",
Month: time.June, Year: 2020,
},
{
Slug: "lisp-jam",
Title: "Lessons from the LISP Jam",
Month: time.August, Year: 2020,
},
{
Slug: "parallel-programming",
Title: "Approaches to parallel programming",
Month: time.November, Year: 2020,
},
{
Slug: "skimming",
Title: "Code skimmability as the root cause for bad code structure decisions", // real snappy, this one
Month: time.January, Year: 2021,
},
{
Slug: "config",
Title: "How to design to avoid configuration",
Month: time.March, Year: 2021,
},
{
Slug: "simplicity-performance",
Title: "The relationship of simplicity and performance",
Month: time.May, Year: 2021,
},
{
Slug: "teaching-software",
Title: "How software development is taught",
Month: time.June, Year: 2021,
},
{
Slug: "flexible-software",
Title: "How to design flexible software",
Month: time.December, Year: 2021,
},
{
Slug: "oop",
Title: "What, if anything, is OOP?",
Month: time.May, Year: 2022,
ContentsPath: "oop/OOP.html",
},
}
func FishbowlIndex(c *RequestContext) ResponseData {
type fishbowlTmpl struct {
Date string
Title string
Url string
}
var fishbowlTmpls []fishbowlTmpl
for _, f := range fishbowls {
fishbowlTmpls = append(fishbowlTmpls, fishbowlTmpl{
Date: fmt.Sprintf("%s %d", f.Month.String(), f.Year),
Title: f.Title,
Url: hmnurl.BuildFishbowl(f.Slug),
})
}
var res ResponseData
err := res.WriteTemplate("fishbowl_index.html", getBaseData(c, "Fishbowls", nil), c.Perf)
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render fishbowl index page"))
}
return res
}
func Fishbowl(c *RequestContext) ResponseData {
slug := c.PathParams["slug"]
var info fishbowlInfo
// Only serve up valid fishbowls (with content)
exists := false
for _, fishbowl := range fishbowls {
if fishbowl.Slug == slug && fishbowl.ContentsPath != "" {
exists = true
info = fishbowl
}
}
if !exists {
return FourOhFour(c)
}
// Ensure trailing slash (it matters for relative URLs in the HTML)
if !strings.HasSuffix(c.URL().Path, "/") {
return c.Redirect(c.URL().Path+"/", http.StatusFound)
}
type FishbowlData struct {
templates.BaseData
Slug string
Info fishbowlInfo
Contents template.HTML
}
contentsFile := utils.Must1(fishbowlFS.Open(info.ContentsPath))
tmpl := FishbowlData{
BaseData: getBaseData(c, info.Title, []templates.Breadcrumb{
{Name: "Fishbowls", Url: hmnurl.BuildFishbowlIndex()},
{Name: info.Title, Url: hmnurl.BuildFishbowl(slug)},
}),
Slug: slug,
Info: info,
Contents: template.HTML(utils.Must1(io.ReadAll(contentsFile))),
}
var res ResponseData
err := res.WriteTemplate("fishbowl.html", tmpl, c.Perf)
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render fishbowl index page"))
}
return res
}
func FishbowlFiles(c *RequestContext) ResponseData {
var res ResponseData
fishbowlHTTPFS.ServeHTTP(&res, c.Req)
AddCORSHeaders(c, &res)
return res
}