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
|
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 {
|
||||||
|
|
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
|
// 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")
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue