improved frontmatter processing, added tests
This commit is contained in:
parent
587085df86
commit
4d27581f0a
4 changed files with 111 additions and 29 deletions
|
@ -50,32 +50,6 @@ func processWithYaml(f []byte) (Metadata, []byte, error) {
|
|||
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 {
|
||||
p := &PageData{}
|
||||
if title, ok := m["title"].(string); ok {
|
||||
|
|
81
internal/builder/frontmatter.go
Normal file
81
internal/builder/frontmatter.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -28,10 +28,13 @@ This is the body of the document.`
|
|||
}
|
||||
|
||||
// Test the processFrontmatter function with valid frontmatter
|
||||
meta, err := processFrontmatter(tmpfile.Name())
|
||||
meta, l, err := processFrontmatter(tmpfile.Name())
|
||||
if err != nil {
|
||||
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" {
|
||||
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 := `---
|
||||
title: "Test Title"
|
||||
description: "Test Description"
|
||||
There is no closing delimiter???
|
||||
This is the body of the document.`
|
||||
|
||||
tmpfile, err = os.CreateTemp("", "testfile")
|
||||
|
@ -57,7 +61,30 @@ This is the body of the document.`
|
|||
}
|
||||
|
||||
// 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 {
|
||||
t.Fatalf("Expected error for invalid frontmatter, got nil")
|
||||
}
|
|
@ -70,7 +70,7 @@ func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, se
|
|||
hasFrontmatter := false
|
||||
if toProcess {
|
||||
// process its frontmatter here
|
||||
m, err := processFrontmatter(inPath)
|
||||
m, _, err := processFrontmatter(inPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue