Files
blog/parse.go
2025-10-30 10:00:54 +00:00

128 lines
3.7 KiB
Go

package main
import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
"github.com/niklasfasching/go-org/org"
)
// Post represents a blog post with metadata and content.
type Post struct {
Title string // Title of the post
Slug string // URL-friendly identifier for the post
Tags []string // Tags associated with the post
Description string // Brief description of the post
Date time.Time // Date when the post was published
DateStr string // Date when the post was published (YYYY-MM-DD)
Timestamp int64 // Unix timestamp of the publication date
Path string // File path to the original .org file
PathHtml string // URL path to the rendered HTML file
Content *org.Document // Parsed content of the post
ReadTime uint8 // Estimated reading time in minutes
Hero string // URL path to the hero image for the post
}
// listPosts reads the posts directory and returns a slice of Post structs.
// It filters out non-.org files, parses each .org file, and sorts the posts by date in descending order.
// Returns the slice of posts and any error encountered during the process.
func listPosts() ([]Post, error) {
entries, err := os.ReadDir("posts")
if err != nil {
fmt.Println("Error reading directory:", err)
return nil, err
}
entries = filter(entries, func(e os.DirEntry) bool { return filepath.Ext(e.Name()) == ".org" })
var posts []Post
for _, entry := range entries {
filePath := filepath.Join("posts", entry.Name())
post, err := parseOrg(filePath)
if err != nil {
log.Println("[!] Unable to parse ", filePath)
} else {
posts = append(posts, post)
}
}
sort.Slice(posts, func(i, j int) bool {
return posts[i].Timestamp > posts[j].Timestamp
})
return posts, nil
}
// handleImages processes image and video links in the org document.
// It updates the URL of the link to point to the media directory.
// Parameters:
// - protocol: The protocol of the link (e.g., "file", "http").
// - description: The description of the link.
// - link: The URL of the link.
//
// Returns:
// - The processed link node.
func handleImages(protocol string, description []org.Node, link string) org.Node {
linked := org.RegularLink{protocol, description, link, false}
if linked.Kind() == "image" || linked.Kind() == "video" {
linked.URL = path.Join("/medias/", linked.URL)
}
return linked
}
// parseOrg parses an org file and returns a Post struct.
// It reads the file, extracts metadata, and calculates the reading time.
// Parameters:
// - filePath: The path to the org file.
//
// Returns:
// - The parsed Post struct.
// - Any error encountered during the process.
func parseOrg(filePath string) (Post, error) {
file, err := os.Open(filePath)
if err != nil {
log.Fatal("Error reading file")
return Post{}, err
}
config := org.New()
config.ResolveLink = handleImages
orgData := config.Parse(file, filePath)
title := orgData.Get("TITLE")
description := orgData.Get("DESCRIPTION")
dateStr := strings.Split(orgData.Get("DATE"), "T")[0]
slug := orgData.Get("SLUG")
tags := strings.Split(orgData.Get("TAGS"), ", ")
hero := path.Join("/medias", orgData.Get("HERO"))
date, _ := time.Parse("2006-01-02", dateStr)
ts := date.Unix()
raw, _ := os.ReadFile(filePath)
readTime := len(strings.Split(string(raw), " ")) / 200
return Post{
Title: title,
Slug: slug,
Tags: tags,
Description: description,
Date: date,
DateStr: date.Format("2006-01-02"),
Timestamp: ts,
Path: filePath,
PathHtml: "/posts/" + slug + ".html",
Content: orgData,
ReadTime: uint8(readTime),
Hero: hero,
}, nil
}