From b27c96688785372787eb5c3c71a32767fab71ac4 Mon Sep 17 00:00:00 2001
From: Alan Pearce
Date: Tue, 18 Jun 2024 20:13:26 +0200
Subject: split content and sitemap code from builder
---
internal/builder/builder.go | 35 ++++-------
internal/builder/homepage.templ | 5 +-
internal/builder/list.templ | 11 ++--
internal/builder/post.templ | 3 +-
internal/builder/posts.go | 129 ----------------------------------------
internal/builder/sitemap.go | 29 ---------
internal/builder/template.go | 3 +-
internal/content/posts.go | 129 ++++++++++++++++++++++++++++++++++++++++
internal/sitemap/sitemap.go | 35 +++++++++++
9 files changed, 189 insertions(+), 190 deletions(-)
delete mode 100644 internal/builder/posts.go
delete mode 100644 internal/builder/sitemap.go
create mode 100644 internal/content/posts.go
create mode 100644 internal/sitemap/sitemap.go
diff --git a/internal/builder/builder.go b/internal/builder/builder.go
index 7b6af0b..141bd6f 100644
--- a/internal/builder/builder.go
+++ b/internal/builder/builder.go
@@ -11,13 +11,14 @@ import (
"time"
"website/internal/config"
+ "website/internal/content"
"website/internal/log"
+ "website/internal/sitemap"
"github.com/a-h/templ"
mapset "github.com/deckarep/golang-set/v2"
cp "github.com/otiai10/copy"
"github.com/pkg/errors"
- "github.com/snabb/sitemap"
)
type IOConfig struct {
@@ -108,12 +109,12 @@ func build(outDir string, config config.Config) (*Result, error) {
return nil, errors.WithMessage(err, "could not create post output directory")
}
log.Debug("reading posts")
- posts, tags, err := readPosts("content", "post", publicDir)
+ posts, tags, err := content.ReadPosts("content", "post", publicDir)
if err != nil {
return nil, err
}
- sm := NewSitemap(config)
+ sitemap := sitemap.New(config)
lastMod := time.Now()
if len(posts) > 0 {
lastMod = posts[0].Date
@@ -124,10 +125,7 @@ func build(outDir string, config config.Config) (*Result, error) {
return nil, errors.WithMessage(err, "could not create directory for post")
}
log.Debug("rendering post", "post", post.Basename)
- sm.Add(&sitemap.URL{
- Loc: post.URL,
- LastMod: &post.Date,
- })
+ sitemap.AddPath(post.URL, post.Date)
if err := renderToFile(postPage(config, post), post.Output); err != nil {
return nil, err
}
@@ -140,13 +138,10 @@ func build(outDir string, config config.Config) (*Result, error) {
if err := renderToFile(tagsPage(config, "tags", mapset.Sorted(tags), "/tags"), publicDir, "tags", "index.html"); err != nil {
return nil, err
}
- sm.Add(&sitemap.URL{
- Loc: "/tags/",
- LastMod: &lastMod,
- })
+ sitemap.AddPath("/tags/", lastMod)
for _, tag := range tags.ToSlice() {
- matchingPosts := []Post{}
+ matchingPosts := []content.Post{}
for _, post := range posts {
if slices.Contains(post.Taxonomies.Tags, tag) {
matchingPosts = append(matchingPosts, post)
@@ -160,10 +155,7 @@ func build(outDir string, config config.Config) (*Result, error) {
if err := renderToFile(tagPage(config, tag, matchingPosts, url), publicDir, "tags", tag, "index.html"); err != nil {
return nil, err
}
- sm.Add(&sitemap.URL{
- Loc: url,
- LastMod: &matchingPosts[0].Date,
- })
+ sitemap.AddPath(url, matchingPosts[0].Date)
log.Debug("rendering tags feed", "tag", tag)
feed, err := renderFeed(
@@ -184,10 +176,7 @@ func build(outDir string, config config.Config) (*Result, error) {
if err := renderToFile(listPage(config, posts, "/post"), publicDir, "post", "index.html"); err != nil {
return nil, err
}
- sm.Add(&sitemap.URL{
- Loc: "/post/",
- LastMod: &lastMod,
- })
+ sitemap.AddPath("/post/", lastMod)
log.Debug("rendering feed")
feed, err := renderFeed(config.Title, config, posts, "feed")
@@ -223,9 +212,7 @@ func build(outDir string, config config.Config) (*Result, error) {
// it would be nice to set LastMod here, but using the latest post
// date would be wrong as the homepage has its own content file
// without a date, which could be newer
- sm.Add(&sitemap.URL{
- Loc: "/",
- })
+ sitemap.AddPath("/", time.Time{})
h, _ = getHTMLStyleHash(publicDir, "index.html")
r.Hashes = append(r.Hashes, h)
@@ -235,7 +222,7 @@ func build(outDir string, config config.Config) (*Result, error) {
}
log.Debug("rendering sitemap")
- if err := writerToFile(sm, publicDir, "sitemap.xml"); err != nil {
+ if err := writerToFile(sitemap, publicDir, "sitemap.xml"); err != nil {
return nil, err
}
diff --git a/internal/builder/homepage.templ b/internal/builder/homepage.templ
index 9897b5d..1df401b 100644
--- a/internal/builder/homepage.templ
+++ b/internal/builder/homepage.templ
@@ -3,11 +3,12 @@ package builder
import (
"website/internal/config"
"path"
+ "website/internal/content"
)
func getContent(filename string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
- _, index, err := getPost(path.Join("content", filename))
+ _, index, err := content.GetPost(path.Join("content", filename))
if err != nil {
return err
}
@@ -17,7 +18,7 @@ func getContent(filename string) templ.Component {
})
}
-templ homepage(config config.Config, posts []Post) {
+templ homepage(config config.Config, posts []content.Post) {
@page(config, PageSettings{
Title: config.Title,
TitleAttrs: templ.Attributes{
diff --git a/internal/builder/list.templ b/internal/builder/list.templ
index 48563ed..24cafe8 100644
--- a/internal/builder/list.templ
+++ b/internal/builder/list.templ
@@ -1,8 +1,11 @@
package builder
-import "website/internal/config"
+import (
+ "website/internal/config"
+ "website/internal/content"
+)
-templ tagPage(config config.Config, tag string, posts []Post, path string) {
+templ tagPage(config config.Config, tag string, posts []content.Post, path string) {
@page(config, PageSettings{
Title: tag,
Path: path,
@@ -21,7 +24,7 @@ templ tagPage(config config.Config, tag string, posts []Post, path string) {
}
}
-templ listPage(config config.Config, posts []Post, path string) {
+templ listPage(config config.Config, posts []content.Post, path string) {
@page(config, PageSettings{
Title: config.Title,
TitleAttrs: templ.Attributes{
@@ -34,7 +37,7 @@ templ listPage(config config.Config, posts []Post, path string) {
}
}
-templ list(posts []Post) {
+templ list(posts []content.Post) {
for _, post := range posts {
-
diff --git a/internal/builder/post.templ b/internal/builder/post.templ
index 740c5aa..6f12e5c 100644
--- a/internal/builder/post.templ
+++ b/internal/builder/post.templ
@@ -3,6 +3,7 @@ package builder
import (
"time"
"website/internal/config"
+ "website/internal/content"
)
func Unsafe(html string) templ.Component {
@@ -18,7 +19,7 @@ templ postDate(d time.Time) {
}
-templ postPage(config config.Config, post Post) {
+templ postPage(config config.Config, post content.Post) {
@page(config, PageSettings{
Title: post.Title,
TitleAttrs: templ.Attributes{
diff --git a/internal/builder/posts.go b/internal/builder/posts.go
deleted file mode 100644
index deae3e8..0000000
--- a/internal/builder/posts.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package builder
-
-import (
- "bytes"
- "os"
- "path"
- "path/filepath"
- "slices"
- "strings"
- "time"
- "website/internal/log"
-
- "github.com/adrg/frontmatter"
- mapset "github.com/deckarep/golang-set/v2"
- "github.com/pkg/errors"
- fences "github.com/stefanfritsch/goldmark-fences"
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/extension"
- htmlrenderer "github.com/yuin/goldmark/renderer/html"
-)
-
-type PostMatter struct {
- Date time.Time `toml:"date"`
- Description string `toml:"description"`
- Title string `toml:"title"`
- Taxonomies struct {
- Tags []string `toml:"tags"`
- } `toml:"taxonomies"`
-}
-
-type Post struct {
- Input string
- Output string
- Basename string
- URL string
- Content string
- PostMatter
-}
-
-type Tags mapset.Set[string]
-
-var markdown = goldmark.New(
- goldmark.WithRendererOptions(
- htmlrenderer.WithUnsafe(),
- ),
- goldmark.WithExtensions(
- extension.GFM,
- extension.Footnote,
- extension.Typographer,
- &fences.Extender{},
- ),
-)
-
-func getPost(filename string) (*PostMatter, []byte, error) {
- matter := PostMatter{}
- content, err := os.Open(filename)
- if err != nil {
- return nil, nil, errors.WithMessagef(err, "could not open post %s", filename)
- }
- defer content.Close()
- rest, err := frontmatter.MustParse(content, &matter)
- if err != nil {
- return nil, nil, errors.WithMessagef(
- err,
- "could not parse front matter of post %s",
- filename,
- )
- }
-
- return &matter, rest, nil
-}
-
-func renderMarkdown(content []byte) (string, error) {
- var buf bytes.Buffer
- if err := markdown.Convert(content, &buf); err != nil {
- return "", errors.WithMessage(err, "could not convert markdown content")
- }
-
- return buf.String(), nil
-}
-
-func readPosts(root string, inputDir string, outputDir string) ([]Post, Tags, error) {
- tags := mapset.NewSet[string]()
- posts := []Post{}
- subdir := filepath.Join(root, inputDir)
- files, err := os.ReadDir(subdir)
- if err != nil {
- return nil, nil, errors.WithMessagef(err, "could not read post directory %s", subdir)
- }
- outputReplacer := strings.NewReplacer(root, outputDir, ".md", "/index.html")
- urlReplacer := strings.NewReplacer(root, "", ".md", "/")
- for _, f := range files {
- pathFromRoot := filepath.Join(subdir, f.Name())
- if !f.IsDir() && path.Ext(pathFromRoot) == ".md" {
- output := outputReplacer.Replace(pathFromRoot)
- url := urlReplacer.Replace(pathFromRoot)
- log.Debug("reading post", "post", pathFromRoot)
- matter, content, err := getPost(pathFromRoot)
- if err != nil {
- return nil, nil, err
- }
-
- for _, tag := range matter.Taxonomies.Tags {
- tags.Add(strings.ToLower(tag))
- }
-
- log.Debug("rendering markdown in post", "post", pathFromRoot)
- html, err := renderMarkdown(content)
- if err != nil {
- return nil, nil, err
- }
- post := Post{
- Input: pathFromRoot,
- Output: output,
- Basename: filepath.Base(url),
- URL: url,
- PostMatter: *matter,
- Content: html,
- }
-
- posts = append(posts, post)
- }
- }
- slices.SortFunc(posts, func(a, b Post) int {
- return b.Date.Compare(a.Date)
- })
-
- return posts, tags, nil
-}
diff --git a/internal/builder/sitemap.go b/internal/builder/sitemap.go
deleted file mode 100644
index 81e3a31..0000000
--- a/internal/builder/sitemap.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package builder
-
-import (
- "io"
- "website/internal/config"
-
- "github.com/snabb/sitemap"
-)
-
-type Sitemap struct {
- config *config.Config
- Sitemap *sitemap.Sitemap
-}
-
-func NewSitemap(cfg config.Config) *Sitemap {
- return &Sitemap{
- config: &cfg,
- Sitemap: sitemap.New(),
- }
-}
-
-func (s *Sitemap) Add(u *sitemap.URL) {
- u.Loc = s.config.BaseURL.JoinPath(u.Loc).String()
- s.Sitemap.Add(u)
-}
-
-func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
- return s.Sitemap.WriteTo(w)
-}
diff --git a/internal/builder/template.go b/internal/builder/template.go
index 376e48a..9b1d9bd 100644
--- a/internal/builder/template.go
+++ b/internal/builder/template.go
@@ -9,6 +9,7 @@ import (
"text/template"
"website/internal/atom"
"website/internal/config"
+ "website/internal/content"
"website/internal/log"
"github.com/PuerkitoBio/goquery"
@@ -89,7 +90,7 @@ func renderRobotsTXT(config config.Config) (io.Reader, error) {
func renderFeed(
title string,
config config.Config,
- posts []Post,
+ posts []content.Post,
specific string,
) (io.Reader, error) {
reader, err := loadTemplate("templates/feed.xml")
diff --git a/internal/content/posts.go b/internal/content/posts.go
new file mode 100644
index 0000000..c3511cf
--- /dev/null
+++ b/internal/content/posts.go
@@ -0,0 +1,129 @@
+package content
+
+import (
+ "bytes"
+ "os"
+ "path"
+ "path/filepath"
+ "slices"
+ "strings"
+ "time"
+ "website/internal/log"
+
+ "github.com/adrg/frontmatter"
+ mapset "github.com/deckarep/golang-set/v2"
+ "github.com/pkg/errors"
+ fences "github.com/stefanfritsch/goldmark-fences"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/extension"
+ htmlrenderer "github.com/yuin/goldmark/renderer/html"
+)
+
+type PostMatter struct {
+ Date time.Time `toml:"date"`
+ Description string `toml:"description"`
+ Title string `toml:"title"`
+ Taxonomies struct {
+ Tags []string `toml:"tags"`
+ } `toml:"taxonomies"`
+}
+
+type Post struct {
+ Input string
+ Output string
+ Basename string
+ URL string
+ Content string
+ PostMatter
+}
+
+type Tags mapset.Set[string]
+
+var markdown = goldmark.New(
+ goldmark.WithRendererOptions(
+ htmlrenderer.WithUnsafe(),
+ ),
+ goldmark.WithExtensions(
+ extension.GFM,
+ extension.Footnote,
+ extension.Typographer,
+ &fences.Extender{},
+ ),
+)
+
+func GetPost(filename string) (*PostMatter, []byte, error) {
+ matter := PostMatter{}
+ content, err := os.Open(filename)
+ if err != nil {
+ return nil, nil, errors.WithMessagef(err, "could not open post %s", filename)
+ }
+ defer content.Close()
+ rest, err := frontmatter.MustParse(content, &matter)
+ if err != nil {
+ return nil, nil, errors.WithMessagef(
+ err,
+ "could not parse front matter of post %s",
+ filename,
+ )
+ }
+
+ return &matter, rest, nil
+}
+
+func renderMarkdown(content []byte) (string, error) {
+ var buf bytes.Buffer
+ if err := markdown.Convert(content, &buf); err != nil {
+ return "", errors.WithMessage(err, "could not convert markdown content")
+ }
+
+ return buf.String(), nil
+}
+
+func ReadPosts(root string, inputDir string, outputDir string) ([]Post, Tags, error) {
+ tags := mapset.NewSet[string]()
+ posts := []Post{}
+ subdir := filepath.Join(root, inputDir)
+ files, err := os.ReadDir(subdir)
+ if err != nil {
+ return nil, nil, errors.WithMessagef(err, "could not read post directory %s", subdir)
+ }
+ outputReplacer := strings.NewReplacer(root, outputDir, ".md", "/index.html")
+ urlReplacer := strings.NewReplacer(root, "", ".md", "/")
+ for _, f := range files {
+ pathFromRoot := filepath.Join(subdir, f.Name())
+ if !f.IsDir() && path.Ext(pathFromRoot) == ".md" {
+ output := outputReplacer.Replace(pathFromRoot)
+ url := urlReplacer.Replace(pathFromRoot)
+ log.Debug("reading post", "post", pathFromRoot)
+ matter, content, err := GetPost(pathFromRoot)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, tag := range matter.Taxonomies.Tags {
+ tags.Add(strings.ToLower(tag))
+ }
+
+ log.Debug("rendering markdown in post", "post", pathFromRoot)
+ html, err := renderMarkdown(content)
+ if err != nil {
+ return nil, nil, err
+ }
+ post := Post{
+ Input: pathFromRoot,
+ Output: output,
+ Basename: filepath.Base(url),
+ URL: url,
+ PostMatter: *matter,
+ Content: html,
+ }
+
+ posts = append(posts, post)
+ }
+ }
+ slices.SortFunc(posts, func(a, b Post) int {
+ return b.Date.Compare(a.Date)
+ })
+
+ return posts, tags, nil
+}
diff --git a/internal/sitemap/sitemap.go b/internal/sitemap/sitemap.go
new file mode 100644
index 0000000..a38e277
--- /dev/null
+++ b/internal/sitemap/sitemap.go
@@ -0,0 +1,35 @@
+package sitemap
+
+import (
+ "io"
+ "time"
+ "website/internal/config"
+
+ "github.com/snabb/sitemap"
+)
+
+type Sitemap struct {
+ config *config.Config
+ Sitemap *sitemap.Sitemap
+}
+
+func New(cfg config.Config) *Sitemap {
+ return &Sitemap{
+ config: &cfg,
+ Sitemap: sitemap.New(),
+ }
+}
+
+func (s *Sitemap) AddPath(path string, lastMod time.Time) {
+ url := &sitemap.URL{
+ Loc: s.config.BaseURL.JoinPath(path).String(),
+ }
+ if !lastMod.IsZero() {
+ url.LastMod = &lastMod
+ }
+ s.Sitemap.Add(url)
+}
+
+func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
+ return s.Sitemap.WriteTo(w)
+}
--
cgit 1.4.1