about summary refs log tree commit diff stats
path: root/cmd/build/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/build/main.go')
-rw-r--r--cmd/build/main.go123
1 files changed, 71 insertions, 52 deletions
diff --git a/cmd/build/main.go b/cmd/build/main.go
index 68bba43..81d31e9 100644
--- a/cmd/build/main.go
+++ b/cmd/build/main.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"encoding/xml"
 	"fmt"
+	"io"
 	"io/fs"
 	"log"
 	"log/slog"
@@ -179,10 +180,19 @@ func layout(filename string, config Config, pageTitle string) (*goquery.Document
 	return doc, nil
 }
 
-func renderPost(post Post, config Config) (string, error) {
+func renderHTML(doc *goquery.Document) io.Reader {
+	r, w := io.Pipe()
+	go func() {
+		goquery.Render(w, doc.Children())
+		defer w.Close()
+	}()
+	return r
+}
+
+func renderPost(post Post, config Config) (r io.Reader, err error) {
 	doc, err := layout("templates/post.html", config, post.PostMatter.Title)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	doc.Find(".title").AddClass("p-author h-card").SetAttr("rel", "author")
 	doc.Find(".h-entry .dt-published").
@@ -199,13 +209,14 @@ func renderPost(post Post, config Config) (string, error) {
 		cat.Find(".p-category").SetAttr("href", fmt.Sprintf("/tags/%s/", tag)).SetText("#" + tag)
 		categories.AppendSelection(cat)
 	}
-	return doc.Html()
+
+	return renderHTML(doc), nil
 }
 
-func renderTags(tags Tags, config Config) (string, error) {
+func renderTags(tags Tags, config Config) (io.Reader, error) {
 	doc, err := layout("templates/tags.html", config, config.Title)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	tagList := doc.Find(".tags")
 	tpl := doc.Find(".h-feed")
@@ -215,10 +226,10 @@ func renderTags(tags Tags, config Config) (string, error) {
 		li.Find("a").SetAttr("href", fmt.Sprintf("/tags/%s/", tag)).SetText("#" + tag)
 		tagList.AppendSelection(li)
 	}
-	return doc.Html()
+	return renderHTML(doc), nil
 }
 
-func renderListPage(tag string, config Config, posts []Post) (string, error) {
+func renderListPage(tag string, config Config, posts []Post) (io.Reader, error) {
 	var title string
 	if len(tag) > 0 {
 		title = tag
@@ -227,7 +238,7 @@ func renderListPage(tag string, config Config, posts []Post) (string, error) {
 	}
 	doc, err := layout("templates/list.html", config, title)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	feed := doc.Find(".h-feed")
 	tpl := feed.Find(".h-entry")
@@ -249,17 +260,17 @@ func renderListPage(tag string, config Config, posts []Post) (string, error) {
 		feed.AppendSelection(entry)
 	}
 
-	return doc.Html()
+	return renderHTML(doc), nil
 }
 
-func renderHomepage(config Config, posts []Post) (string, error) {
+func renderHomepage(config Config, posts []Post) (io.Reader, error) {
 	_, index, err := getPost("content/_index.md")
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	doc, err := layout("templates/homepage.html", config, config.Title)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	doc.Find("body").AddClass("h-card")
 	doc.Find(".title").AddClass("p-name u-url")
@@ -267,7 +278,7 @@ func renderHomepage(config Config, posts []Post) (string, error) {
 
 	md := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()))
 	if err := md.Convert(*index, &buf); err != nil {
-		return "", err
+		return nil, err
 	}
 	doc.Find("#content").SetHtml(buf.String())
 
@@ -300,21 +311,21 @@ func renderHomepage(config Config, posts []Post) (string, error) {
 		elsewhere.AppendSelection(el)
 	}
 
-	return doc.Html()
+	return renderHTML(doc), nil
 }
 
-func render404(config Config) (string, error) {
+func render404(config Config) (io.Reader, error) {
 	doc, err := layout("templates/404.html", config, "404 Not Found")
 	if err != nil {
-		return "", err
+		return nil, err
 	}
-	return doc.Html()
+	return renderHTML(doc), nil
 }
 
-func renderFeed(title string, config Config, posts []Post, specific string) (string, error) {
+func renderFeed(title string, config Config, posts []Post, specific string) (io.Reader, error) {
 	reader, err := os.Open("templates/feed.xml")
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	defer reader.Close()
 	doc, err := xmlquery.Parse(reader)
@@ -330,7 +341,7 @@ func renderFeed(title string, config Config, posts []Post, specific string) (str
 	for _, post := range posts {
 		fullURL, err := url.JoinPath(config.BaseURL, post.URL)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 		text, err := xml.MarshalIndent(&FeedEntry{
 			Title:   post.Title,
@@ -345,7 +356,7 @@ func renderFeed(title string, config Config, posts []Post, specific string) (str
 			},
 		}, "  ", "    ")
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 		entry, err := xmlquery.ParseWithOptions(strings.NewReader(string(text)), xmlquery.ParserOptions{
 			Decoder: &xmlquery.DecoderOptions{
@@ -355,18 +366,18 @@ func renderFeed(title string, config Config, posts []Post, specific string) (str
 			},
 		})
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 		xmlquery.AddChild(feed, entry.SelectElement("entry"))
 	}
 
-	return doc.OutputXML(true), nil
+	return strings.NewReader(doc.OutputXML(true)), nil
 }
 
-func renderFeedStyles() (string, error) {
+func renderFeedStyles() (io.Reader, error) {
 	reader, err := os.Open("templates/feed-styles.xsl")
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	defer reader.Close()
 	nsMap := map[string]string{
@@ -377,22 +388,31 @@ func renderFeedStyles() (string, error) {
 	doc, err := xmlquery.Parse(reader)
 	expr, err := xpath.CompileWithNS("//xhtml:style", nsMap)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	style := xmlquery.QuerySelector(doc, expr)
 	css, err := os.ReadFile("templates/style.css")
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	xmlquery.AddChild(style, &xmlquery.Node{
 		Type: xmlquery.TextNode,
 		Data: string(css),
 	})
-	return doc.OutputXML(true), nil
+	return strings.NewReader(doc.OutputXML(true)), nil
 }
 
-type errFiler struct {
-	err error
+func outputToFile(output io.Reader, filename ...string) error {
+	file, err := os.OpenFile(path.Join(filename...), os.O_WRONLY|os.O_CREATE, 00644)
+	if err != nil {
+		return errors.WithMessage(err, "could not open output file")
+	}
+	defer file.Close()
+
+	if _, err := file.ReadFrom(output); err != nil {
+		return errors.WithMessage(err, "could not write output file")
+	}
+	return nil
 }
 
 func build() error {
@@ -418,21 +438,20 @@ func build() error {
 		if err != nil {
 			return errors.WithMessagef(err, "could not render post %s", post.Input)
 		}
-		if err := os.WriteFile(post.Output, []byte(output), 0755); err != nil {
-			return errors.WithMessage(err, "could not write output file")
+		if err := outputToFile(output, post.Output); err != nil {
+			return err
 		}
 	}
 
 	if err := os.MkdirAll(path.Join(outDir, "tags"), 0755); err != nil {
 		return errors.WithMessage(err, "could not create directory for tags")
 	}
-	tagsHtml, err := renderTags(tags, *config)
+	output, err := renderTags(tags, *config)
 	if err != nil {
 		return errors.WithMessage(err, "could not render tags")
 	}
-	err = os.WriteFile(path.Join(outDir, "tags", "index.html"), []byte(tagsHtml), 0644)
-	if err != nil {
-		return errors.WithMessage(err, "could not write output file")
+	if err := outputToFile(output, outDir, "tags", "index.html"); err != nil {
+		return err
 	}
 
 	for _, tag := range tags.ToSlice() {
@@ -445,60 +464,60 @@ func build() error {
 		if err := os.MkdirAll(path.Join(outDir, "tags", tag), 0755); err != nil {
 			return errors.WithMessage(err, "could not create directory")
 		}
-		tagPage, err := renderListPage(tag, *config, matchingPosts)
+		output, err := renderListPage(tag, *config, matchingPosts)
 		if err != nil {
 			return errors.WithMessage(err, "could not render tag page")
 		}
-		if err := os.WriteFile(path.Join(outDir, "tags", tag, "index.html"), []byte(tagPage), 0644); err != nil {
-			return errors.WithMessage(err, "could not write tag output file")
+		if err := outputToFile(output, outDir, "tags", tag, "index.html"); err != nil {
+			return err
 		}
 
-		feedPage, err := renderFeed(fmt.Sprintf("%s - %s", config.Title, tag), *config, matchingPosts, tag)
+		output, err = renderFeed(fmt.Sprintf("%s - %s", config.Title, tag), *config, matchingPosts, tag)
 		if err != nil {
 			return errors.WithMessage(err, "could not render tag feed page")
 		}
-		if err := os.WriteFile(path.Join(outDir, "tags", tag, "atom.xml"), []byte(feedPage), 0644); err != nil {
-			return errors.WithMessage(err, "could not write tag feed output file")
+		if err := outputToFile(output, outDir, "tags", tag, "atom.xml"); err != nil {
+			return err
 		}
 	}
 	listPage, err := renderListPage("", *config, posts)
 	if err != nil {
 		return errors.WithMessage(err, "could not render list page")
 	}
-	if err := os.WriteFile(path.Join(outDir, "post", "index.html"), []byte(listPage), 0644); err != nil {
-		return errors.WithMessage(err, "could not write list page output file")
+	if err := outputToFile(listPage, outDir, "post", "index.html"); err != nil {
+		return err
 	}
 
 	feed, err := renderFeed(config.Title, *config, posts, "feed")
 	if err != nil {
 		return errors.WithMessage(err, "could not render feed")
 	}
-	if err := os.WriteFile(path.Join(outDir, "atom.xml"), []byte(feed), 0644); err != nil {
-		return errors.WithMessage(err, "could not write feed")
+	if err := outputToFile(feed, outDir, "atom.xml"); err != nil {
+		return err
 	}
 
 	feedStyles, err := renderFeedStyles()
 	if err != nil {
 		return errors.WithMessage(err, "could not render feed styles")
 	}
-	if err := os.WriteFile(path.Join(outDir, "feed-styles.xsl"), []byte(feedStyles), 0644); err != nil {
-		return errors.WithMessage(err, "could not write feed styles")
+	if err := outputToFile(feedStyles, outDir, "feed-styles.xsl"); err != nil {
+		return err
 	}
 
 	homePage, err := renderHomepage(*config, posts)
 	if err != nil {
 		return errors.WithMessage(err, "could not render homepage")
 	}
-	if err := os.WriteFile(path.Join(outDir, "index.html"), []byte(homePage), 0644); err != nil {
-		return errors.WithMessage(err, "could not write homepage")
+	if err := outputToFile(homePage, outDir, "index.html"); err != nil {
+		return err
 	}
 
 	notFound, err := render404(*config)
 	if err != nil {
 		return errors.WithMessage(err, "could not render 404 page")
 	}
-	if err := os.WriteFile(path.Join(outDir, "404.html"), []byte(notFound), 0644); err != nil {
-		return errors.WithMessage(err, "could not write 404 file")
+	if err := outputToFile(notFound, outDir, "404.html"); err != nil {
+		return err
 	}
 
 	return nil