From 3fe75e9ad5bac701d1b0c481d0cbf3844c00a99e Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sun, 20 Apr 2025 15:39:22 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feature:=20Set=20up=20templated=20view?= =?UTF-8?q?s=20and=20blog=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + content/blog/another-blog-post.md | 20 ++ content/blog/my-first-blog-post.md | 13 +- main.go | 230 +++++++++++---------- public/about/index.html | 45 ++-- public/blog/another-blog-post/index.html | 68 ++++++ public/blog/category/anger/index.html | 42 ++++ public/blog/category/go/index.html | 44 ++++ public/blog/category/progrmming/index.html | 42 ++++ public/blog/index.html | 64 +++--- public/blog/my-first-blog-post/index.html | 72 +++++-- public/index.html | 45 ++-- templates/base.html | 28 +++ templates/blog_index_page.html | 14 ++ templates/blog_post_page.html | 22 ++ templates/category_page.html | 12 ++ templates/footer.html | 5 + templates/header.html | 11 + templates/layout.html | 46 ----- templates/static_page.html | 10 + 20 files changed, 580 insertions(+), 255 deletions(-) create mode 100644 .gitignore create mode 100644 content/blog/another-blog-post.md create mode 100644 public/blog/another-blog-post/index.html create mode 100644 public/blog/category/anger/index.html create mode 100644 public/blog/category/go/index.html create mode 100644 public/blog/category/progrmming/index.html create mode 100644 templates/base.html create mode 100644 templates/blog_index_page.html create mode 100644 templates/blog_post_page.html create mode 100644 templates/category_page.html create mode 100644 templates/footer.html create mode 100644 templates/header.html delete mode 100644 templates/layout.html create mode 100644 templates/static_page.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d36308 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +bak/ diff --git a/content/blog/another-blog-post.md b/content/blog/another-blog-post.md new file mode 100644 index 0000000..ca70801 --- /dev/null +++ b/content/blog/another-blog-post.md @@ -0,0 +1,20 @@ +--- +title: Go SSG - Another Blog Post +description: A longer summary of the post. Lorem ipsum dolor set amit. +date: 2025-04-19 +categories: + - go + - anger +--- + +# Why isn't this working?! 😭 + +This is my first Go SSG blog post. + +This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently. + +It supports features like: + +- Markdown content +- Customizable templates +- Simple configuration diff --git a/content/blog/my-first-blog-post.md b/content/blog/my-first-blog-post.md index 584c6b9..7838499 100644 --- a/content/blog/my-first-blog-post.md +++ b/content/blog/my-first-blog-post.md @@ -2,8 +2,19 @@ title: Go SSG - My First Blog Post description: A short summary of the post. date: 2025-04-19 +categories: + - progrmming + - go --- # *Tap Tap Tap* Is this thing on? -This is my first blog post! +This is my first Go SSG blog post. + +This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently. + +It supports features like: + +- Markdown content +- Customizable templates +- Simple configuration diff --git a/main.go b/main.go index f232b78..7399962 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ + package main import ( @@ -7,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" "time" @@ -14,26 +16,27 @@ import ( "gopkg.in/yaml.v3" ) -// NavItem represents a navigation link type NavItem struct { Title string URL string } -// PageMeta holds front matter metadata type PageMeta struct { - Title string `yaml:"title"` - Description string `yaml:"description"` - Date string `yaml:"date"` - Slug string // we'll set this ourselves from filename + Title string `yaml:"title"` + Description string `yaml:"description"` + Date string `yaml:"date"` + Categories []string `yaml:"categories"` + Slug string } +var slugRegex = regexp.MustCompile(`[^a-z0-9]+`) + func main() { contentDir := "./content" outputDir := "./public" - templateFile := "./templates/layout.html" - tpl, err := template.ParseFiles(templateFile) + // Load all templates in the templates folder + tpl, err := template.New("").Funcs(templateFuncs()).ParseGlob("templates/*.html") if err != nil { fmt.Printf("Template parsing error: %v\n", err) return @@ -45,7 +48,6 @@ func main() { return } - // Collect nav info early var nav []NavItem for _, file := range files { if filepath.Ext(file.Name()) == ".md" { @@ -54,77 +56,63 @@ func main() { if name != "index" { url = "/" + name + "/" } - nav = append(nav, NavItem{ - Title: strings.Title(name), - URL: url, - }) + nav = append(nav, NavItem{Title: strings.Title(name), URL: url}) } } - // Add blog to the main nav - nav = append(nav, NavItem{ - Title: "Blog", - URL: "/blog/", - }) + // Add blog to main nav + nav = append(nav, NavItem{Title: "Blog", URL: "/blog/"}) - // Generate index.html for each static page markdown file + // Static page rendering for _, file := range files { - if filepath.Ext(file.Name()) != ".md" { - continue + if filepath.Ext(file.Name()) == ".md" { + name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) + title := strings.Title(name) + mdPath := filepath.Join(contentDir, file.Name()) + rawContent, err := ioutil.ReadFile(mdPath) + if err != nil { + fmt.Printf("Failed to read %s: %v\n", file.Name(), err) + continue + } + + meta, content := parseFrontMatter(rawContent) + htmlContent := blackfriday.Run(content) + if meta.Title == "" { + meta.Title = title + } + + var outPath string + if name == "index" { + outPath = filepath.Join(outputDir, "index.html") + } else { + subDir := filepath.Join(outputDir, name) + os.MkdirAll(subDir, os.ModePerm) + outPath = filepath.Join(subDir, "index.html") + } + + outFile, err := os.Create(outPath) + if err != nil { + fmt.Printf("Failed to create %s: %v\n", outPath, err) + continue + } + + tpl.ExecuteTemplate(outFile, "static_page", map[string]interface{}{ + "Title": meta.Title, + "Description": meta.Description, + "Date": meta.Date, + "Categories": meta.Categories, + "Content": template.HTML(htmlContent), + "Nav": nav, + "Year": time.Now().Year(), + "PageTemplate": "static", + }) + + fmt.Printf("Generated: %s\n", outPath) } - - name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) - mdPath := filepath.Join(contentDir, file.Name()) - rawContent, err := ioutil.ReadFile(mdPath) - if err != nil { - fmt.Printf("Failed to read %s: %v\n", file.Name(), err) - continue - } - - meta, content := parseFrontMatter(rawContent) - htmlContent := blackfriday.Run(content) - - // Determine output path - var outPath string - if name == "index" { - outPath = filepath.Join(outputDir, "index.html") - } else { - subDir := filepath.Join(outputDir, name) - os.MkdirAll(subDir, os.ModePerm) - outPath = filepath.Join(subDir, "index.html") - } - - outFile, err := os.Create(outPath) - if err != nil { - fmt.Printf("Failed to create %s: %v\n", outPath, err) - continue - } - - // Fall back to filename for title if none provided - if meta.Title == "" { - meta.Title = strings.Title(name) - } - - data := map[string]interface{}{ - "Title": meta.Title, - "Description": meta.Description, - "Date": meta.Date, - "Content": template.HTML(htmlContent), - "Nav": nav, - "Year": time.Now().Year(), - } - - err = tpl.Execute(outFile, data) - if err != nil { - fmt.Printf("Template execution failed for %s: %v\n", file.Name(), err) - continue - } - - fmt.Printf("Generated: %s\n", outPath) } - // Generate blog index var blogPosts []PageMeta + categoryMap := make(map[string][]PageMeta) blogDir := filepath.Join(contentDir, "blog") blogFiles, _ := ioutil.ReadDir(blogDir) @@ -144,12 +132,16 @@ func main() { meta, content := parseFrontMatter(rawContent) html := blackfriday.Run(content) - meta.Slug = slug if meta.Title == "" { meta.Title = strings.Title(slug) } + for _, cat := range meta.Categories { + slug := slugify(cat) + categoryMap[slug] = append(categoryMap[slug], meta) + } + outPath := filepath.Join(outputDir, "blog", slug, "index.html") os.MkdirAll(filepath.Dir(outPath), os.ModePerm) @@ -159,47 +151,54 @@ func main() { continue } - err = tpl.Execute(outFile, map[string]interface{}{ - "Title": meta.Title, - "Description": meta.Description, - "Date": meta.Date, - "Content": template.HTML(html), - "Nav": nav, - "Year": time.Now().Year(), + tpl.ExecuteTemplate(outFile, "blog_post_page", map[string]interface{}{ + "Title": meta.Title, + "Description": meta.Description, + "Date": meta.Date, + "Categories": meta.Categories, + "Content": template.HTML(html), + "Nav": nav, + "Year": time.Now().Year(), + "PageTemplate": "blog_post", }) - if err != nil { - fmt.Printf("Template error for blog post %s: %v\n", file.Name(), err) - continue - } - blogPosts = append(blogPosts, meta) } - err = buildBlogIndex(blogPosts, tpl, outputDir, nav) - if err != nil { - fmt.Println("Failed to build blog index:", err) + buildBlogIndex(blogPosts, tpl, outputDir, nav) + + for catSlug, posts := range categoryMap { + outDir := filepath.Join(outputDir, "blog", "category", catSlug) + os.MkdirAll(outDir, os.ModePerm) + + outPath := filepath.Join(outDir, "index.html") + outFile, err := os.Create(outPath) + if err != nil { + fmt.Printf("Failed to create category page for %s: %v\n", catSlug, err) + continue + } + + tpl.ExecuteTemplate(outFile, "category_page", map[string]interface{}{ + "Title": "Category: " + strings.Title(strings.ReplaceAll(catSlug, "-", " ")), + "Posts": posts, + "Nav": nav, + "Year": time.Now().Year(), + "PageTemplate": "category_page", + }) + + fmt.Printf("Generated category: /blog/category/%s/\n", catSlug) } fmt.Println("✅ Site generation complete.") } -// parseFrontMatter splits raw markdown into meta + content func parseFrontMatter(raw []byte) (PageMeta, []byte) { var meta PageMeta - - // Normalize line endings raw = bytes.ReplaceAll(raw, []byte("\r\n"), []byte("\n")) - // Only try to parse if the file starts with "---\n" if bytes.HasPrefix(raw, []byte("---\n")) { - // Split after the first two "---\n" lines parts := bytes.SplitN(raw, []byte("---\n"), 3) - - if len(parts) >= 3 { - // parts[0] = empty (before first ---) - // parts[1] = YAML content - // parts[2] = remaining markdown content + if len(parts) == 3 { err := yaml.Unmarshal(parts[1], &meta) if err != nil { fmt.Printf("⚠️ Failed to parse front matter: %v\n", err) @@ -208,26 +207,39 @@ func parseFrontMatter(raw []byte) (PageMeta, []byte) { } } - // If there's no front matter, return raw as-is return meta, raw } -// buildBlogIndex generates the blog index page -func buildBlogIndex(posts []PageMeta, tpl *template.Template, outputDir string, nav []NavItem) error { +func buildBlogIndex(posts []PageMeta, tpl *template.Template, outputDir string, nav []NavItem) { outPath := filepath.Join(outputDir, "blog", "index.html") os.MkdirAll(filepath.Dir(outPath), os.ModePerm) outFile, err := os.Create(outPath) if err != nil { - return err + fmt.Println("Failed to create blog index:", err) + return } - data := map[string]interface{}{ - "Title": "Go SSG - Blog", - "Posts": posts, - "Nav": nav, - "Year": time.Now().Year(), - } - - return tpl.Execute(outFile, data) + fmt.Printf("Rendering blog index: %d posts\n", len(posts)) + tpl.ExecuteTemplate(outFile, "blog_index_page", map[string]interface{}{ + "Title": "Blog Index", + "Posts": posts, + "Nav": nav, + "Year": time.Now().Year(), + "PageTemplate": "blog_index", + }) +} + +func slugify(s string) string { + s = strings.ToLower(s) + s = slugRegex.ReplaceAllString(s, "-") + return strings.Trim(s, "-") +} + +func templateFuncs() template.FuncMap { + return template.FuncMap{ + "lower": strings.ToLower, + "slugify": slugify, + "urlquery": template.URLQueryEscaper, + } } diff --git a/public/about/index.html b/public/about/index.html index 9e0d152..394a711 100644 --- a/public/about/index.html +++ b/public/about/index.html @@ -1,40 +1,43 @@ + + - Go SSG - About - - -
- +
+
- Published: 2025-04-19 -

About

+
+

Go SSG - About

+

About

Experimenting with Go. Seems pretty cool so far!

+
+
- - + + - - + diff --git a/public/blog/another-blog-post/index.html b/public/blog/another-blog-post/index.html new file mode 100644 index 0000000..357d04d --- /dev/null +++ b/public/blog/another-blog-post/index.html @@ -0,0 +1,68 @@ + + + + + + + Go SSG - Another Blog Post + + + +
+ +
+ + +
+
+

Go SSG - Another Blog Post

+

2025-04-19

+ + +

+ Categories: + + + go + + , + anger + +

+ + +

Why isn’t this working?! 😭

+ +

This is my first Go SSG blog post.

+ +

This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.

+ +

It supports features like:

+ +
    +
  • Markdown content
  • +
  • Customizable templates
  • +
  • Simple configuration
  • +
+ +
+
+ + + + + + + diff --git a/public/blog/category/anger/index.html b/public/blog/category/anger/index.html new file mode 100644 index 0000000..b72ccdc --- /dev/null +++ b/public/blog/category/anger/index.html @@ -0,0 +1,42 @@ + + + + + + + Category: Anger + + + +
+ +
+ + +
+

Category: Anger

+ +
+ + + + + + + diff --git a/public/blog/category/go/index.html b/public/blog/category/go/index.html new file mode 100644 index 0000000..06fe45e --- /dev/null +++ b/public/blog/category/go/index.html @@ -0,0 +1,44 @@ + + + + + + + Category: Go + + + +
+ +
+ + +
+

Category: Go

+ +
+ + + + + + + diff --git a/public/blog/category/progrmming/index.html b/public/blog/category/progrmming/index.html new file mode 100644 index 0000000..8fd848c --- /dev/null +++ b/public/blog/category/progrmming/index.html @@ -0,0 +1,42 @@ + + + + + + + Category: Progrmming + + + +
+ +
+ + +
+

Category: Progrmming

+ +
+ + + + + + + diff --git a/public/blog/index.html b/public/blog/index.html index dd864b5..86a574f 100644 --- a/public/blog/index.html +++ b/public/blog/index.html @@ -1,50 +1,44 @@ + + - - Go SSG - Blog - + Blog Index - -
- +
+
- - +

Blog Index

+ +
- -
-

Blog Posts

- -
- - + + - - + diff --git a/public/blog/my-first-blog-post/index.html b/public/blog/my-first-blog-post/index.html index e1cd3d5..7fb54a3 100644 --- a/public/blog/my-first-blog-post/index.html +++ b/public/blog/my-first-blog-post/index.html @@ -1,40 +1,68 @@ + + - Go SSG - My First Blog Post - - -
- +
+
- Published: 2025-04-19 -

Tap Tap Tap Is this thing on?

+
+

Go SSG - My First Blog Post

+

2025-04-19

-

This is my first blog post!

+ +

+ Categories: + + + progrmming + + , + go + +

+ +

Tap Tap Tap Is this thing on?

- -
+

This is my first Go SSG blog post.

+ +

This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.

+ +

It supports features like:

+ + + + + + + + - - + diff --git a/public/index.html b/public/index.html index 6039a8e..13c55bb 100644 --- a/public/index.html +++ b/public/index.html @@ -1,40 +1,43 @@ + + - Go SSG - Home - - -
- +
+
- Published: 2025-04-19 -

Welcome

+
+

Go SSG - Home

+

Welcome

This is your first static site built with Go!

+
+
- - + + - - + diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..8e7a919 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,28 @@ +{{ define "base" }} + + + + + {{ .Title }} + + + {{ template "header" . }} + +
+ {{- if eq .PageTemplate "blog_index" -}} + {{ template "blog_index_content" . }} + {{- else if eq .PageTemplate "static" -}} + {{ template "static_content" . }} + {{- else if eq .PageTemplate "blog_post" -}} + {{ template "blog_post_content" . }} + {{- else if eq .PageTemplate "category_page" -}} + {{ template "category_content" . }} + {{- else -}} +

Unknown PageTemplate: {{ .PageTemplate }}

+ {{- end -}} +
+ + {{ template "footer" . }} + + +{{ end }} diff --git a/templates/blog_index_page.html b/templates/blog_index_page.html new file mode 100644 index 0000000..a6eb264 --- /dev/null +++ b/templates/blog_index_page.html @@ -0,0 +1,14 @@ +{{ define "blog_index_page" }} +{{ template "base" . }} +{{ end }} + +{{ define "blog_index_content" }} +

Blog Index

+ +{{ end }} diff --git a/templates/blog_post_page.html b/templates/blog_post_page.html new file mode 100644 index 0000000..993b223 --- /dev/null +++ b/templates/blog_post_page.html @@ -0,0 +1,22 @@ +{{ define "blog_post_page" }} +{{ template "base" . }} +{{ end }} + +{{ define "blog_post_content" }} +
+

{{ .Title }}

+ {{ if .Date }}

{{ .Date }}

{{ end }} + + {{ if .Categories }} +

+ Categories: + {{ range $i, $cat := .Categories }} + {{ if $i }}, {{ end }} + {{ $cat }} + {{ end }} +

+ {{ end }} + + {{ .Content }} +
+{{ end }} diff --git a/templates/category_page.html b/templates/category_page.html new file mode 100644 index 0000000..d0b120c --- /dev/null +++ b/templates/category_page.html @@ -0,0 +1,12 @@ +{{ define "category_page" }} +{{ template "base" . }} +{{ end }} + +{{ define "category_content" }} +

{{ .Title }}

+ +{{ end }} diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..7f5b966 --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,5 @@ +{{ define "footer" }} + +{{ end }} diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..d7eee8d --- /dev/null +++ b/templates/header.html @@ -0,0 +1,11 @@ +{{ define "header" }} +
+ +
+{{ end }} diff --git a/templates/layout.html b/templates/layout.html deleted file mode 100644 index 06efa66..0000000 --- a/templates/layout.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - {{ .Title }} - {{ if .Description }}{{ end }} - - - -
- -
- -
- {{ if .Date }}Published: {{ .Date }}{{ end }} - {{ .Content }} - - {{ if .Posts }} -
-

Blog Posts

-
    - {{ range .Posts }} -
  • - {{ .Title }} - {{ if .Date }} {{ .Date }}{{ end }}
    - {{ .Description }} -
  • - {{ end }} -
-
- {{ end }} -
- - - - - diff --git a/templates/static_page.html b/templates/static_page.html new file mode 100644 index 0000000..d30a95c --- /dev/null +++ b/templates/static_page.html @@ -0,0 +1,10 @@ +{{ define "static_page" }} +{{ template "base" . }} +{{ end }} + +{{ define "static_content" }} +
+

{{ .Title }}

+ {{ .Content }} +
+{{ end }}