diff --git a/internal/filetree/filetree.go b/internal/filetree/filetree.go index 30d74da..ce0c838 100644 --- a/internal/filetree/filetree.go +++ b/internal/filetree/filetree.go @@ -28,9 +28,15 @@ func New(paths []string) *FileTree { tree := &FileTree{ root: make(Node), } - for _, path := range paths { - tree.addPath(strings.Split(path, "/")) + // Split path into components, automatically filtering empty parts + parts := strings.FieldsFunc(path, func(r rune) bool { + return r == '/' || r == '\\' + }) + + if len(parts) > 0 { + tree.addPath(parts) + } } return tree diff --git a/internal/filetree/filetree_test.go b/internal/filetree/filetree_test.go index 74e33cf..899f1af 100644 --- a/internal/filetree/filetree_test.go +++ b/internal/filetree/filetree_test.go @@ -68,4 +68,56 @@ func TestFileTree(t *testing.T) { t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, result) } }) + + t.Run("mixed path separators", func(t *testing.T) { + // Test that FileTree can handle mixed Windows and Unix paths + paths := []string{ + "file1.txt", + "dir\\subdir\\file2.txt", // Windows-style + "dir/file3.txt", // Unix-style + } + result := Build(paths, "") + expected := strings.Join([]string{ + "/", + "├── dir/", + "│ ├── subdir/", + "│ │ └── file2.txt", + "│ └── file3.txt", + "└── file1.txt", + }, "\n") + + if result != expected { + t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, result) + } + }) + + t.Run("edge cases with slashes", func(t *testing.T) { + // Test edge cases: double slashes, leading/trailing slashes + paths := []string{ + "normal/path.txt", + "double//slash.txt", // double slash + "/leading/slash.txt", // leading slash + "trailing/slash/.txt", // trailing slash + "multiple///slashes.txt", // multiple slashes + } + result := Build(paths, "") + expected := strings.Join([]string{ + "/", + "├── double/", + "│ └── slash.txt", + "├── leading/", + "│ └── slash.txt", + "├── multiple/", + "│ └── slashes.txt", + "├── normal/", + "│ └── path.txt", + "└── trailing/", + " └── slash/", + " └── .txt", + }, "\n") + + if result != expected { + t.Errorf("Expected:\n%s\n\nGot:\n%s", expected, result) + } + }) } diff --git a/internal/processor/processor.go b/internal/processor/processor.go index 7142dde..2d52280 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -212,19 +212,22 @@ func (p *Processor) collectFiles() ([]string, error) { if info.IsDir() { return nil } - - // Get relative path for ignore checking + // Get relative path and normalize separators for cross-platform consistency relPath, err := filepath.Rel(p.rootDir, path) if err != nil { return fmt.Errorf("failed to get relative path: %w", err) } - - // Skip if file matches ignore pattern - if p.matcher != nil && p.matcher.Match(strings.Split(relPath, "/"), false) { + + // Normalize to forward slashes for consistent processing + // This ensures gitignore patterns work and output is uniform across platforms + normalizedPath := filepath.ToSlash(relPath) + // Check gitignore patterns using normalized path + if p.matcher != nil && p.matcher.Match(strings.Split(normalizedPath, "/"), false) { return nil } - files = append(files, relPath) + // Store normalized path for consistent cross-platform output + files = append(files, normalizedPath) return nil }) @@ -274,6 +277,5 @@ func (p *Processor) writeContents(w *bufio.Writer, files []string) error { return err } } - return nil }