163 lines
4.4 KiB
Go
163 lines
4.4 KiB
Go
// parse.go
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"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"
|
|
)
|
|
|
|
// GetContentPath ensures the content is cached and returns the path to the cached file.
|
|
func GetContentPath(file ContentFile) (string, error) {
|
|
cachePath := GetCacheFilename(file.OriginalPath)
|
|
|
|
// 1. Check Cache Freshness (if not in TestMode)
|
|
if !TestMode {
|
|
cacheInfo, errCache := os.Stat(cachePath)
|
|
origInfo, errOrig := os.Stat(file.OriginalPath)
|
|
|
|
if errCache == nil && errOrig == nil && cacheInfo.ModTime().After(origInfo.ModTime()) {
|
|
return cachePath, nil
|
|
}
|
|
}
|
|
|
|
// 2. Regenerate Content
|
|
cacheMutex.Lock()
|
|
defer cacheMutex.Unlock()
|
|
|
|
if !TestMode {
|
|
cacheInfo, errCache := os.Stat(cachePath)
|
|
origInfo, errOrig := os.Stat(file.OriginalPath)
|
|
if errCache == nil && errOrig == nil && cacheInfo.ModTime().After(origInfo.ModTime()) {
|
|
return cachePath, nil
|
|
}
|
|
}
|
|
|
|
// Read Raw File
|
|
raw, err := ReadRaw(file.OriginalPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read file: %w", err)
|
|
}
|
|
|
|
var dataToWrite []byte
|
|
|
|
if !file.IsMarkdown {
|
|
dataToWrite = raw
|
|
} else {
|
|
// Configure Goldmark with the custom tag extension
|
|
md := goldmark.New(
|
|
goldmark.WithExtensions(
|
|
extension.GFM,
|
|
&frontmatter.Extender{},
|
|
templates.SidebarTag, // ||| -> <sidebar>
|
|
templates.TopBanner,
|
|
),
|
|
goldmark.WithRendererOptions(
|
|
html.WithUnsafe(),
|
|
),
|
|
)
|
|
|
|
var buf bytes.Buffer
|
|
ctx := parser.NewContext()
|
|
if err := md.Convert(raw, &buf, parser.WithContext(ctx)); err != nil {
|
|
return "", fmt.Errorf("failed to parse markdown: %w", err)
|
|
}
|
|
|
|
// Extract Frontmatter
|
|
var meta templates.PageMetadata
|
|
d := frontmatter.Get(ctx)
|
|
if d != nil {
|
|
if err := d.Decode(&meta); err != nil {
|
|
fmt.Printf("Warning: failed to decode frontmatter for %s: %v\n", file.OriginalPath, err)
|
|
}
|
|
}
|
|
|
|
if meta.Title == "" {
|
|
base := filepath.Base(file.OriginalPath)
|
|
meta.Title = strings.TrimSuffix(base, filepath.Ext(base))
|
|
}
|
|
|
|
htmlContent := buf.String()
|
|
|
|
// 3. Post-Process for Index (Dynamic Content Injection)
|
|
if file.RoutePath == "/" {
|
|
// Generate posts data
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
Add More Custom tags here. Add a customTag in tags.go as well, and the implementation in a template file.
|
|
*/
|
|
|
|
// ||| SideBar
|
|
sidebarRegex := regexp.MustCompile(`(?s)<sidebar>.*?</sidebar>`)
|
|
|
|
if sidebarRegex.MatchString(htmlContent) {
|
|
// Render the dynamic content
|
|
latestPostsHTML := templates.RenderLatestPosts(posts)
|
|
dirLink := templates.RenderDirectoryLink()
|
|
|
|
// Wrap in <div class="sidebar"> to match CSS
|
|
replacement := fmt.Sprintf(`<div class="sidebar">%s%s</div>`, latestPostsHTML, dirLink)
|
|
|
|
// Replace the placeholder tag with the actual content
|
|
htmlContent = sidebarRegex.ReplaceAllString(htmlContent, replacement)
|
|
}
|
|
|
|
// _-_- TopBanner
|
|
topbannerRegex := regexp.MustCompile(`(?s)<topbanner>.*?</topbanner>`)
|
|
|
|
if topbannerRegex.MatchString(htmlContent) {
|
|
htmlContent = topbannerRegex.ReplaceAllStringFunc(htmlContent, func(match string) string {
|
|
// 'match' is "<topbanner>...content...</topbanner>"
|
|
|
|
// Remove the known tags safely
|
|
innerContent := strings.TrimPrefix(match, "<topbanner>")
|
|
innerContent = strings.TrimSuffix(innerContent, "</topbanner>")
|
|
|
|
// Pass to template
|
|
bannerHTML := templates.RenderTopBanner(innerContent)
|
|
|
|
// Return the formatted HTML
|
|
return fmt.Sprintf(`<div class="topbanner">%s</div>`, bannerHTML)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Build Full Page
|
|
dataToWrite = templates.BuildFullPage([]byte(htmlContent), meta)
|
|
}
|
|
|
|
// 4. Write to Cache
|
|
tmpPath := cachePath + ".tmp"
|
|
if err := os.WriteFile(tmpPath, dataToWrite, 0644); err != nil {
|
|
return "", fmt.Errorf("failed to write cache: %w", err)
|
|
}
|
|
if err := os.Rename(tmpPath, cachePath); err != nil {
|
|
return "", fmt.Errorf("failed to commit cache: %w", err)
|
|
}
|
|
|
|
return cachePath, nil
|
|
}
|