From 463250eb7b346e1eaeb84b6f0243b06235b83947 Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sat, 19 Apr 2025 15:48:56 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feature:=20Wire=20up=20blog=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/about.md | 6 + content/blog/my-first-blog-post.md | 9 + content/index.md | 6 + main.go | 225 +++++++++++++++++----- public/about/index.html | 73 +++---- public/blog/index.html | 50 +++++ public/blog/my-first-blog-post/index.html | 40 ++++ public/index.html | 73 +++---- templates/layout.html | 17 ++ 9 files changed, 379 insertions(+), 120 deletions(-) create mode 100644 content/blog/my-first-blog-post.md create mode 100644 public/blog/index.html create mode 100644 public/blog/my-first-blog-post/index.html diff --git a/content/about.md b/content/about.md index 57c402b..bf43cb3 100644 --- a/content/about.md +++ b/content/about.md @@ -1,3 +1,9 @@ +--- +title: Go SSG - About +description: More About My Go SSG Site. +date: 2025-04-19 +--- + # About Experimenting with Go. Seems pretty cool so far! diff --git a/content/blog/my-first-blog-post.md b/content/blog/my-first-blog-post.md new file mode 100644 index 0000000..584c6b9 --- /dev/null +++ b/content/blog/my-first-blog-post.md @@ -0,0 +1,9 @@ +--- +title: Go SSG - My First Blog Post +description: A short summary of the post. +date: 2025-04-19 +--- + +# *Tap Tap Tap* Is this thing on? + +This is my first blog post! diff --git a/content/index.md b/content/index.md index def2499..561cc44 100644 --- a/content/index.md +++ b/content/index.md @@ -1,3 +1,9 @@ +--- +title: Go SSG - Home +description: My Go SSG Site. +date: 2025-04-19 +--- + # Welcome This is your first static site built with Go! diff --git a/main.go b/main.go index f74ec5a..f232b78 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "html/template" "io/ioutil" @@ -10,102 +11,222 @@ import ( "time" "github.com/russross/blackfriday/v2" + "gopkg.in/yaml.v3" ) -// NavItem represents a link in the navigation menu +// 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 +} + func main() { contentDir := "./content" outputDir := "./public" templateFile := "./templates/layout.html" - tpl, err := parseTemplate(templateFile) + tpl, err := template.ParseFiles(templateFile) if err != nil { fmt.Printf("Template parsing error: %v\n", err) return } - files, err := readContentFiles(contentDir) + files, err := ioutil.ReadDir(contentDir) if err != nil { fmt.Printf("Failed to read content directory: %v\n", err) return } - nav := buildNavigation(files) - + // Collect nav info early + var nav []NavItem for _, file := range files { if filepath.Ext(file.Name()) == ".md" { - err := generateHTML(file, contentDir, outputDir, tpl, nav) - if err != nil { - fmt.Printf("Error generating HTML for %s: %v\n", file.Name(), err) + name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) + url := "/" + if name != "index" { + url = "/" + name + "/" } + nav = append(nav, NavItem{ + Title: strings.Title(name), + URL: url, + }) } } + // Add blog to the main nav + nav = append(nav, NavItem{ + Title: "Blog", + URL: "/blog/", + }) + + // Generate index.html for each static page markdown file + for _, file := range files { + if filepath.Ext(file.Name()) != ".md" { + continue + } + + 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 + + blogDir := filepath.Join(contentDir, "blog") + blogFiles, _ := ioutil.ReadDir(blogDir) + + for _, file := range blogFiles { + if filepath.Ext(file.Name()) != ".md" { + continue + } + + slug := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) + blogPath := filepath.Join(blogDir, file.Name()) + rawContent, err := ioutil.ReadFile(blogPath) + if err != nil { + fmt.Printf("Failed to read blog post %s: %v\n", file.Name(), err) + continue + } + + meta, content := parseFrontMatter(rawContent) + html := blackfriday.Run(content) + + meta.Slug = slug + if meta.Title == "" { + meta.Title = strings.Title(slug) + } + + outPath := filepath.Join(outputDir, "blog", slug, "index.html") + os.MkdirAll(filepath.Dir(outPath), os.ModePerm) + + outFile, err := os.Create(outPath) + if err != nil { + fmt.Printf("Failed to create blog file %s: %v\n", outPath, err) + 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(), + }) + + 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) + } + fmt.Println("✅ Site generation complete.") } -func parseTemplate(templateFile string) (*template.Template, error) { - return template.ParseFiles(templateFile) -} +// parseFrontMatter splits raw markdown into meta + content +func parseFrontMatter(raw []byte) (PageMeta, []byte) { + var meta PageMeta -func readContentFiles(contentDir string) ([]os.FileInfo, error) { - return ioutil.ReadDir(contentDir) -} + // Normalize line endings + raw = bytes.ReplaceAll(raw, []byte("\r\n"), []byte("\n")) -func buildNavigation(files []os.FileInfo) []NavItem { - var nav []NavItem - for _, file := range files { - if filepath.Ext(file.Name()) == ".md" { - name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) - title := strings.Title(name) - url := "/" - if name != "index" { - url = "/" + name + "/" + // 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 + err := yaml.Unmarshal(parts[1], &meta) + if err != nil { + fmt.Printf("⚠️ Failed to parse front matter: %v\n", err) } - nav = append(nav, NavItem{Title: title, URL: url}) + return meta, parts[2] } } - return nav + + // If there's no front matter, return raw as-is + return meta, raw } -func generateHTML(file os.FileInfo, contentDir, outputDir string, tpl *template.Template, nav []NavItem) error { - name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) - title := strings.Title(name) - - mdPath := filepath.Join(contentDir, file.Name()) - mdContent, err := ioutil.ReadFile(mdPath) - if err != nil { - return fmt.Errorf("failed to read %s: %w", file.Name(), err) - } - - htmlContent := blackfriday.Run(mdContent) - - 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") - } +// buildBlogIndex generates the blog index page +func buildBlogIndex(posts []PageMeta, tpl *template.Template, outputDir string, nav []NavItem) error { + outPath := filepath.Join(outputDir, "blog", "index.html") + os.MkdirAll(filepath.Dir(outPath), os.ModePerm) outFile, err := os.Create(outPath) if err != nil { - return fmt.Errorf("failed to create %s: %w", outPath, err) + return err } - defer outFile.Close() data := map[string]interface{}{ - "Content": template.HTML(htmlContent), - "Title": title, - "Nav": nav, - "Year": time.Now().Year(), - "Date": time.Now().Format("January 2, 2006"), + "Title": "Go SSG - Blog", + "Posts": posts, + "Nav": nav, + "Year": time.Now().Year(), } return tpl.Execute(outFile, data) diff --git a/public/about/index.html b/public/about/index.html index 55cb9ad..9e0d152 100644 --- a/public/about/index.html +++ b/public/about/index.html @@ -1,35 +1,40 @@ - - - - - - About - - - -
- -
- -
-

About

+ + -

-Experimenting with Go. Seems pretty cool so far!

- -
- - - - - + + + Go SSG - About + + + + +
+ +
+ +
+ Published: 2025-04-19 +

About

+ +

Experimenting with Go. Seems pretty cool so far!

+ + + +
+ + + + + diff --git a/public/blog/index.html b/public/blog/index.html new file mode 100644 index 0000000..dd864b5 --- /dev/null +++ b/public/blog/index.html @@ -0,0 +1,50 @@ + + + + + + Go SSG - Blog + + + + +
+ +
+ +
+ + + + +
+

Blog Posts

+ +
+ +
+ + + + + diff --git a/public/blog/my-first-blog-post/index.html b/public/blog/my-first-blog-post/index.html new file mode 100644 index 0000000..e1cd3d5 --- /dev/null +++ b/public/blog/my-first-blog-post/index.html @@ -0,0 +1,40 @@ + + + + + + Go SSG - My First Blog Post + + + + +
+ +
+ +
+ Published: 2025-04-19 +

Tap Tap Tap Is this thing on?

+ +

This is my first blog post!

+ + + +
+ + + + + diff --git a/public/index.html b/public/index.html index e173b53..6039a8e 100644 --- a/public/index.html +++ b/public/index.html @@ -1,35 +1,40 @@ - - - - - - Index - - - -
- -
- -
-

Welcome

+ + -

-This is your first static site built with Go!

- -
- - - - - + + + Go SSG - Home + + + + +
+ +
+ +
+ Published: 2025-04-19 +

Welcome

+ +

This is your first static site built with Go!

+ + + +
+ + + + + diff --git a/templates/layout.html b/templates/layout.html index 8d904bc..06efa66 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -4,6 +4,7 @@ {{ .Title }} + {{ if .Description }}{{ end }} @@ -18,7 +19,23 @@
+ {{ if .Date }}Published: {{ .Date }}{{ end }} {{ .Content }} + + {{ if .Posts }} +
+

Blog Posts

+
    + {{ range .Posts }} +
  • + {{ .Title }} + {{ if .Date }} {{ .Date }}{{ end }}
    + {{ .Description }} +
  • + {{ end }} +
+
+ {{ end }}