improved frontmatter processing, added tests

This commit is contained in:
Daniel Fichtinger 2025-01-02 01:47:49 -05:00
parent 587085df86
commit 4d27581f0a
No known key found for this signature in database
GPG key ID: D1B0947B25420214
4 changed files with 111 additions and 29 deletions

View file

@ -50,32 +50,6 @@ 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) {
// TODO: Also save index of the line where the actual document starts?
f, err := util.ReadFile(p)
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 frontmatter, return nil -- handled by caller
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 {

View file

@ -0,0 +1,81 @@
package builder
import (
"bufio"
"bytes"
"errors"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
func processFrontmatter(p string) (Metadata, int, error) {
f, l, err := readFrontmatter(p)
if err != nil {
return nil, l, err
}
var meta Metadata
// Parse YAML
if err := yaml.Unmarshal(f, &meta); err != nil {
return nil, l, fmt.Errorf("yaml frontmatter could not be parsed: %w", err)
}
return meta, l, nil
}
// readFrontmatter reads the file at `path` and scans
// it for --- delimited frontmatter. It does not attempt
// to parse the data, it only scans for the delimiters.
// It returns the frontmatter contents as a byte array
// and its length in lines.
func readFrontmatter(path string) ([]byte, int, error) {
file, err := os.Open(path)
if err != nil {
return nil, 0, err
}
defer file.Close()
lines := make([]string, 0, 10)
s := bufio.NewScanner(file)
i := 0
delims := 0
for s.Scan() {
l := s.Text()
if l == `---` {
if i > 0 && delims == 0 {
// if --- is not the first line, we
// assume the file does not contain frontmatter
return nil, 0, nil
}
delims += 1
i += 1
if delims == 2 {
break
}
} else {
lines = append(lines, l)
i += 1
}
}
// check whether any errors occurred while scanning
if err := s.Err(); err != nil {
return nil, 0, err
}
if delims == 2 {
l := len(lines)
if l == 0 {
// no valid frontmatter
return nil, 0, errors.New("frontmatter cannot be empty")
}
// convert to byte array
var b bytes.Buffer
for _, line := range lines {
b.WriteString(line + "\n")
}
return b.Bytes(), l, nil
} else {
// not enough delimiters, don't
// treat as frontmatter
return nil, 0, errors.New("frontmatter is missing closing delimiter")
}
}

View file

@ -28,10 +28,13 @@ This is the body of the document.`
} }
// Test the processFrontmatter function with valid frontmatter // Test the processFrontmatter function with valid frontmatter
meta, err := processFrontmatter(tmpfile.Name()) meta, l, err := processFrontmatter(tmpfile.Name())
if err != nil { if err != nil {
t.Fatalf("processFrontmatter failed: %v", err) t.Fatalf("processFrontmatter failed: %v", err)
} }
if l != 2 {
t.Errorf("Expected length 2, got %d", l)
}
if meta["title"] != "Test Title" || meta["description"] != "Test Description" { if meta["title"] != "Test Title" || meta["description"] != "Test Description" {
t.Errorf("Expected title 'Test Title' and description 'Test Description', got title '%s' and description '%s'", meta["title"], meta["description"]) t.Errorf("Expected title 'Test Title' and description 'Test Description', got title '%s' and description '%s'", meta["title"], meta["description"])
@ -41,6 +44,7 @@ This is the body of the document.`
invalidContent := `--- invalidContent := `---
title: "Test Title" title: "Test Title"
description: "Test Description" description: "Test Description"
There is no closing delimiter???
This is the body of the document.` This is the body of the document.`
tmpfile, err = os.CreateTemp("", "testfile") tmpfile, err = os.CreateTemp("", "testfile")
@ -57,7 +61,30 @@ This is the body of the document.`
} }
// Test the processFrontmatter function with invalid frontmatter // Test the processFrontmatter function with invalid frontmatter
_, err = processFrontmatter(tmpfile.Name()) _, _, err = processFrontmatter(tmpfile.Name())
if err == nil {
t.Fatalf("Expected error for invalid frontmatter, got nil")
}
// Create a temporary file with invalid frontmatter
invalidContent = `---
---
This is the body of the document.`
tmpfile, err = os.CreateTemp("", "testfile")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write([]byte(invalidContent)); err != nil {
t.Fatal(err)
}
if err := tmpfile.Close(); err != nil {
t.Fatal(err)
}
// Test the processFrontmatter function with invalid frontmatter
_, _, err = processFrontmatter(tmpfile.Name())
if err == nil { if err == nil {
t.Fatalf("Expected error for invalid frontmatter, got nil") t.Fatalf("Expected error for invalid frontmatter, got nil")
} }

View file

@ -70,7 +70,7 @@ func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, se
hasFrontmatter := false hasFrontmatter := false
if toProcess { if toProcess {
// process its frontmatter here // process its frontmatter here
m, err := processFrontmatter(inPath) m, _, err := processFrontmatter(inPath)
if err != nil { if err != nil {
return err return err
} }