diff --git a/internal/builder/build_page.go b/internal/builder/build_page.go index b3bb817..0183871 100644 --- a/internal/builder/build_page.go +++ b/internal/builder/build_page.go @@ -24,6 +24,7 @@ type PageData struct { FooterName string Footer template.HTML Template string + Type string } type Metadata map[string]interface{} @@ -49,6 +50,32 @@ func processWithYaml(f []byte) (Metadata, []byte, error) { 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 { p := &PageData{} 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 if t, ok := m["type"].(string); util.InDir(in, "posts") && !ok || (ok && t == "article" || t == "post") { p.Template = (settings.ArticleTemplate) + p.Type = "post" } else { p.Template = (settings.DefaultTemplate) + p.Type = "" } return p } diff --git a/internal/builder/build_page_test.go b/internal/builder/build_page_test.go new file mode 100644 index 0000000..3785faa --- /dev/null +++ b/internal/builder/build_page_test.go @@ -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) + } + }) + } +} diff --git a/internal/builder/process.go b/internal/builder/process.go new file mode 100644 index 0000000..5f57055 --- /dev/null +++ b/internal/builder/process.go @@ -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 +} diff --git a/internal/builder/traverse.go b/internal/builder/traverse.go index 5272dca..824df5a 100644 --- a/internal/builder/traverse.go +++ b/internal/builder/traverse.go @@ -8,7 +8,8 @@ import ( "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 { 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 { 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) return err diff --git a/internal/util/file.go b/internal/util/file.go index 2028018..e164319 100644 --- a/internal/util/file.go +++ b/internal/util/file.go @@ -1,6 +1,8 @@ package util import ( + "bufio" + "bytes" "io" "os" ) @@ -56,3 +58,25 @@ func CopyFile(inPath string, outPath string) error { 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 +} diff --git a/internal/util/queue.go b/internal/util/queue.go new file mode 100644 index 0000000..3fce4fa --- /dev/null +++ b/internal/util/queue.go @@ -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 + } +}