diff --git a/cmd/zona/main.go b/cmd/zona/main.go index d61da6c..5409934 100644 --- a/cmd/zona/main.go +++ b/cmd/zona/main.go @@ -42,7 +42,8 @@ func main() { } settings := builder.GetSettings(*rootPath, "foobar") - err := builder.Traverse(*rootPath, "foobar", settings) + // err := builder.Traverse(*rootPath, "foobar", settings) + err := builder.ProcessTraverse(*rootPath, "foobar", settings) if err != nil { fmt.Printf("Error: %s\n", err.Error()) } diff --git a/internal/builder/build_page.go b/internal/builder/build_page.go index 0183871..03b01e0 100644 --- a/internal/builder/build_page.go +++ b/internal/builder/build_page.go @@ -51,8 +51,8 @@ func processWithYaml(f []byte) (Metadata, []byte, error) { } func processFrontmatter(p string) (Metadata, error) { - // read only the first three lines - f, err := util.ReadNLines(p, 3) + // TODO: Also save index of the line where the actual document starts? + f, err := util.ReadFile(p) if err != nil { return nil, err } @@ -60,11 +60,13 @@ func processFrontmatter(p string) (Metadata, error) { 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 + // No frontmatter, return nil -- handled by caller return nil, nil } // Separate YAML from rest of document split := strings.SplitN(normalized, "---\n", 3) + // __AUTO_GENERATED_PRINT_VAR_START__ + fmt.Println(fmt.Sprintf("processFrontmatter split: %v", split)) // __AUTO_GENERATED_PRINT_VAR_END__ if len(split) < 3 { return nil, fmt.Errorf("Invalid frontmatter format.") } @@ -127,7 +129,7 @@ func buildPageData(m Metadata, in string, out string, settings *Settings) *PageD return p } -func ConvertFile(in string, out string, settings *Settings) error { +func BuildHtmlFile(in string, out string, settings *Settings) error { mdPre, err := util.ReadFile(in) if err != nil { return err diff --git a/internal/builder/build_page_test.go b/internal/builder/build_page_test.go index 3785faa..cf0a311 100644 --- a/internal/builder/build_page_test.go +++ b/internal/builder/build_page_test.go @@ -1,37 +1,64 @@ +// FILE: internal/builder/build_page_test.go package builder -import "testing" +import ( + "os" + "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. +func TestProcessFrontmatter(t *testing.T) { + // Create a temporary file with valid frontmatter + validContent := `--- +title: "Test Title" +description: "Test Description" +--- +This is the body of the document.` + + tmpfile, err := os.CreateTemp("", "testfile") + if err != nil { + t.Fatal(err) } - 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) - } - }) + defer os.Remove(tmpfile.Name()) // clean up + + if _, err := tmpfile.Write([]byte(validContent)); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + // Test the processFrontmatter function with valid frontmatter + meta, err := processFrontmatter(tmpfile.Name()) + if err != nil { + t.Fatalf("processFrontmatter failed: %v", err) + } + + 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"]) + } + + // Create a temporary file with invalid frontmatter + invalidContent := `--- +title: "Test Title" +description: "Test Description" +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") } } diff --git a/internal/builder/convert_test.go b/internal/builder/convert_test.go deleted file mode 100644 index 45c4843..0000000 --- a/internal/builder/convert_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package builder_test - -import ( - "os" - "path/filepath" - "testing" - - "github.com/ficcdaf/zona/internal/builder" - "github.com/ficcdaf/zona/internal/util" -) - -func TestMdToHTML(t *testing.T) { - md := []byte("# Hello World\n\nThis is a test.") - expectedHTML := "
This is a test.
\n" - nExpectedHTML := util.NormalizeContent(expectedHTML) - html, err := builder.MdToHTML(md) - nHtml := util.NormalizeContent(string(html)) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - if nHtml != nExpectedHTML { - t.Errorf("Expected:\n%s\nGot:\n%s", expectedHTML, html) - } -} - -func TestWriteFile(t *testing.T) { - path := filepath.Join(t.TempDir(), "test.txt") - content := []byte("Hello, World!") - - err := builder.WriteFile(content, path) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - // Verify file content - data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("Error reading file: %v", err) - } - if string(data) != string(content) { - t.Errorf("Expected:\n%s\nGot:\n%s", content, data) - } -} - -func TestReadFile(t *testing.T) { - path := filepath.Join(t.TempDir(), "test.txt") - content := []byte("Hello, World!") - - err := os.WriteFile(path, content, 0644) - if err != nil { - t.Fatalf("Error writing file: %v", err) - } - - data, err := builder.ReadFile(path) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - if string(data) != string(content) { - t.Errorf("Expected:\n%s\nGot:\n%s", content, data) - } -} - -func TestCopyFile(t *testing.T) { - src := filepath.Join(t.TempDir(), "source.txt") - dst := filepath.Join(t.TempDir(), "dest.txt") - content := []byte("File content for testing.") - - err := os.WriteFile(src, content, 0644) - if err != nil { - t.Fatalf("Error writing source file: %v", err) - } - - err = builder.CopyFile(src, dst) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - // Verify destination file content - data, err := os.ReadFile(dst) - if err != nil { - t.Fatalf("Error reading destination file: %v", err) - } - if string(data) != string(content) { - t.Errorf("Expected:\n%s\nGot:\n%s", content, data) - } -} - -func TestConvertFile(t *testing.T) { - src := filepath.Join(t.TempDir(), "test.md") - dst := filepath.Join(t.TempDir(), "test.html") - mdContent := []byte("# Test Title\n\nThis is Markdown content.") - nExpectedHTML := util.NormalizeContent("This is Markdown content.
\n") - - err := os.WriteFile(src, mdContent, 0644) - if err != nil { - t.Fatalf("Error writing source Markdown file: %v", err) - } - - err = builder.ConvertFile(src, dst) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - // Verify destination HTML content - data, err := os.ReadFile(dst) - if err != nil { - t.Fatalf("Error reading HTML file: %v", err) - } - if util.NormalizeContent(string(data)) != nExpectedHTML { - t.Errorf("Expected:\n%s\nGot:\n%s", nExpectedHTML, data) - } -} - -func TestChangeExtension(t *testing.T) { - input := "test.md" - output := builder.ChangeExtension(input, ".html") - expected := "test.html" - - if output != expected { - t.Errorf("Expected %s, got %s", expected, output) - } -} diff --git a/internal/builder/process.go b/internal/builder/process.go index 5f57055..260be0a 100644 --- a/internal/builder/process.go +++ b/internal/builder/process.go @@ -8,22 +8,39 @@ import ( ) type ProcessMemory struct { - // Pages holds all page data that may be + // Files holds all page data that may be // needed while building *other* pages. - Pages []*Page + Files []*File // 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 + // This list is ONLY referenced for generating + // the archive, NOT by the build process! + Posts []*File } -type Page struct { - Data *PageData - Ext string - InPath string - OutPath string - Copy bool +type File struct { + Data *PageData + Ext string + InPath string + OutPath string + ShouldCopy bool + HasFrontmatter bool +} + +// NewProcessMemory initializes an empty +// process memory structure +func NewProcessMemory() *ProcessMemory { + f := make([]*File, 0) + q := make([]int, 0) + p := make([]*File, 0) + pm := &ProcessMemory{ + f, + q, + p, + } + return pm } // processFile processes the metadata only @@ -38,36 +55,44 @@ func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, se if !entry.IsDir() { ext = filepath.Ext(inPath) outPath = util.ReplaceRoot(inPath, outRoot) + // NOTE: This could be an if statement, but keeping + // the switch makes it easy to extend the logic here later 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, - } + + var pd *PageData + hasFrontmatter := false 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) + if m != nil { + hasFrontmatter = true } - page.Data = pd + pd = buildPageData(m, inPath, outPath, settings) + + } else { + pd = nil } - pm.Pages = append(pm.Pages, page) + file := &File{ + pd, + ext, + inPath, + outPath, + !toProcess, + hasFrontmatter, + } + if pd != nil && pd.Type == "post" { + pm.Posts = append(pm.Posts, file) + } + pm.Files = append(pm.Files, file) return nil } diff --git a/internal/builder/traverse.go b/internal/builder/traverse.go index 824df5a..d397f39 100644 --- a/internal/builder/traverse.go +++ b/internal/builder/traverse.go @@ -23,7 +23,7 @@ func buildFile(inPath string, entry fs.DirEntry, err error, outRoot string, sett if err := util.CreateParents(outPath); err != nil { return err } - if err := ConvertFile(inPath, outPath, settings); err != nil { + if err := BuildHtmlFile(inPath, outPath, settings); err != nil { return errors.Join(errors.New("Error processing file "+inPath), err) } else { return nil @@ -52,3 +52,12 @@ func Traverse(root string, outRoot string, settings *Settings) error { err := filepath.WalkDir(root, walkFunc) return err } + +func ProcessTraverse(root string, outRoot string, settings *Settings) error { + pm := NewProcessMemory() + walkFunc := func(path string, entry fs.DirEntry, err error) error { + return processFile(path, entry, err, outRoot, settings, pm) + } + err := filepath.WalkDir(root, walkFunc) + return err +} diff --git a/internal/util/file_test.go b/internal/util/file_test.go new file mode 100644 index 0000000..be3131d --- /dev/null +++ b/internal/util/file_test.go @@ -0,0 +1,37 @@ +// FILE: internal/util/file_test.go +package util + +import ( + "bytes" + "os" + "testing" +) + +func TestReadNLines(t *testing.T) { + // Create a temporary file + tmpfile, err := os.CreateTemp("", "testfile") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) // clean up + + // Write some lines to the temporary file + content := []byte("line1\nline2\nline3\nline4\nline5\n") + if _, err := tmpfile.Write(content); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + // Test the ReadNLines function + lines, err := ReadNLines(tmpfile.Name(), 3) + if err != nil { + t.Fatalf("ReadNLines failed: %v", err) + } + + expected := []byte("line1\nline2\nline3\n") + if !bytes.Equal(lines, expected) { + t.Errorf("Expected %q, got %q", expected, lines) + } +}