begin implementing separate metadata parsing

This commit is contained in:
Daniel Fichtinger 2024-12-31 22:47:01 -05:00
parent 709a2738f9
commit 35c14f09c0
6 changed files with 192 additions and 2 deletions

View file

@ -24,6 +24,7 @@ type PageData struct {
FooterName string FooterName string
Footer template.HTML Footer template.HTML
Template string Template string
Type string
} }
type Metadata map[string]interface{} type Metadata map[string]interface{}
@ -49,6 +50,32 @@ func processWithYaml(f []byte) (Metadata, []byte, error) {
return meta, []byte(split[2]), nil return meta, []byte(split[2]), nil
} }
func processFrontmatter(p string) (Metadata, error) {
// read only the first three lines
f, err := util.ReadNLines(p, 3)
if err != nil {
return nil, err
}
// Check if the file has valid metadata
trimmed := bytes.TrimSpace(f)
normalized := strings.ReplaceAll(string(trimmed), "\r\n", "\n")
if !strings.HasPrefix(normalized, ("---\n")) {
// No valid yaml, so return the entire content
return nil, nil
}
// Separate YAML from rest of document
split := strings.SplitN(normalized, "---\n", 3)
if len(split) < 3 {
return nil, fmt.Errorf("Invalid frontmatter format.")
}
var meta Metadata
// Parse YAML
if err := yaml.Unmarshal([]byte(split[1]), &meta); err != nil {
return nil, err
}
return meta, nil
}
func buildPageData(m Metadata, in string, out string, settings *Settings) *PageData { func buildPageData(m Metadata, in string, out string, settings *Settings) *PageData {
p := &PageData{} p := &PageData{}
if title, ok := m["title"].(string); ok { if title, ok := m["title"].(string); ok {
@ -92,8 +119,10 @@ func buildPageData(m Metadata, in string, out string, settings *Settings) *PageD
// TODO: Don't hard code posts dir name // TODO: Don't hard code posts dir name
if t, ok := m["type"].(string); util.InDir(in, "posts") && !ok || (ok && t == "article" || t == "post") { if t, ok := m["type"].(string); util.InDir(in, "posts") && !ok || (ok && t == "article" || t == "post") {
p.Template = (settings.ArticleTemplate) p.Template = (settings.ArticleTemplate)
p.Type = "post"
} else { } else {
p.Template = (settings.DefaultTemplate) p.Template = (settings.DefaultTemplate)
p.Type = ""
} }
return p return p
} }

View file

@ -0,0 +1,37 @@
package builder
import "testing"
func Test_processWithYaml(t *testing.T) {
tests := []struct {
name string // description of this test case
// Named input parameters for target function.
f []byte
want Metadata
want2 []byte
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got2, gotErr := processWithYaml(tt.f)
if gotErr != nil {
if !tt.wantErr {
t.Errorf("processWithYaml() failed: %v", gotErr)
}
return
}
if tt.wantErr {
t.Fatal("processWithYaml() succeeded unexpectedly")
}
// TODO: update the condition below to compare got with tt.want.
if true {
t.Errorf("processWithYaml() = %v, want %v", got, tt.want)
}
if true {
t.Errorf("processWithYaml() = %v, want %v", got2, tt.want2)
}
})
}
}

View file

@ -0,0 +1,73 @@
package builder
import (
"io/fs"
"path/filepath"
"github.com/ficcdaf/zona/internal/util"
)
type ProcessMemory struct {
// Pages holds all page data that may be
// needed while building *other* pages.
Pages []*Page
// Queue is a FIFO queue of Pages indexes to be built.
// queue should be constructed after all the Pages have been parsed
Queue []int
// Posts is an array of pointers to post pages
Posts []*Page
}
type Page struct {
Data *PageData
Ext string
InPath string
OutPath string
Copy bool
}
// processFile processes the metadata only
// of each file
func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, settings *Settings, pm *ProcessMemory) error {
if err != nil {
return err
}
var toProcess bool
var outPath string
var ext string
if !entry.IsDir() {
ext = filepath.Ext(inPath)
outPath = util.ReplaceRoot(inPath, outRoot)
switch ext {
case ".md":
// fmt.Println("Processing markdown...")
toProcess = true
outPath = util.ChangeExtension(outPath, ".html")
// If it's not a file we need to process,
// we simply copy it to the destination path.
default:
toProcess = false
}
}
page := &Page{
nil,
ext,
inPath,
outPath,
!toProcess,
}
if toProcess {
// process its frontmatter here
m, err := processFrontmatter(inPath)
if err != nil {
return err
}
pd := buildPageData(m, inPath, outPath, settings)
if pd.Type == "post" {
pm.Posts = append(pm.Posts, page)
}
page.Data = pd
}
pm.Pages = append(pm.Pages, page)
return nil
}

View file

@ -8,7 +8,8 @@ import (
"github.com/ficcdaf/zona/internal/util" "github.com/ficcdaf/zona/internal/util"
) )
func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, settings *Settings) error { // TODO: Process the metadata and build a queue of files to convert here instead of converting them immediately
func buildFile(inPath string, entry fs.DirEntry, err error, outRoot string, settings *Settings) error {
if err != nil { if err != nil {
return err return err
} }
@ -46,7 +47,7 @@ func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, se
func Traverse(root string, outRoot string, settings *Settings) error { func Traverse(root string, outRoot string, settings *Settings) error {
walkFunc := func(path string, entry fs.DirEntry, err error) error { walkFunc := func(path string, entry fs.DirEntry, err error) error {
return processFile(path, entry, err, outRoot, settings) return buildFile(path, entry, err, outRoot, settings)
} }
err := filepath.WalkDir(root, walkFunc) err := filepath.WalkDir(root, walkFunc)
return err return err

View file

@ -1,6 +1,8 @@
package util package util
import ( import (
"bufio"
"bytes"
"io" "io"
"os" "os"
) )
@ -56,3 +58,25 @@ func CopyFile(inPath string, outPath string) error {
return nil return nil
} }
} }
// ReadNLines reads the first N lines from a file as a single byte array
func ReadNLines(filename string, n int) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var buffer bytes.Buffer
scanner := bufio.NewScanner(file)
for i := 0; i < 3 && scanner.Scan(); i++ {
buffer.Write(scanner.Bytes())
buffer.WriteByte('\n')
}
if err := scanner.Err(); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}

26
internal/util/queue.go Normal file
View file

@ -0,0 +1,26 @@
package util
// Enqueue appends an int to the queue
func Enqueue(queue []int, element int) []int {
queue = append(queue, element)
return queue
}
// Dequeue pops the first element of the queue
func Dequeue(queue []int) (int, []int) {
element := queue[0] // The first element is the one to be dequeued.
if len(queue) == 1 {
tmp := []int{}
return element, tmp
}
return element, queue[1:] // Slice off the element once it is dequeued.
}
func Tail(queue []int) int {
l := len(queue)
if l == 0 {
return -1
} else {
return l - 1
}
}