198 lines
5.3 KiB
Go
198 lines
5.3 KiB
Go
|
package website
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"html/template"
|
||
|
"io/fs"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
|
||
|
"git.handmade.network/hmn/hmn/src/config"
|
||
|
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||
|
"git.handmade.network/hmn/hmn/src/templates"
|
||
|
)
|
||
|
|
||
|
func CineraIndex(c *RequestContext) ResponseData {
|
||
|
topic := c.PathParams["topic"]
|
||
|
slug := c.CurrentProject.Slug
|
||
|
|
||
|
_, foundTopic := topicsForProject(slug, topic)
|
||
|
if foundTopic == "" {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
|
||
|
indexPath := path.Join(config.Config.EpisodeGuide.CineraOutputPath, slug, foundTopic, fmt.Sprintf("%s.index", foundTopic))
|
||
|
|
||
|
content, err := os.ReadFile(indexPath)
|
||
|
if err != nil {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
|
||
|
var res ResponseData
|
||
|
res.Write(content)
|
||
|
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
var episodeListRegex = regexp.MustCompile(`(?ism)^.*<body>(?P<guide>.*)</body>.*$`)
|
||
|
|
||
|
type EpisodeListData struct {
|
||
|
templates.BaseData
|
||
|
Content template.HTML
|
||
|
CurrentTopic string
|
||
|
Topics []templates.Link
|
||
|
}
|
||
|
|
||
|
func EpisodeList(c *RequestContext) ResponseData {
|
||
|
topic := c.PathParams["topic"]
|
||
|
slug := c.CurrentProject.Slug
|
||
|
|
||
|
defaultTopic, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug]
|
||
|
|
||
|
if !hasEpisodeGuide {
|
||
|
return c.Redirect(hmnurl.BuildProjectHomepage(slug), http.StatusSeeOther)
|
||
|
}
|
||
|
|
||
|
if topic == "" {
|
||
|
return c.Redirect(hmnurl.BuildEpisodeList(slug, defaultTopic), http.StatusSeeOther)
|
||
|
}
|
||
|
|
||
|
allTopics, foundTopic := topicsForProject(slug, topic)
|
||
|
if foundTopic == "" {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
|
||
|
htmlPath := path.Join(config.Config.EpisodeGuide.CineraOutputPath, slug, foundTopic, "index.html")
|
||
|
|
||
|
htmlContent, err := os.ReadFile(htmlPath)
|
||
|
if err != nil {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
matches := episodeListRegex.FindStringSubmatch(string(htmlContent))
|
||
|
if matches == nil || matches[episodeListRegex.SubexpIndex("guide")] == "" {
|
||
|
c.Logger.Error().Str("Filename", htmlPath).Msg("Episode guide index.html can't be parsed.")
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
guide := matches[episodeListRegex.SubexpIndex("guide")]
|
||
|
|
||
|
var topicLinks []templates.Link
|
||
|
for _, t := range allTopics {
|
||
|
url := ""
|
||
|
if t != foundTopic {
|
||
|
url = hmnurl.BuildEpisodeList(slug, t)
|
||
|
}
|
||
|
topicLinks = append(topicLinks, templates.Link{LinkText: t, Url: url})
|
||
|
}
|
||
|
|
||
|
var res ResponseData
|
||
|
baseData := getBaseData(c)
|
||
|
res.MustWriteTemplate("episode_list.html", EpisodeListData{
|
||
|
BaseData: baseData,
|
||
|
Content: template.HTML(guide),
|
||
|
CurrentTopic: foundTopic,
|
||
|
Topics: topicLinks,
|
||
|
}, c.Perf)
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
var episodeTitleRegex = regexp.MustCompile(`(?ism)<span class="episode_name">(?P<title>.*?)</span>`)
|
||
|
var episodeContentRegex = regexp.MustCompile(`(?ism)<body>(?P<content>.*)</body>`)
|
||
|
|
||
|
type EpisodeData struct {
|
||
|
templates.BaseData
|
||
|
Content template.HTML
|
||
|
}
|
||
|
|
||
|
func Episode(c *RequestContext) ResponseData {
|
||
|
episode := c.PathParams["episode"]
|
||
|
topic := c.PathParams["topic"]
|
||
|
slug := c.CurrentProject.Slug
|
||
|
|
||
|
_, hasEpisodeGuide := config.Config.EpisodeGuide.Projects[slug]
|
||
|
|
||
|
if !hasEpisodeGuide {
|
||
|
return c.Redirect(hmnurl.BuildProjectHomepage(slug), http.StatusSeeOther)
|
||
|
}
|
||
|
|
||
|
_, foundTopic := topicsForProject(slug, topic)
|
||
|
if foundTopic == "" {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
|
||
|
foundEpisode := findEpisode(slug, foundTopic, episode)
|
||
|
if foundEpisode == "" {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
|
||
|
htmlPath := path.Join(config.Config.EpisodeGuide.CineraOutputPath, slug, foundTopic, "episode", foundTopic, foundEpisode, "index.html")
|
||
|
htmlContent, err := os.ReadFile(htmlPath)
|
||
|
if err != nil {
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
|
||
|
titleMatches := episodeTitleRegex.FindStringSubmatch(string(htmlContent))
|
||
|
title := fmt.Sprintf("%s Episode Guide", c.CurrentProject.Name)
|
||
|
if titleMatches != nil && titleMatches[episodeTitleRegex.SubexpIndex("title")] != "" {
|
||
|
title = fmt.Sprintf("%s | %s", titleMatches[episodeTitleRegex.SubexpIndex("title")], title)
|
||
|
}
|
||
|
|
||
|
contentMatches := episodeContentRegex.FindStringSubmatch(string(htmlContent))
|
||
|
if contentMatches == nil || contentMatches[episodeContentRegex.SubexpIndex("content")] == "" {
|
||
|
c.Logger.Error().Str("filename", htmlPath).Msg("Episode file can't be parsed.")
|
||
|
return FourOhFour(c)
|
||
|
}
|
||
|
content := contentMatches[episodeContentRegex.SubexpIndex("content")]
|
||
|
|
||
|
var res ResponseData
|
||
|
baseData := getBaseData(c)
|
||
|
baseData.Title = title
|
||
|
res.MustWriteTemplate("episode.html", EpisodeData{
|
||
|
BaseData: baseData,
|
||
|
Content: template.HTML(content),
|
||
|
}, c.Perf)
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func topicsForProject(projectSlug string, requestedTopic string) ([]string, string) {
|
||
|
searchPath := path.Join(config.Config.EpisodeGuide.CineraOutputPath, projectSlug)
|
||
|
entries, err := fs.ReadDir(os.DirFS(searchPath), ".")
|
||
|
if err != nil {
|
||
|
return nil, ""
|
||
|
}
|
||
|
var allTopics []string
|
||
|
foundTopic := ""
|
||
|
for _, entry := range entries {
|
||
|
if entry.IsDir() {
|
||
|
t := entry.Name()
|
||
|
allTopics = append(allTopics, t)
|
||
|
if strings.ToLower(t) == strings.ToLower(requestedTopic) {
|
||
|
foundTopic = t
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return allTopics, foundTopic
|
||
|
}
|
||
|
|
||
|
// NOTE(asaf): Assuming topic is valid. Please verify before calling.
|
||
|
func findEpisode(projectSlug string, topic string, requestedEpisode string) string {
|
||
|
searchPath := path.Join(config.Config.EpisodeGuide.CineraOutputPath, projectSlug, topic, "episode", topic) // NOTE(asaf): Yes. We have `topic` twice in the path.
|
||
|
entries, err := fs.ReadDir(os.DirFS(searchPath), ".")
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
for _, entry := range entries {
|
||
|
if entry.IsDir() {
|
||
|
episode := entry.Name()
|
||
|
if strings.ToLower(episode) == strings.ToLower(requestedEpisode) {
|
||
|
return episode
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ""
|
||
|
}
|