From d81cfa5e67c7518fb29dc5f98c00c14a43e00db6 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:14:42 +0100 Subject: [PATCH] api cleanup --- cmd/Drename/main.go | 18 ++-- internal/disk/callbacks.go | 8 +- internal/disk/loader.go | 6 +- main.go | 11 +- pkg/metadata/frontmatter.go | 109 +++++++++++--------- pkg/metadata/frontmatter_test.go | 168 +++++++++++++++---------------- pkg/metadata/metadata.go | 10 -- pkg/metadata/metadata_test.go | 58 ----------- 8 files changed, 172 insertions(+), 216 deletions(-) diff --git a/cmd/Drename/main.go b/cmd/Drename/main.go index e784a97..0440704 100644 --- a/cmd/Drename/main.go +++ b/cmd/Drename/main.go @@ -190,15 +190,19 @@ func windowMode(args []string) error { // Update frontmatter if applicable if supportsFrontmatter && len(body) > 0 { - // Create new frontmatter - fm := &metadata.FrontMatter{ - Title: title, - Tags: tags, - Signature: signature, + // Parse existing frontmatter to preserve Identifier and FileType + existing, err := metadata.ParseFrontMatter(string(body), ext) + if err != nil { + return fmt.Errorf("failed to parse existing frontmatter: %w", err) } - // Update frontmatter in content - newContent, err := metadata.UpdateFrontMatter(string(body), fm) + // Update only the fields that changed from rename + existing.Title = title + existing.Tags = tags + existing.Signature = signature + + // Apply updated frontmatter to content + newContent, err := metadata.Apply(string(body), existing) if err != nil { return fmt.Errorf("failed to update frontmatter: %w", err) } diff --git a/internal/disk/callbacks.go b/internal/disk/callbacks.go index 11c9ad5..f9cb918 100644 --- a/internal/disk/callbacks.go +++ b/internal/disk/callbacks.go @@ -36,14 +36,14 @@ func HandleUpdateEvent(f *client.Fsys, identifier, denoteDir string) error { // Only update frontmatter if file exists (skip if not yet created via Put) if _, err := os.Stat(path); err == nil && SupportsFrontMatter(path) { ext := strings.ToLower(filepath.Ext(path)) - var fileType string + var fileType metadata.FileType switch ext { case ".org": - fileType = "org" + fileType = metadata.FileTypeOrg case ".md": - fileType = "md-yaml" + fileType = metadata.FileTypeMdYaml case ".txt": - fileType = "txt" + fileType = metadata.FileTypeTxt } fm := &metadata.FrontMatter{ diff --git a/internal/disk/loader.go b/internal/disk/loader.go index 7390965..e0ce223 100644 --- a/internal/disk/loader.go +++ b/internal/disk/loader.go @@ -67,10 +67,10 @@ func UpdateFrontMatter(path string, fm *metadata.FrontMatter) error { return fmt.Errorf("failed to read file: %w", err) } - // Update front matter (pure function) - newContent, err := metadata.UpdateFrontMatter(string(content), fm) + // Apply front matter to content + newContent, err := metadata.Apply(string(content), fm) if err != nil { - return fmt.Errorf("failed to update front matter: %w", err) + return fmt.Errorf("failed to apply front matter: %w", err) } // Write back to file diff --git a/main.go b/main.go index 2255304..5a5f29c 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,9 @@ package main import ( "denote/internal/disk" - "denote/pkg/metadata" p9client "denote/internal/p9/client" p9server "denote/internal/p9/server" + "denote/pkg/metadata" "fmt" "log" "os" @@ -21,7 +21,7 @@ import ( const ( wname = "/Denote/" - ftype = "md-yaml" + ftype = metadata.FileTypeMdYaml ) var denoteDir = os.Getenv("HOME") + "/doc" @@ -112,7 +112,12 @@ func handleNewEvent(f *client.Fsys, identifier, denoteDir string) error { } } - path, content := metadata.GenerateNote(targetDir, title, signature, tags, ftype) + newIdentifier := metadata.GenerateIdentifier() + ext := metadata.GetExtension(ftype) + filename := metadata.BuildFilename(newIdentifier, signature, title, tags, ext) + path := filepath.Join(targetDir, filename) + fm := metadata.NewFrontMatter(title, signature, tags, ftype, newIdentifier) + content := string(fm.Bytes()) if err := p9client.WriteFile(f, "n/"+identifier+"/path", path); err != nil { return fmt.Errorf("failed to update path in metadata: %w", err) diff --git a/pkg/metadata/frontmatter.go b/pkg/metadata/frontmatter.go index 852109f..59b8a0c 100644 --- a/pkg/metadata/frontmatter.go +++ b/pkg/metadata/frontmatter.go @@ -8,15 +8,15 @@ import ( ) // Templates for denote frontmatter (org, md, txt) -var templates = map[string]string{ - "org": `#+title: %s +var templates = map[FileType]string{ + FileTypeOrg: `#+title: %s #+date: %s #+filetags: %s #+identifier: %s #+signature: %s `, - "md-yaml": `--- + FileTypeMdYaml: `--- title: %s date: %s tags: %s @@ -25,7 +25,7 @@ signature: %s --- `, - "md-toml": `+++ + FileTypeMdToml: `+++ title = %s date = %s tags = %s @@ -34,7 +34,7 @@ signature = %s +++ `, - "txt": `title: %s + FileTypeTxt: `title: %s date: %s tags: %s identifier: %s @@ -44,24 +44,39 @@ signature: %s `, } +// FileType represents supported file formats for denote notes +type FileType string + +const ( + FileTypeOrg FileType = "org" + FileTypeMdYaml FileType = "md-yaml" + FileTypeMdToml FileType = "md-toml" + FileTypeTxt FileType = "txt" +) + // fileExtensions contains the list of file extensions // for which Denote should add front matter. -var fileExtensions = map[string]string{ - "org": ".org", - "md-yaml": ".md", - "md-toml": ".md", - "txt": ".txt", +var fileExtensions = map[FileType]string{ + FileTypeOrg: ".org", + FileTypeMdYaml: ".md", + FileTypeMdToml: ".md", + FileTypeTxt: ".txt", +} + +// GetExtension returns the file extension for a given file type. +func GetExtension(fileType FileType) string { + return fileExtensions[fileType] } // FormatTags formats tags according to file type -func FormatTags(tags []string, fileType string) string { +func FormatTags(tags []string, fileType FileType) string { if len(tags) == 0 { return "" } switch fileType { - case "org": + case FileTypeOrg: return ":" + strings.Join(tags, ":") + ":" - case "md-yaml", "md-toml": + case FileTypeMdYaml, FileTypeMdToml: return "[" + strings.Join(tags, ", ") + "]" default: return strings.Join(tags, " ") @@ -74,7 +89,22 @@ type FrontMatter struct { Tags []string Identifier string Signature string - FileType string // org, md-yaml, md-toml, txt + FileType FileType +} + +// Bytes returns the formatted frontmatter content as bytes +func (fm *FrontMatter) Bytes() []byte { + template := templates[fm.FileType] + dateStr := time.Now().Format("2006-01-02 Mon 15:04") + + // For org-mode, wrap date in brackets for timestamp + if fm.FileType == FileTypeOrg { + dateStr = "[" + dateStr + "]" + } + + keywordsStr := FormatTags(fm.Tags, fm.FileType) + content := fmt.Sprintf(template, fm.Title, dateStr, keywordsStr, fm.Identifier, fm.Signature) + return []byte(content) } // ParseFrontMatter extracts front matter from file content. @@ -87,7 +117,7 @@ func ParseFrontMatter(content string, ext string) (*FrontMatter, error) { switch ext { case ".org": - fm.FileType = "org" + fm.FileType = FileTypeOrg if m := regexp.MustCompile(`(?m)^#\+title:[ \t]*(.+)$`).FindStringSubmatch(text); m != nil { fm.Title = strings.TrimSpace(m[1]) } @@ -105,7 +135,7 @@ func ParseFrontMatter(content string, ext string) (*FrontMatter, error) { // Try YAML first yamlRe := regexp.MustCompile(`(?ms)^---\n(.*?)\n---`) if m := yamlRe.FindStringSubmatch(text); m != nil { - fm.FileType = "md-yaml" + fm.FileType = FileTypeMdYaml yamlContent := m[1] if m := regexp.MustCompile(`(?m)^title:[ \t]*["']?(.+?)["']?$`).FindStringSubmatch(yamlContent); m != nil { fm.Title = strings.TrimSpace(m[1]) @@ -127,7 +157,7 @@ func ParseFrontMatter(content string, ext string) (*FrontMatter, error) { // Try TOML tomlRe := regexp.MustCompile(`(?ms)^\+\+\+\n(.*?)\n\+\+\+`) if m := tomlRe.FindStringSubmatch(text); m != nil { - fm.FileType = "md-toml" + fm.FileType = FileTypeMdToml tomlContent := m[1] if m := regexp.MustCompile(`(?m)^title[ \t]*=[ \t]*["']?(.+?)["']?$`).FindStringSubmatch(tomlContent); m != nil { fm.Title = strings.TrimSpace(m[1]) @@ -149,7 +179,7 @@ func ParseFrontMatter(content string, ext string) (*FrontMatter, error) { } case ".txt": - fm.FileType = "txt" + fm.FileType = FileTypeTxt if m := regexp.MustCompile(`(?m)^title:[ \t]*(.+)$`).FindStringSubmatch(text); m != nil { fm.Title = strings.TrimSpace(m[1]) } @@ -167,25 +197,15 @@ func ParseFrontMatter(content string, ext string) (*FrontMatter, error) { return fm, nil } -// UpdateFrontMatter updates front matter in file content and returns the new content. +// Apply applies front matter to file content, replacing existing front matter if present. // originalContent is the current file content, fm is the new front matter to apply. -func UpdateFrontMatter(originalContent string, fm *FrontMatter) (string, error) { +func Apply(originalContent string, fm *FrontMatter) (string, error) { text := originalContent - - dateStr := time.Now().Format("2006-01-02 Mon 15:04") - keywordsStr := FormatTags(fm.Tags, fm.FileType) - - // For org-mode, wrap date in brackets for timestamp - if fm.FileType == "org" { - dateStr = "[" + dateStr + "]" - } - - template := templates[fm.FileType] - newFrontMatter := fmt.Sprintf(template, fm.Title, dateStr, keywordsStr, fm.Identifier, fm.Signature) + newFrontMatter := string(fm.Bytes()) var newText string switch fm.FileType { - case "org": + case FileTypeOrg: // Find end of front matter (first blank line or non-#+ line) lines := strings.Split(text, "\n") endIdx := 0 @@ -205,7 +225,7 @@ func UpdateFrontMatter(originalContent string, fm *FrontMatter) (string, error) newText = newFrontMatter + text } - case "md-yaml": + case FileTypeMdYaml: // Replace YAML front matter (match trailing blank lines to avoid duplication) re := regexp.MustCompile(`(?s)^---\n.*?\n---\n\n*`) if re.MatchString(text) { @@ -214,7 +234,7 @@ func UpdateFrontMatter(originalContent string, fm *FrontMatter) (string, error) newText = newFrontMatter + text } - case "md-toml": + case FileTypeMdToml: // Replace TOML front matter (match trailing blank lines to avoid duplication) re := regexp.MustCompile(`(?s)^\+\+\+\n.*?\n\+\+\+\n\n*`) if re.MatchString(text) { @@ -223,7 +243,7 @@ func UpdateFrontMatter(originalContent string, fm *FrontMatter) (string, error) newText = newFrontMatter + text } - case "txt": + case FileTypeTxt: // Replace text front matter (match trailing blank lines to avoid duplication) re := regexp.MustCompile(`(?s)^title:.*?\n-+\n\n*`) if re.MatchString(text) { @@ -238,16 +258,13 @@ func UpdateFrontMatter(originalContent string, fm *FrontMatter) (string, error) return newText, nil } -// Generate generates front matter content for given parameters -func Generate(title, signature string, tags []string, fileType, identifier string) string { - template := templates[fileType] - dateStr := time.Now().Format("2006-01-02 Mon 15:04") - - // For org-mode, wrap date in brackets for timestamp - if fileType == "org" { - dateStr = "[" + dateStr + "]" +// NewFrontMatter creates a new FrontMatter struct from given parameters +func NewFrontMatter(title, signature string, tags []string, fileType FileType, identifier string) *FrontMatter { + return &FrontMatter{ + Title: title, + Tags: tags, + Identifier: identifier, + Signature: signature, + FileType: fileType, } - - keywordsStr := FormatTags(tags, fileType) - return fmt.Sprintf(template, title, dateStr, keywordsStr, identifier, signature) } diff --git a/pkg/metadata/frontmatter_test.go b/pkg/metadata/frontmatter_test.go index 306a18d..7b6c9ac 100644 --- a/pkg/metadata/frontmatter_test.go +++ b/pkg/metadata/frontmatter_test.go @@ -12,73 +12,73 @@ func TestFormatTags(t *testing.T) { tests := []struct { name string tags []string - fileType string + fileType FileType want string }{ { name: "org with multiple tags", tags: []string{"tag1", "tag2"}, - fileType: "org", + fileType: FileTypeOrg, want: ":tag1:tag2:", }, { name: "org with single tag", tags: []string{"single"}, - fileType: "org", + fileType: FileTypeOrg, want: ":single:", }, { name: "org with empty tags", tags: []string{}, - fileType: "org", + fileType: FileTypeOrg, want: "", }, { name: "md-yaml with multiple tags", tags: []string{"tag1", "tag2"}, - fileType: "md-yaml", + fileType: FileTypeMdYaml, want: "[tag1, tag2]", }, { name: "md-yaml with single tag", tags: []string{"single"}, - fileType: "md-yaml", + fileType: FileTypeMdYaml, want: "[single]", }, { name: "md-yaml with empty tags", tags: []string{}, - fileType: "md-yaml", + fileType: FileTypeMdYaml, want: "", }, { name: "md-toml with multiple tags", tags: []string{"tag1", "tag2"}, - fileType: "md-toml", + fileType: FileTypeMdToml, want: "[tag1, tag2]", }, { name: "md-toml with empty tags", tags: []string{}, - fileType: "md-toml", + fileType: FileTypeMdToml, want: "", }, { name: "txt with multiple tags", tags: []string{"tag1", "tag2"}, - fileType: "txt", + fileType: FileTypeTxt, want: "tag1 tag2", }, { name: "txt with single tag", tags: []string{"single"}, - fileType: "txt", + fileType: FileTypeTxt, want: "single", }, { name: "txt with empty tags", tags: []string{}, - fileType: "txt", + fileType: FileTypeTxt, want: "", }, } @@ -93,22 +93,22 @@ func TestFormatTags(t *testing.T) { } } -// TestGenerate validates front matter generation for all file types +// TestFrontMatterBytes validates front matter formatting for all file types // Maps to dt-denote--format-front-matter from original tests -func TestGenerate(t *testing.T) { +func TestFrontMatterBytes(t *testing.T) { identifier := "20240101T120000" title := "Test Note" tags := []string{"tag1", "tag2"} tests := []struct { name string - fileType string + fileType FileType wantContains []string wantNotContains []string }{ { name: "org format", - fileType: "org", + fileType: FileTypeOrg, wantContains: []string{ "#+title: Test Note", "#+filetags: :tag1:tag2:", @@ -118,7 +118,7 @@ func TestGenerate(t *testing.T) { }, { name: "md-yaml format", - fileType: "md-yaml", + fileType: FileTypeMdYaml, wantContains: []string{ "---", "title: Test Note", @@ -129,7 +129,7 @@ func TestGenerate(t *testing.T) { }, { name: "md-toml format", - fileType: "md-toml", + fileType: FileTypeMdToml, wantContains: []string{ "+++", "title = Test Note", @@ -140,7 +140,7 @@ func TestGenerate(t *testing.T) { }, { name: "txt format", - fileType: "txt", + fileType: FileTypeTxt, wantContains: []string{ "title: Test Note", "tags: tag1 tag2", @@ -153,64 +153,64 @@ func TestGenerate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := Generate(title, "", tags, tt.fileType, identifier) + fm := NewFrontMatter(title, "", tags, tt.fileType, identifier) + got := string(fm.Bytes()) for _, want := range tt.wantContains { if !strings.Contains(got, want) { - t.Errorf("Generate(%q, %v, %q, %q) missing %q\nGot:\n%s", - title, tags, tt.fileType, identifier, want, got) + t.Errorf("FrontMatter.Bytes() missing %q\nGot:\n%s", want, got) } } for _, notWant := range tt.wantNotContains { if strings.Contains(got, notWant) { - t.Errorf("Generate(%q, %v, %q, %q) should not contain %q\nGot:\n%s", - title, tags, tt.fileType, identifier, notWant, got) + t.Errorf("FrontMatter.Bytes() should not contain %q\nGot:\n%s", notWant, got) } } }) } } -// TestGenerateEmptyTags validates front matter generation with no tags -func TestGenerateEmptyTags(t *testing.T) { +// TestFrontMatterBytesEmptyTags validates front matter formatting with no tags +func TestFrontMatterBytesEmptyTags(t *testing.T) { identifier := "20240101T120000" title := "Test Note" tags := []string{} tests := []struct { name string - fileType string + fileType FileType wantTags string }{ { name: "org with empty tags", - fileType: "org", + fileType: FileTypeOrg, wantTags: "#+filetags:", }, { name: "md-yaml with empty tags", - fileType: "md-yaml", + fileType: FileTypeMdYaml, wantTags: "tags:", }, { name: "md-toml with empty tags", - fileType: "md-toml", + fileType: FileTypeMdToml, wantTags: "tags =", }, { name: "txt with empty tags", - fileType: "txt", + fileType: FileTypeTxt, wantTags: "tags:", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := Generate(title, "", tags, tt.fileType, identifier) + fm := NewFrontMatter(title, "", tags, tt.fileType, identifier) + got := string(fm.Bytes()) if !strings.Contains(got, tt.wantTags) { - t.Errorf("Generate() with empty tags should contain %q\nGot:\n%s", + t.Errorf("FrontMatter.Bytes() with empty tags should contain %q\nGot:\n%s", tt.wantTags, got) } }) @@ -226,7 +226,7 @@ func TestParseFrontMatter(t *testing.T) { wantTitle string wantTags []string wantIdentifier string - wantFileType string + wantFileType FileType }{ { name: "org format", @@ -240,7 +240,7 @@ func TestParseFrontMatter(t *testing.T) { wantTitle: "Org Note", wantTags: []string{"work", "emacs"}, wantIdentifier: "20240101T120000", - wantFileType: "org", + wantFileType: FileTypeOrg, }, { name: "org with single tag", @@ -251,7 +251,7 @@ func TestParseFrontMatter(t *testing.T) { wantTitle: "Single Tag", wantTags: []string{"single"}, wantIdentifier: "20240101T120000", - wantFileType: "org", + wantFileType: FileTypeOrg, }, { name: "org without tags", @@ -261,7 +261,7 @@ func TestParseFrontMatter(t *testing.T) { wantTitle: "No Tags", wantTags: nil, wantIdentifier: "20240101T120000", - wantFileType: "org", + wantFileType: FileTypeOrg, }, { name: "markdown yaml", @@ -277,7 +277,7 @@ identifier: 20240101T120000 wantTitle: "Markdown Note", wantTags: []string{"work", "personal"}, wantIdentifier: "20240101T120000", - wantFileType: "md-yaml", + wantFileType: FileTypeMdYaml, }, { name: "markdown yaml with quoted title", @@ -290,7 +290,7 @@ identifier: 20240101T120000 wantTitle: "Quoted Title", wantTags: []string{"test"}, wantIdentifier: "20240101T120000", - wantFileType: "md-yaml", + wantFileType: FileTypeMdYaml, }, { name: "markdown toml", @@ -306,7 +306,7 @@ Content`, wantTitle: "TOML Note", wantTags: []string{"rust", "go"}, wantIdentifier: "20240101T120000", - wantFileType: "md-toml", + wantFileType: FileTypeMdToml, }, { name: "txt format", @@ -321,7 +321,7 @@ Content here`, wantTitle: "Plain Text", wantTags: []string{"simple", "plain"}, wantIdentifier: "20240101T120000", - wantFileType: "txt", + wantFileType: FileTypeTxt, }, { name: "txt without tags", @@ -332,7 +332,7 @@ identifier: 20240101T120000 wantTitle: "No Tags Text", wantTags: nil, wantIdentifier: "20240101T120000", - wantFileType: "txt", + wantFileType: FileTypeTxt, }, } @@ -385,8 +385,8 @@ date: 2024-01-01 } } -// TestUpdateFrontMatter validates front matter updates -func TestUpdateFrontMatter(t *testing.T) { +// TestApply validates front matter updates +func TestApply(t *testing.T) { tests := []struct { name string original string @@ -407,7 +407,7 @@ This should be preserved`, Title: "New Title", Tags: []string{"new", "updated"}, Identifier: "20240101T120000", - FileType: "org", + FileType: FileTypeOrg, }, wantContains: []string{ "#+title: New Title", @@ -431,7 +431,7 @@ Content preserved`, Title: "New Title", Tags: []string{"new"}, Identifier: "20240101T120000", - FileType: "md-yaml", + FileType: FileTypeMdYaml, }, wantContains: []string{ "---", @@ -455,7 +455,7 @@ Content here`, Title: "New Title", Tags: []string{"updated"}, Identifier: "20240101T120000", - FileType: "md-toml", + FileType: FileTypeMdToml, }, wantContains: []string{ "+++", @@ -477,7 +477,7 @@ Text content`, Title: "New Title", Tags: []string{"new"}, Identifier: "20240101T120000", - FileType: "txt", + FileType: FileTypeTxt, }, wantContains: []string{ "title: New Title", @@ -494,7 +494,7 @@ Text content`, Title: "Added Title", Tags: []string{"added"}, Identifier: "20240101T120000", - FileType: "org", + FileType: FileTypeOrg, }, wantContains: []string{ "#+title: Added Title", @@ -510,7 +510,7 @@ Text content`, Title: "Added Title", Tags: []string{"added"}, Identifier: "20240101T120000", - FileType: "md-yaml", + FileType: FileTypeMdYaml, }, wantContains: []string{ "---", @@ -524,27 +524,27 @@ Text content`, for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := UpdateFrontMatter(tt.original, tt.fm) + got, err := Apply(tt.original, tt.fm) if err != nil { - t.Fatalf("UpdateFrontMatter() error = %v", err) + t.Fatalf("Apply() error = %v", err) } for _, want := range tt.wantContains { if !strings.Contains(got, want) { - t.Errorf("UpdateFrontMatter() missing %q\nGot:\n%s", want, got) + t.Errorf("Apply() missing %q\nGot:\n%s", want, got) } } // Verify original content is preserved if !strings.Contains(got, tt.wantPreserve) { - t.Errorf("UpdateFrontMatter() didn't preserve %q\nGot:\n%s", tt.wantPreserve, got) + t.Errorf("Apply() didn't preserve %q\nGot:\n%s", tt.wantPreserve, got) } }) } } -// TestUpdateFrontMatterEmptyTags validates updating with empty tags -func TestUpdateFrontMatterEmptyTags(t *testing.T) { +// TestApplyEmptyTags validates updating with empty tags +func TestApplyEmptyTags(t *testing.T) { original := `--- title: Test tags: [old, tags] @@ -557,27 +557,27 @@ Content` Title: "Test", Tags: []string{}, Identifier: "20240101T120000", - FileType: "md-yaml", + FileType: FileTypeMdYaml, } - got, err := UpdateFrontMatter(original, fm) + got, err := Apply(original, fm) if err != nil { - t.Fatalf("UpdateFrontMatter() error = %v", err) + t.Fatalf("Apply() error = %v", err) } // Should have empty tags field, not omit it if !strings.Contains(got, "tags:") { - t.Errorf("UpdateFrontMatter() should include tags field even when empty") + t.Errorf("Apply() should include tags field even when empty") } // Content should be preserved if !strings.Contains(got, "Content") { - t.Errorf("UpdateFrontMatter() should preserve content") + t.Errorf("Apply() should preserve content") } } -// TestUpdateFrontMatterUnsupportedType validates error handling -func TestUpdateFrontMatterUnsupportedType(t *testing.T) { +// TestApplyUnsupportedType validates error handling +func TestApplyUnsupportedType(t *testing.T) { fm := &FrontMatter{ Title: "Test", Tags: []string{"test"}, @@ -585,36 +585,33 @@ func TestUpdateFrontMatterUnsupportedType(t *testing.T) { FileType: "unsupported", } - _, err := UpdateFrontMatter("content", fm) + _, err := Apply("content", fm) if err == nil { - t.Error("UpdateFrontMatter() should error on unsupported file type") + t.Error("Apply() should error on unsupported file type") } if !strings.Contains(err.Error(), "unsupported file type") { - t.Errorf("UpdateFrontMatter() error = %v, want 'unsupported file type'", err) + t.Errorf("Apply() error = %v, want 'unsupported file type'", err) } } -// TestFileExtensions validates file extension mapping -func TestFileExtensions(t *testing.T) { +// TestGetExtension validates file extension mapping +func TestGetExtension(t *testing.T) { tests := []struct { - fileType string + fileType FileType want string }{ - {"org", ".org"}, - {"md-yaml", ".md"}, - {"md-toml", ".md"}, - {"txt", ".txt"}, + {FileTypeOrg, ".org"}, + {FileTypeMdYaml, ".md"}, + {FileTypeMdToml, ".md"}, + {FileTypeTxt, ".txt"}, } for _, tt := range tests { - t.Run(tt.fileType, func(t *testing.T) { - got, ok := fileExtensions[tt.fileType] - if !ok { - t.Errorf("fileExtensions[%q] not found", tt.fileType) - } + t.Run(string(tt.fileType), func(t *testing.T) { + got := GetExtension(tt.fileType) if got != tt.want { - t.Errorf("fileExtensions[%q] = %q, want %q", tt.fileType, got, tt.want) + t.Errorf("GetExtension(%q) = %q, want %q", tt.fileType, got, tt.want) } }) } @@ -622,18 +619,19 @@ func TestFileExtensions(t *testing.T) { // TestFrontMatterRoundTrip validates generate -> parse -> update cycle func TestFrontMatterRoundTrip(t *testing.T) { - fileTypes := []string{"org", "md-yaml", "md-toml", "txt"} + fileTypes := []FileType{FileTypeOrg, FileTypeMdYaml, FileTypeMdToml, FileTypeTxt} title := "Test Note" tags := []string{"tag1", "tag2"} identifier := "20240101T120000" for _, fileType := range fileTypes { - t.Run(fileType, func(t *testing.T) { + t.Run(string(fileType), func(t *testing.T) { // Generate front matter - content := Generate(title, "", tags, fileType, identifier) + fm := NewFrontMatter(title, "", tags, fileType, identifier) + content := string(fm.Bytes()) // Parse it back - ext := fileExtensions[fileType] + ext := GetExtension(fileType) fm, err := ParseFrontMatter(content, ext) if err != nil { t.Fatalf("ParseFrontMatter() error = %v", err) @@ -659,9 +657,9 @@ func TestFrontMatterRoundTrip(t *testing.T) { fm.Title = newTitle fm.Tags = newTags - updated, err := UpdateFrontMatter(content, fm) + updated, err := Apply(content, fm) if err != nil { - t.Fatalf("UpdateFrontMatter() error = %v", err) + t.Fatalf("Apply() error = %v", err) } // Parse again and verify diff --git a/pkg/metadata/metadata.go b/pkg/metadata/metadata.go index 031e822..97bb7fb 100644 --- a/pkg/metadata/metadata.go +++ b/pkg/metadata/metadata.go @@ -236,13 +236,3 @@ func BuildFilename(identifier, signature, title string, keywords []string, ext s return fmt.Sprintf("%s%s--%s%s%s", identifier, signaturePart, titleSlug, keywordsPart, ext) } -// GenerateNote creates a new note with generated identifier and content. -// Returns (path, content). -func GenerateNote(dir, title, signature string, keywords []string, fileType string) (string, string) { - identifier := GenerateIdentifier() - ext := fileExtensions[fileType] - filename := BuildFilename(identifier, signature, title, keywords, ext) - path := filepath.Join(dir, filename) - content := Generate(title, signature, keywords, fileType, identifier) - return path, content -} diff --git a/pkg/metadata/metadata_test.go b/pkg/metadata/metadata_test.go index da8e230..8c31ccd 100644 --- a/pkg/metadata/metadata_test.go +++ b/pkg/metadata/metadata_test.go @@ -3,7 +3,6 @@ package metadata import ( "regexp" "slices" - "strings" "testing" "time" ) @@ -772,60 +771,3 @@ func TestResultsFromString(t *testing.T) { }) } } - -// TestGenerateNote validates note generation (integration test) -func TestGenerateNote(t *testing.T) { - t.Run("without signature", func(t *testing.T) { - dir := "/tmp/notes" - title := "Test Note" - signature := "" - keywords := []string{"test", "example"} - fileType := "md-yaml" - - path, content := GenerateNote(dir, title, signature, keywords, fileType) - - // Validate path format - if !strings.HasPrefix(path, dir+"/") { - t.Errorf("GenerateNote() path = %q, should start with %q", path, dir+"/") - } - - // Validate filename contains identifier and title - if !strings.Contains(path, "--test-note__test_example.md") { - t.Errorf("GenerateNote() path = %q, should contain proper filename", path) - } - - // Validate content has front matter - if !strings.Contains(content, "---") { - t.Errorf("GenerateNote() content should contain YAML front matter") - } - if !strings.Contains(content, "title:") { - t.Errorf("GenerateNote() content should contain title field") - } - if !strings.Contains(content, "[test, example]") { - t.Errorf("GenerateNote() content should contain formatted tags") - } - }) - - t.Run("with signature", func(t *testing.T) { - dir := "/tmp/notes" - title := "Test Note" - signature := "hello" - keywords := []string{"test"} - fileType := "md-yaml" - - path, content := GenerateNote(dir, title, signature, keywords, fileType) - - // Validate filename contains signature - if !strings.Contains(path, "==hello--test-note__test.md") { - t.Errorf("GenerateNote() path = %q, should contain signature ==hello", path) - } - - // Validate content has signature in front matter - if !strings.Contains(content, "signature:") { - t.Errorf("GenerateNote() content should contain signature field") - } - if !strings.Contains(content, "signature: hello") { - t.Errorf("GenerateNote() content should contain signature value") - } - }) -}