My Neocities site is built by a simple static site generator that I wrote in Go. It's kinda the first real
thing I've made in Go, and it is thus far in its early stages, so there are probably ways to make it more
efficient. I'll probably set up a git repo at some point, but for now I'll just put the code for
myneocities.go here.
This code currently assumes the following folder structure:
package main
import (
"html/template"
"os"
"sort"
)
const (
TemplatesDir = "0_templates/"
LayoutHTML = "layout.html"
StyleCSS = "style.css"
InputDir = "1_content/"
OutputDir = "2_output/"
)
type Page struct {
PageFilename string
PageContent string //keep in mind that os.ReadFile productes []byte, template.Execute wants template.HTML
} //at the moment of rendering, an anonymous struct is created with PageContent as template.HTML instead of string
func ConstructPageFromFile(dir string, file string) Page {
filecontent, err := os.ReadFile(dir + file)
if err != nil {
panic(err)
}
return Page{file, string(filecontent)}
}
func RenderPage(templ *template.Template, dir string, page Page) {
outfile, err := os.Create(dir + page.PageFilename)
if err != nil {
panic(err)
}
pageEscaped := struct { //need to wrap the PageContent as a template.HTML instead of string
PageFilename string
PageContent template.HTML
}{
PageFilename: page.PageFilename,
PageContent: template.HTML(page.PageContent), //wraps to template.HTML string instead of regular string
}
templ.Execute(outfile, pageEscaped)
defer outfile.Close()
}
func CreateContentManifest(dir string) ([]Page, []Page) {
// read the passed directory (will be InputDir)
arrMainFiles, err := os.ReadDir(dir) //returns ([]os.DirEntry, error)
if err != nil {
panic(err)
}
// read the "blog" directory within the passed directory
arrBlogFiles, err := os.ReadDir(dir + "/blog") //returns ([]os.DirEntry, error)
if err != nil {
panic(err)
}
// initialize two arrays of Page constructs - these will be output
var arrMainManifest []Page
var arrBlogManifest []Page
// iterate thru the two directories and create Page constructs from each file
// TODO -- handle other file types, like images, so they can be in the InputDir with the HTML snippets
for i := range arrMainFiles {
if !arrMainFiles[i].IsDir() {
arrMainManifest = append(arrMainManifest, ConstructPageFromFile(InputDir, arrMainFiles[i].Name()))
}
}
// (sort the blog files descending first)
sort.Slice(arrBlogFiles, func(i, j int) bool {
return arrBlogFiles[i].Name() > arrBlogFiles[j].Name()
}) //then iterate thru like the main files
for j := range arrBlogFiles {
if !arrBlogFiles[j].IsDir() {
arrBlogManifest = append(arrBlogManifest, ConstructPageFromFile(InputDir+"blog/", arrBlogFiles[j].Name()))
}
}
return arrMainManifest, arrBlogManifest
}
func main() {
// parse layout.html template
LayoutHTMLTemplate := template.Must(template.ParseFiles(TemplatesDir + LayoutHTML)) //type *template.Template
// create manifest of pages to generate
PagesManifest, BlogsManifest := CreateContentManifest(InputDir)
// copy stylesheet
StyleCSSFile, err := os.ReadFile(TemplatesDir + StyleCSS)
if err != nil {
panic(err)
}
os.WriteFile(OutputDir+StyleCSS, StyleCSSFile, 0664)
// generate main pages from the PagesManifest
for i := range PagesManifest {
RenderPage(LayoutHTMLTemplate, OutputDir, PagesManifest[i])
}
// generate blog post individual pages & overall blog page from the BlogsManifest
var BlogPage = Page{"blog.html", ""}
BlogPage.PageFilename = "blog.html"
for j := range BlogsManifest {
RenderPage(LayoutHTMLTemplate, OutputDir, BlogsManifest[j])
BlogPage.PageContent += BlogsManifest[j].PageContent
}
RenderPage(LayoutHTMLTemplate, OutputDir, BlogPage) //render blog.html
}