summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitja Felicijan <m@mitjafelicijan.com>2023-07-08 19:24:16 +0200
committerMitja Felicijan <m@mitjafelicijan.com>2023-07-08 19:24:16 +0200
commit8e83285cc5b36c516880c9e321b86cb78db0d27f (patch)
treeaf17d0182e4ba9f306c74ac3e29fee7630214f5a
downloadjbmafp-8e83285cc5b36c516880c9e321b86cb78db0d27f.tar.gz
Enageg!
-rw-r--r--.gitignore2
-rw-r--r--LICENSE24
-rw-r--r--README.md11
-rw-r--r--go.mod27
-rw-r--r--go.sum65
-rw-r--r--main.go326
6 files changed, 455 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1be5c1c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+public/
+example/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..adcb269
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+BSD 2-Clause License
+
+Copyright (c) 2022-2023, Mitja Felicijan <m@mitjafelicijan.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..395da96
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# Just Build Me A Fucking Page
+
+I am just so sick of all these complicated static site generators forcing you to
+care about taxonomies and shit like this. All I want is to have a bunch of
+markdown files and let them use specific templates. That is about it. Nothing
+fancy!
+
+This generator is not for people who need something more complicated. Use Hugo
+instead. But if you need a simple blog page that needs to spit out an RSS feed
+or two and have the option to define different templates for different posts,
+well then this might be useful to you.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e5b9f5d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,27 @@
+module mitjafelicijan.com/jbmafp
+
+go 1.20
+
+require (
+ github.com/DavidBelicza/TextRank/v2 v2.1.3
+ github.com/alexflint/go-arg v1.4.3
+ github.com/microcosm-cc/bluemonday v1.0.24
+ github.com/otiai10/copy v1.12.0
+ github.com/tdewolff/minify/v2 v2.12.7
+ github.com/yuin/goldmark v1.5.4
+ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87
+ github.com/yuin/goldmark-meta v1.1.0
+ gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+ github.com/alecthomas/chroma/v2 v2.2.0 // indirect
+ github.com/alexflint/go-scalar v1.1.0 // indirect
+ github.com/aymerick/douceur v0.2.0 // indirect
+ github.com/dlclark/regexp2 v1.7.0 // indirect
+ github.com/gorilla/css v1.0.0 // indirect
+ github.com/tdewolff/parse/v2 v2.6.6 // indirect
+ golang.org/x/net v0.10.0 // indirect
+ golang.org/x/sys v0.8.0 // indirect
+ gopkg.in/yaml.v2 v2.3.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..0fec4c2
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,65 @@
+github.com/DavidBelicza/TextRank/v2 v2.1.3 h1:6gnDe761kdIdrCgTSzCf8fPu7bga/+jSiWqbPIhzlBw=
+github.com/DavidBelicza/TextRank/v2 v2.1.3/go.mod h1:JWemq/WyDpOm6yxMhEOjnXCUXds0wQ6NT4TP4Af6byU=
+github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
+github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
+github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo=
+github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA=
+github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
+github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
+github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
+github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
+github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
+github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw=
+github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
+github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=
+github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
+github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
+github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
+github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
+github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
+github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
+github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
+github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
+github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
+github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 h1:Py16JEzkSdKAtEFJjiaYLYBOWGXc1r/xHj/Q/5lA37k=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
+github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
+github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..0fdc2da
--- /dev/null
+++ b/main.go
@@ -0,0 +1,326 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "html/template"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ yaml "gopkg.in/yaml.v3"
+
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark-meta"
+ "github.com/yuin/goldmark/extension"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer/html"
+
+ "github.com/DavidBelicza/TextRank/v2"
+ "github.com/alexflint/go-arg"
+ "github.com/microcosm-cc/bluemonday"
+
+ "github.com/tdewolff/minify/v2"
+ mcss "github.com/tdewolff/minify/v2/css"
+ mhtml "github.com/tdewolff/minify/v2/html"
+ mjs "github.com/tdewolff/minify/v2/js"
+
+ highlighting "github.com/yuin/goldmark-highlighting/v2"
+
+ cp "github.com/otiai10/copy"
+)
+
+type ConfigExtrasItem struct {
+ Type string `yaml:"type"`
+ Template string `yaml:"template"`
+ URL string `yaml:"url"`
+}
+
+type Config struct {
+ Title string `yaml:"title"`
+ Description string `yaml:"description"`
+ BaseURL string `yaml:"baseurl"`
+ Language string `yaml:"language"`
+ Highlighting string `yaml:"highlighting"`
+ Minify bool `yaml:"minify"`
+ Extras []ConfigExtrasItem `yaml:"extras"`
+}
+
+type Page struct {
+ Filepath string
+ Raw string
+ HTML template.HTML
+ Text string
+ Summary string
+ Meta map[string]interface{}
+ Title string
+ Type string
+ RelPermalink string
+ Created time.Time
+ Draft bool
+}
+
+// Function to clean HTML tags using bluemonday.
+func cleanHTMLTags(htmlString string) string {
+ p := bluemonday.StrictPolicy()
+ cleanString := p.Sanitize(htmlString)
+ return cleanString
+}
+
+func initializeProject(projectRoot string) {
+ fmt.Println("Initializing new project")
+}
+
+func main() {
+ projectRoot := os.Getenv("PROJECT_ROOT")
+ if projectRoot == "" {
+ projectRoot = "./"
+ }
+
+ fmt.Println("Come back later! Still WIP!")
+ os.Exit(0)
+
+ // Parsing arguments.
+ var args struct {
+ Init bool `arg:"-i,--init" help:"initialize new project"`
+ Build bool `arg:"-b,--build" help:"build the website"`
+ }
+
+ arg.MustParse(&args)
+
+ if !args.Init && !args.Build {
+ fmt.Println("No arguments provided. Try using `jbmafp --help`")
+ os.Exit(0)
+ }
+
+ if args.Init {
+ initializeProject(projectRoot)
+ os.Exit(0)
+ }
+
+ os.Exit(0)
+
+ // Read config file.
+ configFilepath := path.Join(projectRoot, "config.yaml")
+ configFile, err := os.ReadFile(configFilepath)
+ if err != nil {
+ panic(err)
+ }
+ config := Config{}
+ err = yaml.Unmarshal(configFile, &config)
+ if err != nil {
+ panic(err)
+ }
+
+ // Gets the list of all markdown files.
+ files, err := filepath.Glob(path.Join(projectRoot, "content/*.md"))
+ if err != nil {
+ panic(err)
+ }
+
+ md := goldmark.New(
+ goldmark.WithExtensions(
+ extension.GFM,
+ meta.Meta,
+ highlighting.NewHighlighting(
+ highlighting.WithStyle(config.Highlighting),
+ ),
+ ),
+ goldmark.WithParserOptions(
+ parser.WithAutoHeadingID(),
+ parser.WithBlockParsers(),
+ parser.WithInlineParsers(),
+ parser.WithParagraphTransformers(),
+ parser.WithAttribute(),
+ ),
+ goldmark.WithRendererOptions(
+ html.WithXHTML(),
+ html.WithUnsafe(),
+ ),
+ )
+
+ // Parse all markdown files in content folder.
+ pages := []Page{}
+ for _, file := range files {
+ source, err := os.ReadFile(file)
+ if err != nil {
+ panic(err)
+ }
+
+ var buf bytes.Buffer
+ ctx := parser.NewContext()
+ if err := md.Convert(source, &buf, parser.WithContext(ctx)); err != nil {
+ panic(err)
+ }
+
+ // Rank and summarize.
+ tr := textrank.NewTextRank()
+ rule := textrank.NewDefaultRule()
+ language := textrank.NewDefaultLanguage()
+ algorithmDef := textrank.NewDefaultAlgorithm()
+ tr.Populate(cleanHTMLTags(buf.String()), language, rule)
+ tr.Ranking(algorithmDef)
+
+ sentences := textrank.FindSentencesByRelationWeight(tr, 50)
+ sentences = textrank.FindSentencesFrom(tr, 0, 1)
+
+ summary := ""
+ for _, s := range sentences {
+ summary = strings.ReplaceAll(s.Value, "\n", "")
+ }
+
+ metaData := meta.Get(ctx)
+ t, _ := time.Parse("2006-01-02T15:04:05-07:00", metaData["date"].(string))
+ pages = append(pages, Page{
+ Filepath: file,
+ Meta: metaData,
+ Raw: buf.String(),
+ HTML: template.HTML(buf.String()),
+ Text: cleanHTMLTags(buf.String()),
+ Summary: summary,
+ Title: metaData["title"].(string),
+ Type: metaData["type"].(string),
+ RelPermalink: metaData["url"].(string),
+ Created: t,
+ Draft: metaData["draft"].(bool),
+ })
+ }
+
+ // Sorting pages in descending created order.
+ sort.Slice(pages, func(i, j int) bool {
+ return pages[i].Created.After(pages[j].Created)
+ })
+
+ // Generate HTML files for all pages.
+ for _, page := range pages {
+ outFilepath := path.Join(projectRoot, "public", page.Meta["url"].(string))
+ if !page.Draft {
+ pageTemplateFilename := fmt.Sprintf("%s.html", page.Meta["type"].(string))
+ templatePathname := path.Join(projectRoot, "templates", pageTemplateFilename)
+ baseTemplatePathname := path.Join(projectRoot, "templates/base.html")
+ t, err := template.ParseFiles(baseTemplatePathname, templatePathname)
+ if err != nil {
+ panic(err)
+ }
+
+ type Payload struct {
+ Config Config
+ Page Page
+ }
+
+ var buf bytes.Buffer
+ err = t.Execute(&buf, Payload{
+ Config: config,
+ Page: page,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ outHTML := buf.String()
+ if config.Minify {
+ m := minify.New()
+ m.AddFunc("text/html", mhtml.Minify)
+ m.AddFunc("text/css", mcss.Minify)
+ m.AddFunc("application/js", mjs.Minify)
+ outHTML, err = m.String("text/html", outHTML)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ os.WriteFile(outFilepath, []byte(outHTML), 0755)
+ log.Println("Wrote", outFilepath)
+ } else {
+ log.Println("Skipped", outFilepath)
+
+ }
+
+ }
+
+ // Generates index page.
+ {
+ log.Println("Writing index...")
+ templatePathname := path.Join(projectRoot, "templates/index.html")
+ baseTemplatePathname := path.Join(projectRoot, "templates/base.html")
+ t, err := template.ParseFiles(baseTemplatePathname, templatePathname)
+ if err != nil {
+ panic(err)
+ }
+
+ type Payload struct {
+ Config Config
+ Pages []Page
+ }
+
+ var buf bytes.Buffer
+ err = t.Execute(&buf, Payload{
+ Config: config,
+ Pages: pages,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ outHTML := buf.String()
+ if config.Minify {
+ m := minify.New()
+ m.AddFunc("text/html", mhtml.Minify)
+ m.AddFunc("text/css", mcss.Minify)
+ m.AddFunc("application/js", mjs.Minify)
+ outHTML, err = m.String("text/html", outHTML)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ outFilepath := path.Join(projectRoot, "public", "index.html")
+ os.WriteFile(outFilepath, []byte(outHTML), 0755)
+ }
+
+ // Copy static files.
+ {
+ log.Println("Copying static files...")
+ err := cp.Copy(path.Join(projectRoot, "static"), path.Join(projectRoot, "public"))
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ // Generates extras.
+ {
+ for _, extra := range config.Extras {
+ log.Printf("Writing extras %s\n", extra.URL)
+ templatePathname := path.Join(projectRoot, "templates", extra.Template)
+ t, err := template.ParseFiles(templatePathname)
+ if err != nil {
+ panic(err)
+ }
+
+ type Payload struct {
+ Config Config
+ Pages []Page
+ }
+
+ var buf bytes.Buffer
+ err = t.Execute(&buf, Payload{
+ Config: config,
+ Pages: pages,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ outFilepath := path.Join(projectRoot, "public", extra.URL)
+ os.WriteFile(outFilepath, []byte(buf.String()), 0755)
+
+ }
+ }
+
+ // Guess we are done!
+ log.Println("Done & done...")
+}