about summary refs log tree commit diff stats
path: root/internal/builder/template.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/builder/template.go')
-rw-r--r--internal/builder/template.go172
1 files changed, 172 insertions, 0 deletions
diff --git a/internal/builder/template.go b/internal/builder/template.go
new file mode 100644
index 0000000..9f019df
--- /dev/null
+++ b/internal/builder/template.go
@@ -0,0 +1,172 @@
+package builder
+
+import (
+	"bytes"
+	"encoding/xml"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"text/template"
+
+	"go.alanpearce.eu/website/internal/atom"
+	"go.alanpearce.eu/website/internal/config"
+	"go.alanpearce.eu/website/internal/content"
+
+	"github.com/PuerkitoBio/goquery"
+	"github.com/antchfx/xmlquery"
+	"github.com/antchfx/xpath"
+	"gitlab.com/tozd/go/errors"
+)
+
+var (
+	css   string
+	nsMap = map[string]string{
+		"xsl":   "http://www.w3.org/1999/XSL/Transform",
+		"atom":  "http://www.w3.org/2005/Atom",
+		"xhtml": "http://www.w3.org/1999/xhtml",
+	}
+)
+
+func loadCSS(source string) {
+	bytes, err := os.ReadFile(filepath.Join(source, "templates/style.css"))
+	if err != nil {
+		panic(err)
+	}
+	css = string(bytes)
+}
+
+type QuerySelection struct {
+	*goquery.Selection
+}
+
+type QueryDocument struct {
+	*goquery.Document
+}
+
+func NewDocumentFromReader(r io.Reader) (*QueryDocument, error) {
+	doc, err := goquery.NewDocumentFromReader(r)
+
+	return &QueryDocument{doc}, errors.Wrap(err, "could not create query document")
+}
+
+func (q *QueryDocument) Find(selector string) *QuerySelection {
+	return &QuerySelection{q.Document.Find(selector)}
+}
+
+func renderRobotsTXT(source string, config *config.Config) (io.Reader, error) {
+	r, w := io.Pipe()
+	tpl, err := template.ParseFiles(filepath.Join(source, "templates/robots.tmpl"))
+	if err != nil {
+		return nil, err
+	}
+	go func() {
+		err = tpl.Execute(w, map[string]interface{}{
+			"BaseURL": config.BaseURL,
+		})
+		if err != nil {
+			w.CloseWithError(err)
+		}
+		w.Close()
+	}()
+
+	return r, nil
+}
+
+func renderFeed(
+	title string,
+	config *config.Config,
+	posts []content.Post,
+	specific string,
+) (io.WriterTo, error) {
+	buf := &bytes.Buffer{}
+	datetime := posts[0].Date.UTC()
+
+	buf.WriteString(xml.Header)
+	err := atom.LinkXSL(buf, "/feed-styles.xsl")
+	if err != nil {
+		return nil, err
+	}
+	feed := &atom.Feed{
+		Title:   title,
+		Link:    atom.MakeLink(config.BaseURL.URL),
+		ID:      atom.MakeTagURI(config, specific),
+		Updated: datetime,
+		Entries: make([]*atom.FeedEntry, len(posts)),
+	}
+
+	for i, post := range posts {
+		feed.Entries[i] = &atom.FeedEntry{
+			Title:   post.Title,
+			Link:    atom.MakeLink(config.BaseURL.JoinPath(post.URL)),
+			ID:      atom.MakeTagURI(config, post.Basename),
+			Updated: post.Date.UTC(),
+			Summary: post.Description,
+			Author:  config.Title,
+			Content: atom.FeedContent{
+				Content: post.Content,
+				Type:    "html",
+			},
+		}
+	}
+	enc := xml.NewEncoder(buf)
+	err = enc.Encode(feed)
+	if err != nil {
+		return nil, err
+	}
+
+	return buf, nil
+}
+
+func renderFeedStyles(source string) (*strings.Reader, error) {
+	tpl, err := template.ParseFiles(filepath.Join(source, "templates/feed-styles.xsl"))
+	if err != nil {
+		return nil, err
+	}
+
+	esc := &strings.Builder{}
+	err = xml.EscapeText(esc, []byte(css))
+	if err != nil {
+		return nil, err
+	}
+
+	w := &strings.Builder{}
+	err = tpl.Execute(w, map[string]interface{}{
+		"css": esc.String(),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.NewReader(w.String()), nil
+}
+
+func getFeedStylesHash(r io.Reader) (string, error) {
+	doc, err := xmlquery.Parse(r)
+	if err != nil {
+		return "", err
+	}
+	expr, err := xpath.CompileWithNS("//xhtml:style", nsMap)
+	if err != nil {
+		return "", errors.Wrap(err, "could not parse XPath")
+	}
+	style := xmlquery.QuerySelector(doc, expr)
+
+	return hash(style.InnerText()), nil
+}
+
+func getHTMLStyleHash(filenames ...string) (string, error) {
+	fn := filepath.Join(filenames...)
+	f, err := os.Open(fn)
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+	doc, err := NewDocumentFromReader(f)
+	if err != nil {
+		return "", err
+	}
+	html := doc.Find("head > style").Text()
+
+	return hash(html), nil
+}