Files
go-cv/content.go
Bryan Joshua Pedini 4779e63132 feat: implement PDF generation and update Dockerfile
- Add PDF generation using go-pdf/fpdf (Pure Go library)
- Walk markdown AST and render directly to PDF
- Update Dockerfile to include themes directory and config
- Fix ENTRYPOINT format for serve mode

Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
2026-03-05 02:33:37 +01:00

138 lines
3.4 KiB
Go

package main
import (
"bytes"
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"github.com/yuin/goldmark"
)
// ContentFile represents a parsed content file
type ContentFile struct {
SourcePath string // Original .md file path
Name string // File name without extension
Content string // Raw markdown content
HTML string // Converted HTML
}
// PageData represents data passed to the template
type PageData struct {
Title string
Content template.HTML
}
// generateOutput reads content from ./content, processes it, and writes to ./output
func generateOutput() error {
// Ensure content directory exists
if _, err := os.Stat(contentPath); os.IsNotExist(err) {
return fmt.Errorf("content directory does not exist: %s", contentPath)
}
// Ensure output directory exists
if err := os.MkdirAll(outputPath, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Load the theme template
themePath := filepath.Join("themes", ws.Theme, "base.html")
tmpl, err := template.ParseFiles(themePath)
if err != nil {
return fmt.Errorf("failed to load theme template %s: %w", themePath, err)
}
// Read all markdown files from content directory
files, err := readContentFiles()
if err != nil {
return fmt.Errorf("failed to read content files: %w", err)
}
if len(files) == 0 {
fmt.Println("Warning: No markdown files found in content directory")
return nil
}
// Process each file
for _, file := range files {
fmt.Printf("Processing: %s\n", file.SourcePath)
// Convert markdown to HTML
html, err := markdownToHTML(file.Content)
if err != nil {
return fmt.Errorf("failed to convert %s: %w", file.SourcePath, err)
}
file.HTML = html
// Apply theme template
var output bytes.Buffer
data := PageData{
Title: file.Name + " - " + ws.AppName,
Content: template.HTML(file.HTML),
}
if err := tmpl.Execute(&output, data); err != nil {
return fmt.Errorf("failed to execute template for %s: %w", file.SourcePath, err)
}
// Write output
outputFile := filepath.Join(outputPath, file.Name+".html")
if err := os.WriteFile(outputFile, output.Bytes(), 0644); err != nil {
return fmt.Errorf("failed to write %s: %w", outputFile, err)
}
fmt.Printf(" -> Written: %s\n", outputFile)
// Generate PDF
if err := generatePDF(file.Content, file.Name); err != nil {
fmt.Printf(" -> PDF generation failed: %s\n", err)
}
}
return nil
}
// readContentFiles reads all .md files from the content directory
func readContentFiles() ([]ContentFile, error) {
var files []ContentFile
entries, err := os.ReadDir(contentPath)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasSuffix(name, ".md") {
continue
}
fullPath := filepath.Join(contentPath, name)
content, err := os.ReadFile(fullPath)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", fullPath, err)
}
files = append(files, ContentFile{
SourcePath: fullPath,
Name: strings.TrimSuffix(name, ".md"),
Content: string(content),
})
}
return files, nil
}
// markdownToHTML converts markdown content to HTML using goldmark
func markdownToHTML(markdown string) (string, error) {
var buf bytes.Buffer
if err := goldmark.Convert([]byte(markdown), &buf); err != nil {
return "", err
}
return buf.String(), nil
}