From d832ebc2fe2c31750c8d4c57e3220a409d6f8bd2 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 23:33:22 +0000 Subject: [PATCH] fix(help): address CodeRabbit review feedback - Add CRLF line ending support to frontmatter regex - Add empty frontmatter block support - Use filepath.Base/Ext for cross-platform path handling - Add tests for CRLF and empty frontmatter cases Co-Authored-By: Claude Opus 4.5 --- pkg/help/parser.go | 13 +++++++------ pkg/help/parser_test.go | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/pkg/help/parser.go b/pkg/help/parser.go index 516afee2..a92b490c 100644 --- a/pkg/help/parser.go +++ b/pkg/help/parser.go @@ -1,6 +1,7 @@ package help import ( + "path/filepath" "regexp" "strings" "unicode" @@ -10,7 +11,8 @@ import ( var ( // frontmatterRegex matches YAML frontmatter delimited by --- - frontmatterRegex = regexp.MustCompile(`(?s)^---\n(.+?)\n---\n?`) + // Supports both LF and CRLF line endings, and empty frontmatter blocks + frontmatterRegex = regexp.MustCompile(`(?s)^---\r?\n(.*?)(?:\r?\n)?---\r?\n?`) // headingRegex matches markdown headings (# to ######) headingRegex = regexp.MustCompile(`^(#{1,6})\s+(.+)$`) @@ -148,13 +150,12 @@ func GenerateID(title string) string { // pathToTitle converts a file path to a title. // "getting-started.md" -> "Getting Started" func pathToTitle(path string) string { - // Get filename without directory - parts := strings.Split(path, "/") - filename := parts[len(parts)-1] + // Get filename without directory (cross-platform) + filename := filepath.Base(path) // Remove extension - if idx := strings.LastIndex(filename, "."); idx != -1 { - filename = filename[:idx] + if ext := filepath.Ext(filename); ext != "" { + filename = strings.TrimSuffix(filename, ext) } // Replace hyphens/underscores with spaces diff --git a/pkg/help/parser_test.go b/pkg/help/parser_test.go index a9ea1c2c..b95cadc8 100644 --- a/pkg/help/parser_test.go +++ b/pkg/help/parser_test.go @@ -105,6 +105,29 @@ Some content here. assert.Equal(t, content, body) } +func TestExtractFrontmatter_Good_CRLF(t *testing.T) { + // Content with CRLF line endings (Windows-style) + content := "---\r\ntitle: CRLF Test\r\n---\r\n\r\n# Content" + + fm, body := ExtractFrontmatter(content) + + assert.NotNil(t, fm) + assert.Equal(t, "CRLF Test", fm.Title) + assert.Contains(t, body, "# Content") +} + +func TestExtractFrontmatter_Good_Empty(t *testing.T) { + // Empty frontmatter block + content := "---\n---\n# Content" + + fm, body := ExtractFrontmatter(content) + + // Empty frontmatter should parse successfully + assert.NotNil(t, fm) + assert.Equal(t, "", fm.Title) + assert.Contains(t, body, "# Content") +} + func TestExtractFrontmatter_Bad_InvalidYAML(t *testing.T) { content := `--- title: [invalid yaml