diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 005efcf..0000000 --- a/TODO.md +++ /dev/null @@ -1,52 +0,0 @@ -# TO-DO - -- **First**, re-write the settings & configuration system from scratch! It's - broken and messy and not worth trying to fix like this. Instead, actually - architect how it should work, _then_ implement it. -- Refactor the directory structure processing - - Implement zola-style structure instead - - `zona init` command to populate the required files, _with_ defaults - (unlike zola) - - Interactive for setting values, also an option to create `.gitignore` - with `public` in it. - - `zona.yml` is **required** and should mark the root: - - `templates`, `content`, `static`, `zona.yml` - - multiple `zona.yml` files should be an error - - if the folder containing `zona.yml` doesn't contain _exactly_ the - expected directories and files, it's an error - - Paths in page metadata should start at these folders - - i.e. `(template|footer|header): name.html` → `root/templates/name.html` - - `(style|icon): name.ext` → `root/static/name.ext` - - Traverse `content` and `static` separately, applying different rules - - everything in `static/**` should be directly copied - - `content/**` should be processed - - `*.md` converted, everything else copied directly - - `./name.md` → ./name/index.html - - Either `./name.md` or `./name/index.md` are valid, _together_ they - cause an error! - - What about markdown links to internal pages? - - Relative links should be supported to play nice with LSP - - in case of relative link, zona should attempt to resolve it, figuring - out which file it's pointing to, and convert it to a `/` prefixed link - pointing to appropriate place - - so `../blog/a-post.md` → `/blog/a-post` where `/blog/a-post/index.html` - exists - - links from project root should also be supported - - check link validity at build time and supply warning - - _tl;dr_ all links should be resolved to the absolute path to that resource - starting from the website root. that's the link that should actually be - written to the HTML. -- Re-consider what `zona.yml` should have in it. - - Set syntax highlighting theme here - - a string that's not a file path: name of any built-in theme in - [chroma](https://github.com/alecthomas/chroma) - - path to `xml` _or_ `yml` file: custom theme for passing to chroma - - if `xml`, pass directly - - if `yml`, parse and convert into expected `xml` format before passing - - Set website root URL here - - toggle option for zona's custom image label expansion, image container div, - etc, basically all the custom rendering stuff -- Syntax highlighting for code blocks -- Add `zona serve` command with local dev server to preview the site -- Both `zona build` and `zona serve` should output warning and error -- Write actual unit tests! diff --git a/cmd/zona/main.go b/cmd/zona/main.go index e1d8a59..d61da6c 100644 --- a/cmd/zona/main.go +++ b/cmd/zona/main.go @@ -42,18 +42,8 @@ func main() { } settings := builder.GetSettings(*rootPath, "foobar") - // err := builder.Traverse(*rootPath, "foobar", settings) - // traverse the source and process file metadata - pm, err := builder.ProcessTraverse(*rootPath, "foobar", settings) + err := builder.Traverse(*rootPath, "foobar", settings) if err != nil { fmt.Printf("Error: %s\n", err.Error()) - os.Exit(1) } - err = builder.BuildProcessedFiles(pm, settings) - if err != nil { - fmt.Printf("Error: %s\n", err.Error()) - os.Exit(1) - } - - fmt.Printf("%#v", pm) } diff --git a/go.mod b/go.mod index 8f10aa7..d9c42d7 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/ficcdaf/zona -go 1.24.2 +// go 1.23.2 +go 1.23.4 require ( - github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b + github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 gopkg.in/yaml.v3 v3.0.1 ) -require golang.org/x/text v0.23.0 +require golang.org/x/text v0.20.0 diff --git a/internal/builder/build_page.go b/internal/builder/build_page.go index 4fa9b3b..783268f 100644 --- a/internal/builder/build_page.go +++ b/internal/builder/build_page.go @@ -2,7 +2,6 @@ package builder import ( "bytes" - "errors" "fmt" "html/template" "log" @@ -25,21 +24,11 @@ type PageData struct { FooterName string Footer template.HTML Template string - Type string } -type Metadata map[string]any +type Metadata map[string]interface{} -type FrontMatter struct { - Title string `yaml:"title"` - Icon string `yaml:"icon"` - Style string `yaml:"style"` - Header string `yaml:"header"` - Footer string `yaml:"footer"` - Type string `yaml:"type"` -} - -func processWithYaml(f []byte) (*FrontMatter, []byte, error) { +func processWithYaml(f []byte) (Metadata, []byte, error) { // Check if the file has valid metadata trimmed := bytes.TrimSpace(f) normalized := strings.ReplaceAll(string(trimmed), "\r\n", "\n") @@ -50,36 +39,31 @@ func processWithYaml(f []byte) (*FrontMatter, []byte, error) { // Separate YAML from rest of document split := strings.SplitN(normalized, "---\n", 3) if len(split) < 3 { - return nil, nil, fmt.Errorf("invalid frontmatter format") + return nil, nil, fmt.Errorf("Invalid frontmatter format.") } - var meta FrontMatter + var meta Metadata // Parse YAML if err := yaml.Unmarshal([]byte(split[1]), &meta); err != nil { return nil, nil, err } - return &meta, []byte(split[2]), nil + return meta, []byte(split[2]), nil } -func buildPageData(m *FrontMatter, in string, out string, settings *Settings) *PageData { +func buildPageData(m Metadata, in string, out string, settings *Settings) *PageData { p := &PageData{} - if m != nil && m.Title != "" { - p.Title = util.WordsToTitle(m.Title) + if title, ok := m["title"].(string); ok { + p.Title = util.WordsToTitle(title) } else { p.Title = util.PathToTitle(in) } - if m != nil && m.Icon != "" { - i, err := util.NormalizePath(m.Icon, in) - if err != nil { - p.Icon = settings.IconName - } else { - p.Icon = i - } + if icon, ok := m["icon"].(string); ok { + p.Icon = icon } else { p.Icon = settings.IconName } var stylePath string - if m != nil && m.Style != "" { - stylePath = m.Style + if style, ok := m["style"].(string); ok { + stylePath = style } else { stylePath = settings.StylePath } @@ -90,34 +74,30 @@ func buildPageData(m *FrontMatter, in string, out string, settings *Settings) *P log.Fatalln("Error calculating stylesheet path: ", err) } p.Stylesheet = relPath - - if m != nil && m.Header != "" { - p.HeaderName = m.Header + if header, ok := m["header"].(string); ok { + p.HeaderName = header // for now we use default anyways p.Header = settings.Header } else { p.HeaderName = settings.HeaderName p.Header = settings.Header } - if m != nil && m.Footer != "" { - p.FooterName = m.Footer + if footer, ok := m["footer"].(string); ok { + p.FooterName = footer p.Footer = settings.Footer } else { p.FooterName = settings.FooterName p.Footer = settings.Footer } - // TODO: Don't hard code posts dir name - if (m != nil && (m.Type == "article" || m.Type == "post")) || util.InDir(in, "posts") { + if t, ok := m["type"].(string); ok && t == "article" || t == "post" { p.Template = (settings.ArticleTemplate) - p.Type = "post" } else { p.Template = (settings.DefaultTemplate) - p.Type = "" } return p } -func _BuildHtmlFile(in string, out string, settings *Settings) error { +func ConvertFile(in string, out string, settings *Settings) error { mdPre, err := util.ReadFile(in) if err != nil { return err @@ -146,58 +126,3 @@ func _BuildHtmlFile(in string, out string, settings *Settings) error { err = util.WriteFile(output.Bytes(), out) return err } - -func BuildFile(f *File, settings *Settings) error { - if f.ShouldCopy { - if err := util.CreateParents(f.OutPath); err != nil { - return err - } - if err := util.CopyFile(f.InPath, f.OutPath); err != nil { - return errors.Join(errors.New("Error processing file "+f.InPath), err) - } else { - return nil - } - } - - if err := util.CreateParents(f.OutPath); err != nil { - return err - } - if err := BuildHtmlFile(f.FrontMatterLen, f.InPath, f.OutPath, f.PageData, settings); err != nil { - return errors.Join(errors.New("Error processing file "+f.InPath), err) - } else { - return nil - } -} - -func BuildHtmlFile(l int, in string, out string, pd *PageData, settings *Settings) error { - // WARN: ReadLineRange is fine, but l is the len of the frontmatter - // NOT including the delimiters! - start := l - // if the frontmatter exists (len > 0), then we need to - // account for two lines of delimiter! - if l != 0 { - start += 2 - } - md, err := util.ReadLineRange(in, start, -1) - if err != nil { - return err - } - fmt.Println("Title: ", pd.Title) - - // build according to template here - html := MdToHTML(md) - pd.Content = template.HTML(html) - - tmpl, err := template.New("webpage").Parse(pd.Template) - if err != nil { - return err - } - - var output bytes.Buffer - if err := tmpl.Execute(&output, pd); err != nil { - return err - } - - err = util.WriteFile(output.Bytes(), out) - return err -} diff --git a/internal/builder/config.go b/internal/builder/config.go index 5d960e5..831ec01 100644 --- a/internal/builder/config.go +++ b/internal/builder/config.go @@ -11,7 +11,7 @@ import ( ) var defaultNames = map[string]string{ - "config": ".zona.yml", + "config": "config.yml", "header": "header.md", "footer": "footer.md", "style": "style.css", @@ -22,7 +22,7 @@ var defaultNames = map[string]string{ } //go:embed embed/article.html -//go:embed embed/.zona.yml +//go:embed embed/config.yml //go:embed embed/default.html //go:embed embed/favicon.png //go:embed embed/footer.md diff --git a/internal/builder/convert.go b/internal/builder/convert.go index 12831b3..ed72fbd 100644 --- a/internal/builder/convert.go +++ b/internal/builder/convert.go @@ -1,7 +1,6 @@ package builder import ( - "bytes" "fmt" "io" "os" @@ -54,33 +53,6 @@ func processLink(p string) string { } } -// renderImage outputs an ast Image node as HTML string. -func renderImage(w io.Writer, node *ast.Image, entering bool, next *ast.Text) { - // we add image-container div tag - // here before the opening img tag - if entering { - fmt.Fprintf(w, "
\n") - fmt.Fprintf(w, ` 0 { - md := []byte(next.Literal) - html, doc := convertEmbedded(md) - altText := extractPlainText(md, doc) - fmt.Fprintf(w, ` alt="%s">`, altText) - // TODO: render inside a special div? - // is this necessary since this is all inside image-container anyways? - fmt.Fprintf(w, `%s`, html) - } else { - // - io.WriteString(w, ">") - } - } else { - // if it's the closing img tag - // we close the div tag *after* - fmt.Fprintf(w, `
`) - fmt.Println("Image node not entering??") - } -} - func renderLink(w io.Writer, l *ast.Link, entering bool) { if entering { destPath := processLink(string(l.Destination)) @@ -94,73 +66,15 @@ func renderLink(w io.Writer, l *ast.Link, entering bool) { } } -func htmlRenderHookNoImage(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) { - if link, ok := node.(*ast.Link); ok { - renderLink(w, link, entering) - return ast.GoToNext, true - } else if _, ok := node.(*ast.Image); ok { - // we do not render images - return ast.GoToNext, true - } - return ast.GoToNext, false -} - -// htmlRenderHook hooks the HTML renderer and overrides the rendering of certain nodes. func htmlRenderHook(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) { if link, ok := node.(*ast.Link); ok { renderLink(w, link, entering) return ast.GoToNext, true - } else if image, ok := node.(*ast.Image); ok { - var nextNode *ast.Text - if entering { - nextNodes := node.GetChildren() - fmt.Println("img next node len:", len(nextNodes)) - if len(nextNodes) == 1 { - if textNode, ok := nextNodes[0].(*ast.Text); ok { - nextNode = textNode - } - } - } - renderImage(w, image, entering, nextNode) - // Skip rendering of `nextNode` explicitly - if nextNode != nil { - return ast.SkipChildren, true - } - return ast.GoToNext, true } return ast.GoToNext, false } -// convertEmbedded renders markdown as HTML -// but does NOT render images -// also returns document AST -func convertEmbedded(md []byte) ([]byte, *ast.Node) { - p := parser.NewWithExtensions(parser.CommonExtensions) - doc := p.Parse(md) - htmlFlags := html.CommonFlags | html.HrefTargetBlank - opts := html.RendererOptions{Flags: htmlFlags} - opts.RenderNodeHook = htmlRenderHookNoImage - r := html.NewRenderer(opts) - html := markdown.Render(doc, r) - return html, &doc -} - func newZonaRenderer(opts html.RendererOptions) *html.Renderer { opts.RenderNodeHook = htmlRenderHook return html.NewRenderer(opts) } - -// ExtractPlainText walks the AST and extracts plain text from the Markdown input. -func extractPlainText(md []byte, doc *ast.Node) string { - var buffer bytes.Buffer - - // Walk the AST and extract text nodes - ast.WalkFunc(*doc, func(node ast.Node, entering bool) ast.WalkStatus { - if textNode, ok := node.(*ast.Text); ok && entering { - buffer.Write(textNode.Literal) // Append the text content - } - return ast.GoToNext - }) - - return buffer.String() -} diff --git a/internal/builder/convert_test.go b/internal/builder/convert_test.go new file mode 100644 index 0000000..45c4843 --- /dev/null +++ b/internal/builder/convert_test.go @@ -0,0 +1,122 @@ +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 := "

Hello World

\n

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("

Test Title

\n

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/embed/.zona.yml b/internal/builder/embed/config.yml similarity index 100% rename from internal/builder/embed/.zona.yml rename to internal/builder/embed/config.yml diff --git a/internal/builder/frontmatter.go b/internal/builder/frontmatter.go deleted file mode 100644 index b25a1b0..0000000 --- a/internal/builder/frontmatter.go +++ /dev/null @@ -1,86 +0,0 @@ -package builder - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "os" - - "gopkg.in/yaml.v3" -) - -func processFrontmatter(p string) (*FrontMatter, int, error) { - f, l, err := readFrontmatter(p) - if err != nil { - return nil, l, err - } - var meta FrontMatter - // 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 == 1 && delims == 0 { - // if --- is not the first line, we - // assume the file does not contain frontmatter - // fmt.Println("Delimiter first line") - return nil, 0, nil - } - delims += 1 - i += 1 - if delims == 2 { - break - } - } else { - if i == 0 { - return nil, 0, nil - } - 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 - s := fmt.Sprintf("%s: frontmatter is missing closing delimiter", path) - return nil, 0, errors.New(s) - } -} diff --git a/internal/builder/frontmatter_test.go b/internal/builder/frontmatter_test.go deleted file mode 100644 index d6abf07..0000000 --- a/internal/builder/frontmatter_test.go +++ /dev/null @@ -1,183 +0,0 @@ -// FILE: internal/builder/build_page_test.go -package builder - -import ( - "bytes" - "os" - "testing" -) - -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) - } - 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, 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"]) - } - - // Create a temporary file with invalid frontmatter - invalidContent := `--- -title: "Test Title" -description: "Test Description" -There is no closing delimiter??? -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") - } - // 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") - } -} - -func TestReadFrontmatter(t *testing.T) { - tests := []struct { - name string - content string - wantErr bool - wantData []byte - wantLen int - }{ - { - name: "Valid frontmatter", - content: `--- -title: "Test" -author: "User" ---- -Content here`, - wantErr: false, - wantData: []byte("title: \"Test\"\nauthor: \"User\"\n"), - wantLen: 2, - }, - { - name: "Missing closing delimiter", - content: `--- -title: "Incomplete Frontmatter"`, - wantErr: true, - }, - { - name: "Frontmatter later in file", - content: `This is some content ---- -title: "Not Frontmatter" ----`, - wantErr: false, - wantData: nil, // Should return nil because `---` is not the first line - wantLen: 0, - }, - { - name: "Empty frontmatter", - content: `--- ----`, - wantErr: true, - }, - { - name: "No frontmatter", - content: `This is just a normal file.`, - wantErr: false, - wantData: nil, // Should return nil as there's no frontmatter - wantLen: 0, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Create a temporary file - tmpFile, err := os.CreateTemp("", "testfile-*.md") - if err != nil { - t.Fatalf("failed to create temp file: %v", err) - } - defer os.Remove(tmpFile.Name()) - - // Write test content - _, err = tmpFile.WriteString(tc.content) - if err != nil { - t.Fatalf("failed to write to temp file: %v", err) - } - tmpFile.Close() - - // Call function under test - data, length, err := readFrontmatter(tmpFile.Name()) - - // Check for expected error - if tc.wantErr { - if err == nil { - t.Errorf("expected error but got none") - } - } else { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - // Check content - if !bytes.Equal(data, tc.wantData) { - t.Errorf("expected %q, got %q", tc.wantData, data) - } - // Check length - if length != tc.wantLen { - t.Errorf("expected length %d, got %d", tc.wantLen, length) - } - } - }) - } -} diff --git a/internal/builder/process.go b/internal/builder/process.go deleted file mode 100644 index 7108668..0000000 --- a/internal/builder/process.go +++ /dev/null @@ -1,116 +0,0 @@ -package builder - -import ( - "io/fs" - "path/filepath" - - "github.com/ficcdaf/zona/internal/util" -) - -type ProcessMemory struct { - // Files holds all page data that may be - // needed while building *other* pages. - 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 - // This list is ONLY referenced for generating - // the archive, NOT by the build process! - Posts []*File -} - -type File struct { - PageData *PageData - Ext string - InPath string - OutPath string - ShouldCopy bool - HasFrontmatter bool - FrontMatterLen int -} - -// 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 -// 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() { - return nil - } else { - ext = filepath.Ext(inPath) - // NOTE: This could be an if statement, but keeping - // the switch makes it easy to extend the logic here later - switch ext { - case ".md": - toProcess = true - outPath = util.ReplaceRoot(inPath, outRoot) - outPath = util.ChangeExtension(outPath, ".html") - outPath = util.Indexify(outPath) - default: - toProcess = false - outPath = util.ReplaceRoot(inPath, outRoot) - } - } - - var pd *PageData - hasFrontmatter := false - l := 0 - if toProcess { - // process its frontmatter here - m, le, err := processFrontmatter(inPath) - l = le - if err != nil { - return err - } - if m != nil { - hasFrontmatter = true - } - pd = buildPageData(m, inPath, outPath, settings) - - } else { - pd = nil - } - file := &File{ - pd, - ext, - inPath, - outPath, - !toProcess, - hasFrontmatter, - l, - } - if pd != nil && pd.Type == "post" { - pm.Posts = append(pm.Posts, file) - } - pm.Files = append(pm.Files, file) - return nil -} - -func BuildProcessedFiles(pm *ProcessMemory, settings *Settings) error { - for _, f := range pm.Files { - err := BuildFile(f, settings) - if err != nil { - return err - } - } - return nil -} diff --git a/internal/builder/traverse.go b/internal/builder/traverse.go index 9022fef..5272dca 100644 --- a/internal/builder/traverse.go +++ b/internal/builder/traverse.go @@ -8,8 +8,7 @@ import ( "github.com/ficcdaf/zona/internal/util" ) -// 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 { +func processFile(inPath string, entry fs.DirEntry, err error, outRoot string, settings *Settings) error { if err != nil { return err } @@ -23,7 +22,7 @@ func buildFile(inPath string, entry fs.DirEntry, err error, outRoot string, sett if err := util.CreateParents(outPath); err != nil { return err } - if err := _BuildHtmlFile(inPath, outPath, settings); err != nil { + if err := ConvertFile(inPath, outPath, settings); err != nil { return errors.Join(errors.New("Error processing file "+inPath), err) } else { return nil @@ -47,17 +46,8 @@ func buildFile(inPath string, entry fs.DirEntry, err error, outRoot string, sett func Traverse(root string, outRoot string, settings *Settings) error { walkFunc := func(path string, entry fs.DirEntry, err error) error { - return buildFile(path, entry, err, outRoot, settings) + return processFile(path, entry, err, outRoot, settings) } err := filepath.WalkDir(root, walkFunc) return err } - -func ProcessTraverse(root string, outRoot string, settings *Settings) (*ProcessMemory, 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 pm, err -} diff --git a/internal/util/file.go b/internal/util/file.go index 4570eb2..2028018 100644 --- a/internal/util/file.go +++ b/internal/util/file.go @@ -1,8 +1,6 @@ package util import ( - "bufio" - "bytes" "io" "os" ) @@ -58,54 +56,3 @@ 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 -} - -// ReadLineRange reads a file in a given range of lines -func ReadLineRange(filename string, start int, end 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) - i := 0 - for scanner.Scan() { - if i >= start && (i <= end || end == -1) { - buffer.Write(scanner.Bytes()) - buffer.WriteByte('\n') - } - if i > end && end != -1 { - break - } - i++ - } - - if err := scanner.Err(); err != nil { - return nil, err - } - - return buffer.Bytes(), nil -} diff --git a/internal/util/file_test.go b/internal/util/file_test.go deleted file mode 100644 index df42d4f..0000000 --- a/internal/util/file_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// 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) - } -} - -func TestReadLineRange(t *testing.T) { - tests := []struct { - name string // description of this test case - // Named input parameters for target function. - filename string - start int - end int - want []byte - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, gotErr := ReadLineRange(tt.filename, tt.start, tt.end) - if gotErr != nil { - if !tt.wantErr { - t.Errorf("ReadLineRange() failed: %v", gotErr) - } - return - } - if tt.wantErr { - t.Fatal("ReadLineRange() succeeded unexpectedly") - } - // TODO: update the condition below to compare got with tt.want. - if true { - t.Errorf("ReadLineRange() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/util/path.go b/internal/util/path.go index 2442f87..12fa15e 100644 --- a/internal/util/path.go +++ b/internal/util/path.go @@ -3,7 +3,6 @@ package util import ( "errors" - "fmt" "os" "path/filepath" "strings" @@ -23,24 +22,16 @@ func ChangeExtension(in string, outExt string) string { return strings.TrimSuffix(in, filepath.Ext(in)) + outExt } -// find the root. check for a .zona.yml first, -// then check if it's cwd. func getRoot(path string) string { - marker := ".zona.yml" for { parent := filepath.Dir(path) - if parent == "/" { - panic(1) - } - candidate := filepath.Join(parent, marker) - // fmt.Printf("check for: %s\n", candidate) - if FileExists(candidate) { - return parent - } else if parent == "." { - return path + if parent == "." { + break } path = parent } + // fmt.Println("getRoot: ", path) + return path } func ReplaceRoot(inPath, outRoot string) string { @@ -49,40 +40,6 @@ func ReplaceRoot(inPath, outRoot string) string { return outPath } -// Indexify converts format path/file.ext -// into path/file/index.ext -func Indexify(in string) string { - ext := filepath.Ext(in) - trimmed := strings.TrimSuffix(in, ext) - filename := filepath.Base(trimmed) - if filename == "index" { - return in - } - prefix := strings.TrimSuffix(trimmed, filename) - return filepath.Join(prefix, filename, "index"+ext) -} - -// InDir checks whether checkPath is -// inside targDir. -func InDir(checkPath string, targDir string) bool { - // fmt.Println("checking dir..") - i := 0 - for i < 10 { - parent := filepath.Dir(checkPath) - fmted := filepath.Base(parent) - switch fmted { - case targDir: - // fmt.Printf("%s in %s\n", checkPath, targDir) - return true - case ".": - return false - } - checkPath = parent - i += 1 - } - return false -} - // FileExists returns a boolean indicating // whether something exists at the path. func FileExists(path string) bool { @@ -112,29 +69,3 @@ func StripTopDir(path string) string { } return filepath.Join(components[1:]...) } - -func resolveRelativeTo(relPath string, basePath string) string { - baseDir := filepath.Dir(basePath) - combined := filepath.Join(baseDir, relPath) - resolved := filepath.Clean(combined) - return resolved -} - -// we want to preserve a valid web-style path -// and convert relative path to web-style -// so we need to see -func NormalizePath(target string, source string) (string, error) { - fmt.Printf("normalizing: %s\n", target) - // empty path is root - if target == "" { - return "/", nil - } - if target[0] == '.' { - resolved := resolveRelativeTo(target, source) - normalized := ReplaceRoot(resolved, "/") - fmt.Printf("Normalized: %s\n", normalized) - return normalized, nil - } else { - return target, nil - } -} diff --git a/internal/util/path_test.go b/internal/util/path_test.go deleted file mode 100644 index ccac2fc..0000000 --- a/internal/util/path_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package util_test - -import ( - "testing" - - "github.com/ficcdaf/zona/internal/util" -) - -func TestIndexify(t *testing.T) { - tests := []struct { - name string // description of this test case - // Named input parameters for target function. - in string - want string - }{ - { - "Simple Path", - "foo/bar/name.html", - "foo/bar/name/index.html", - }, - { - "Index Name", - "foo/bar/index.md", - "foo/bar/index.md", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := util.Indexify(tt.in) - if got != tt.want { - t.Errorf("Indexify() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/util/queue.go b/internal/util/queue.go deleted file mode 100644 index 3fce4fa..0000000 --- a/internal/util/queue.go +++ /dev/null @@ -1,26 +0,0 @@ -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 - } -} diff --git a/justfile b/justfile deleted file mode 100644 index 39f5308..0000000 --- a/justfile +++ /dev/null @@ -1,14 +0,0 @@ -# run go tests -test: - go test ./... - -# test outputs -gentest: - #!/bin/bash - if [ -e foobar ]; then - rm -rf foobar - fi - - go run cmd/zona/main.go test - - # bat foobar/img.html diff --git a/runtest.sh b/runtest.sh index 5df5005..d20c26e 100755 --- a/runtest.sh +++ b/runtest.sh @@ -5,4 +5,4 @@ fi go run cmd/zona/main.go test -bat foobar/img.html +bat foobar/in.html diff --git a/test/.zona.yml b/test/.zona.yml deleted file mode 100644 index e69de29..0000000 diff --git a/test/assets/pic.png b/test/assets/pic.png deleted file mode 100644 index 9470f08..0000000 Binary files a/test/assets/pic.png and /dev/null differ diff --git a/test/img.md b/test/img.md deleted file mode 100644 index 6c2fce1..0000000 --- a/test/img.md +++ /dev/null @@ -1,3 +0,0 @@ -# An image is in this page - -![my *alternate* text](assets/pic.png "my title") diff --git a/test/in.md b/test/in.md index 57171ba..2fd2350 100644 --- a/test/in.md +++ b/test/in.md @@ -1,5 +1,5 @@ --- -title: tiltetest +type: article --- # My amazing markdown file! diff --git a/test/posts/in.md b/test/posts/in.md deleted file mode 100644 index fc679ea..0000000 --- a/test/posts/in.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: tiltetest -icon: ../assets/pic.png ---- - -# My amazing markdown file! - -I can _even_ do **this**! - -- Or, I could... -- [Link](page.md) to this file diff --git a/test/yamltest.md b/test/yamltest.md index ffa1fb3..974dedb 100644 --- a/test/yamltest.md +++ b/test/yamltest.md @@ -1,6 +1,5 @@ --- title: Yaml testing file -type: article --- # My amazing markdown file!