diff options
Diffstat (limited to 'internal/builder/template.go')
-rw-r--r-- | internal/builder/template.go | 172 |
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 +} |