diff options
Diffstat (limited to 'internal/builder')
-rw-r--r-- | internal/builder/builder.go | 88 | ||||
-rw-r--r-- | internal/builder/template.go | 110 |
2 files changed, 85 insertions, 113 deletions
diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 4436151..b99d919 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -6,23 +6,24 @@ import ( "io" "os" "path" + "path/filepath" "slices" "time" - "website/internal/config" - "website/internal/content" - "website/internal/log" - "website/internal/sitemap" - "website/templates" + "go.alanpearce.eu/website/internal/config" + "go.alanpearce.eu/website/internal/content" + "go.alanpearce.eu/x/log" + "go.alanpearce.eu/website/internal/sitemap" + "go.alanpearce.eu/website/templates" "github.com/a-h/templ" mapset "github.com/deckarep/golang-set/v2" - "github.com/pkg/errors" + "gitlab.com/tozd/go/errors" ) type IOConfig struct { Source string `conf:"default:.,short:s,flag:src"` - Destination string `conf:"default:website,short:d,flag:dest"` + Destination string `conf:"default:public,short:d,flag:dest"` Development bool `conf:"default:false,flag:dev"` } @@ -86,30 +87,34 @@ func writerToFile(writer io.WriterTo, pathParts ...string) error { return nil } -func build(outDir string, config config.Config) (*Result, error) { +func joinSourcePath(src string) func(string) string { + return func(rel string) string { + return filepath.Join(src, rel) + } +} + +func build(ioConfig *IOConfig, config *config.Config, log *log.Logger) (*Result, error) { + outDir := ioConfig.Destination + joinSource := joinSourcePath(ioConfig.Source) log.Debug("output", "dir", outDir) r := &Result{ Hashes: make([]string, 0), } - privateDir := path.Join(outDir, "private") - if err := mkdirp(privateDir); err != nil { - return nil, errors.WithMessage(err, "could not create private directory") - } - publicDir := path.Join(outDir, "public") - if err := mkdirp(publicDir); err != nil { - return nil, errors.WithMessage(err, "could not create public directory") - } - err := copyRecursive("static", publicDir) + err := copyRecursive(joinSource("static"), outDir) if err != nil { return nil, errors.WithMessage(err, "could not copy static files") } - if err := mkdirp(publicDir, "post"); err != nil { + if err := mkdirp(outDir, "post"); err != nil { return nil, errors.WithMessage(err, "could not create post output directory") } log.Debug("reading posts") - posts, tags, err := content.ReadPosts("content", "post", publicDir) + posts, tags, err := content.ReadPosts(&content.Config{ + Root: joinSource("content"), + InputDir: "post", + OutputDir: outDir, + }, log.Named("content")) if err != nil { return nil, err } @@ -121,7 +126,7 @@ func build(outDir string, config config.Config) (*Result, error) { } for _, post := range posts { - if err := mkdirp(publicDir, "post", post.Basename); err != nil { + if err := mkdirp(outDir, "post", post.Basename); err != nil { return nil, errors.WithMessage(err, "could not create directory for post") } log.Debug("rendering post", "post", post.Basename) @@ -131,13 +136,13 @@ func build(outDir string, config config.Config) (*Result, error) { } } - if err := mkdirp(publicDir, "tags"); err != nil { + if err := mkdirp(outDir, "tags"); err != nil { return nil, errors.WithMessage(err, "could not create directory for tags") } log.Debug("rendering tags list") if err := renderToFile( templates.TagsPage(config, "tags", mapset.Sorted(tags), "/tags"), - publicDir, + outDir, "tags", "index.html", ); err != nil { @@ -152,14 +157,14 @@ func build(outDir string, config config.Config) (*Result, error) { matchingPosts = append(matchingPosts, post) } } - if err := mkdirp(publicDir, "tags", tag); err != nil { + if err := mkdirp(outDir, "tags", tag); err != nil { return nil, errors.WithMessage(err, "could not create directory") } log.Debug("rendering tags page", "tag", tag) url := "/tags/" + tag if err := renderToFile( templates.TagPage(config, tag, matchingPosts, url), - publicDir, + outDir, "tags", tag, "index.html", @@ -178,13 +183,13 @@ func build(outDir string, config config.Config) (*Result, error) { if err != nil { return nil, errors.WithMessage(err, "could not render tag feed page") } - if err := outputToFile(feed, publicDir, "tags", tag, "atom.xml"); err != nil { + if err := writerToFile(feed, outDir, "tags", tag, "atom.xml"); err != nil { return nil, err } } log.Debug("rendering list page") - if err := renderToFile(templates.ListPage(config, posts, "/post"), publicDir, "post", "index.html"); err != nil { + if err := renderToFile(templates.ListPage(config, posts, "/post"), outDir, "post", "index.html"); err != nil { return nil, err } sitemap.AddPath("/post/", lastMod) @@ -194,16 +199,16 @@ func build(outDir string, config config.Config) (*Result, error) { if err != nil { return nil, errors.WithMessage(err, "could not render feed") } - if err := outputToFile(feed, publicDir, "atom.xml"); err != nil { + if err := writerToFile(feed, outDir, "atom.xml"); err != nil { return nil, err } log.Debug("rendering feed styles") - feedStyles, err := renderFeedStyles() + feedStyles, err := renderFeedStyles(ioConfig.Source) if err != nil { return nil, errors.WithMessage(err, "could not render feed styles") } - if err := outputToFile(feedStyles, publicDir, "feed-styles.xsl"); err != nil { + if err := outputToFile(feedStyles, outDir, "feed-styles.xsl"); err != nil { return nil, err } _, err = feedStyles.Seek(0, 0) @@ -217,7 +222,7 @@ func build(outDir string, config config.Config) (*Result, error) { r.Hashes = append(r.Hashes, h) log.Debug("rendering homepage") - _, text, err := content.GetPost(path.Join("content", "index.md")) + _, text, err := content.GetPost(joinSource(filepath.Join("content", "index.md"))) if err != nil { return nil, err } @@ -225,43 +230,42 @@ func build(outDir string, config config.Config) (*Result, error) { if err != nil { return nil, err } - if err := renderToFile(templates.Homepage(config, posts, content), publicDir, "index.html"); err != nil { + if err := renderToFile(templates.Homepage(config, posts, content), outDir, "index.html"); err != nil { return nil, err } // 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 sitemap.AddPath("/", time.Time{}) - h, _ = getHTMLStyleHash(publicDir, "index.html") + h, _ = getHTMLStyleHash(outDir, "index.html") r.Hashes = append(r.Hashes, h) log.Debug("rendering sitemap") - if err := writerToFile(sitemap, publicDir, "sitemap.xml"); err != nil { + if err := writerToFile(sitemap, outDir, "sitemap.xml"); err != nil { return nil, err } log.Debug("rendering robots.txt") - rob, err := renderRobotsTXT(config) + rob, err := renderRobotsTXT(ioConfig.Source, config) if err != nil { return nil, err } - if err := outputToFile(rob, publicDir, "robots.txt"); err != nil { + if err := outputToFile(rob, outDir, "robots.txt"); err != nil { return nil, err } return r, nil } -func BuildSite(ioConfig IOConfig) (*Result, error) { - config, err := config.GetConfig() - if err != nil { - return nil, errors.WithMessage(err, "could not get config") +func BuildSite(ioConfig *IOConfig, cfg *config.Config, log *log.Logger) (*Result, error) { + if cfg == nil { + return nil, errors.New("config is nil") } - config.InjectLiveReload = ioConfig.Development + cfg.InjectLiveReload = ioConfig.Development compressFiles = !ioConfig.Development templates.Setup() - loadCSS() + loadCSS(ioConfig.Source) - return build(ioConfig.Destination, *config) + return build(ioConfig, cfg, log) } diff --git a/internal/builder/template.go b/internal/builder/template.go index da45fb7..9f019df 100644 --- a/internal/builder/template.go +++ b/internal/builder/template.go @@ -1,53 +1,41 @@ package builder import ( + "bytes" "encoding/xml" "io" "os" "path/filepath" "strings" "text/template" - "website/internal/atom" - "website/internal/config" - "website/internal/content" + + "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" - "github.com/pkg/errors" + "gitlab.com/tozd/go/errors" ) var ( - css string - templateFiles = make(map[string]*os.File) - nsMap = map[string]string{ + 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() { - bytes, err := os.ReadFile("templates/style.css") +func loadCSS(source string) { + bytes, err := os.ReadFile(filepath.Join(source, "templates/style.css")) if err != nil { panic(err) } css = string(bytes) } -func loadTemplate(path string) (file *os.File, err error) { - if templateFiles[path] == nil { - file, err = os.OpenFile(path, os.O_RDONLY, 0) - if err != nil { - return nil, errors.Wrapf(err, "could not load template at path %s", path) - } - templateFiles[path] = file - } - file = templateFiles[path] - - return -} - type QuerySelection struct { *goquery.Selection } @@ -66,9 +54,9 @@ func (q *QueryDocument) Find(selector string) *QuerySelection { return &QuerySelection{q.Document.Find(selector)} } -func renderRobotsTXT(config config.Config) (io.Reader, error) { +func renderRobotsTXT(source string, config *config.Config) (io.Reader, error) { r, w := io.Pipe() - tpl, err := template.ParseFiles("templates/robots.tmpl") + tpl, err := template.ParseFiles(filepath.Join(source, "templates/robots.tmpl")) if err != nil { return nil, err } @@ -87,41 +75,30 @@ func renderRobotsTXT(config config.Config) (io.Reader, error) { func renderFeed( title string, - config config.Config, + config *config.Config, posts []content.Post, specific string, -) (io.Reader, error) { - reader, err := loadTemplate("templates/feed.xml") +) (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 } - defer func() { - _, err := reader.Seek(0, io.SeekStart) - if err != nil { - panic("could not reset reader: " + err.Error()) - } - }() - doc, err := xmlquery.Parse(reader) - if err != nil { - return nil, errors.Wrap(err, "could not parse XML") - } - feed := doc.SelectElement("feed") - feed.SelectElement("title").FirstChild.Data = title - feed.SelectElement("link").SetAttr("href", config.BaseURL.String()) - feed.SelectElement("id").FirstChild.Data = atom.MakeTagURI(config, specific) - datetime, err := posts[0].Date.UTC().MarshalText() - if err != nil { - return nil, errors.Wrap(err, "could not convert post date to text") + feed := &atom.Feed{ + Title: title, + Link: atom.MakeLink(config.BaseURL.URL), + ID: atom.MakeTagURI(config, specific), + Updated: datetime, + Entries: make([]*atom.FeedEntry, len(posts)), } - feed.SelectElement("updated").FirstChild.Data = string(datetime) - tpl := feed.SelectElement("entry") - xmlquery.RemoveFromTree(tpl) - for _, post := range posts { - fullURL := config.BaseURL.JoinPath(post.URL).String() - text, err := xml.MarshalIndent(&atom.FeedEntry{ + for i, post := range posts { + feed.Entries[i] = &atom.FeedEntry{ Title: post.Title, - Link: atom.MakeLink(fullURL), + Link: atom.MakeLink(config.BaseURL.JoinPath(post.URL)), ID: atom.MakeTagURI(config, post.Basename), Updated: post.Date.UTC(), Summary: post.Description, @@ -130,31 +107,19 @@ func renderFeed( Content: post.Content, Type: "html", }, - }, " ", " ") - if err != nil { - return nil, errors.Wrap(err, "could not marshal xml") - } - entry, err := xmlquery.ParseWithOptions( - strings.NewReader(string(text)), - xmlquery.ParserOptions{ - Decoder: &xmlquery.DecoderOptions{ - Strict: false, - AutoClose: xml.HTMLAutoClose, - Entity: xml.HTMLEntity, - }, - }, - ) - if err != nil { - return nil, errors.Wrap(err, "could not parse XML") } - xmlquery.AddChild(feed, entry.SelectElement("entry")) + } + enc := xml.NewEncoder(buf) + err = enc.Encode(feed) + if err != nil { + return nil, err } - return strings.NewReader(doc.OutputXML(true)), nil + return buf, nil } -func renderFeedStyles() (*strings.Reader, error) { - tpl, err := template.ParseFiles("templates/feed-styles.xsl") +func renderFeedStyles(source string) (*strings.Reader, error) { + tpl, err := template.ParseFiles(filepath.Join(source, "templates/feed-styles.xsl")) if err != nil { return nil, err } @@ -169,6 +134,9 @@ func renderFeedStyles() (*strings.Reader, error) { err = tpl.Execute(w, map[string]interface{}{ "css": esc.String(), }) + if err != nil { + return nil, err + } return strings.NewReader(w.String()), nil } |