blog/parse.go

119 lines
3.2 KiB
Go

// parse.go
package main
import (
"bytes"
"fmt"
"path/filepath"
"strings"
"git.else-if.org/jess/blog/templates"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
"go.abhg.dev/goldmark/frontmatter"
)
// GetContent is the primary entry point for retrieving parsed content.
func GetContent(file ContentFile) ([]byte, error) {
// 1. Ask Cache
if data, found := CheckCache(file.OriginalPath); found {
return data, nil
}
// 2. Read Raw
raw, err := ReadRaw(file.OriginalPath)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
// If not markdown (e.g. css, images), return raw bytes immediately
if !file.IsMarkdown {
_ = StoreCache(file.OriginalPath, raw)
return raw, nil
}
// 3. Configure Goldmark
// We use the Extender pattern which is the standard way to add frontmatter support.
// We also enable Unsafe mode to allow raw HTML tags (like <site-headline>) to pass through
// so we can replace them later.
md := goldmark.New(
goldmark.WithExtensions(
extension.GFM,
&frontmatter.Extender{},
),
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
)
// 4. Parse Markdown
var buf bytes.Buffer
ctx := parser.NewContext()
if err := md.Convert(raw, &buf, parser.WithContext(ctx)); err != nil {
return nil, fmt.Errorf("failed to parse markdown: %w", err)
}
// 5. Extract Frontmatter
// We retrieve the metadata using the context after parsing
var meta templates.PageMetadata
d := frontmatter.Get(ctx)
if d != nil {
if err := d.Decode(&meta); err != nil {
// If decoding fails, we just proceed without metadata
fmt.Printf("Warning: failed to decode frontmatter for %s: %v\n", file.OriginalPath, err)
}
}
// If title is missing, try to grab the filename
if meta.Title == "" {
base := filepath.Base(file.OriginalPath)
meta.Title = strings.TrimSuffix(base, filepath.Ext(base))
}
// 6. Special Handling for Index (Dynamic Components)
htmlContent := buf.String()
if file.RoutePath == "/" {
// Generate the list of posts
var posts []templates.PostSnippet
for _, f := range AllContent {
if f.IsMarkdown && f.RoutePath != "/" {
name := filepath.Base(f.OriginalPath)
title := strings.TrimSuffix(name, filepath.Ext(name))
posts = append(posts, templates.PostSnippet{
Title: title,
URL: f.RoutePath,
Date: f.ModTime,
})
}
}
latestPostsHTML := templates.RenderLatestPosts(posts)
dirLink := templates.RenderDirectoryLink()
// Replace custom tags if they exist in the markdown
if strings.Contains(htmlContent, "<latest-posts>") {
htmlContent = strings.ReplaceAll(htmlContent, "<latest-posts>", latestPostsHTML)
} else {
// Fallback: Append if not present
htmlContent = htmlContent + "\n" + dirLink + "\n" + latestPostsHTML + "\n" + dirLink
}
// Handle site-headline if present
// You can add <site-headline>Your Text</site-headline> in your markdown
// For now, we just let it pass through as HTML, or you can add specific replacement logic here.
}
// 7. Build Full Page (HTML Shell)
finalPage := templates.BuildFullPage([]byte(htmlContent), meta)
// 8. Cache
_ = StoreCache(file.OriginalPath, finalPage)
return finalPage, nil
}