aboutsummaryrefslogtreecommitdiffstats
path: root/internal/walker/walker.go
blob: 0ac470dc8a819b33d1ace550d3212dc00fe3e6ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package walker

import (
	"os"
	"path/filepath"
	"strings"

	ignore "github.com/sabhiram/go-gitignore"
)

// DefaultIgnore patterns applied to all walks
var DefaultIgnore = []string{
	"vendor/",
	"node_modules/",
	".git/",
	".codevec/",
}

// Walker walks a directory tree finding files to index
type Walker struct {
	root       string
	extensions []string // e.g., [".go"]
	gitignore  *ignore.GitIgnore
}

// New creates a walker for the given root directory
func New(root string, extensions []string) (*Walker, error) {
	root, err := filepath.Abs(root)
	if err != nil {
		return nil, err
	}

	w := &Walker{
		root:       root,
		extensions: extensions,
	}

	// Load .gitignore if present
	gitignorePath := filepath.Join(root, ".gitignore")
	if _, err := os.Stat(gitignorePath); err == nil {
		gi, err := ignore.CompileIgnoreFile(gitignorePath)
		if err == nil {
			w.gitignore = gi
		}
	}

	return w, nil
}

// Walk returns all matching files in the directory tree
func (w *Walker) Walk() ([]string, error) {
	var files []string

	err := filepath.WalkDir(w.root, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// Get path relative to root for ignore matching
		relPath, err := filepath.Rel(w.root, path)
		if err != nil {
			return err
		}

		// Skip default ignored directories
		if d.IsDir() {
			for _, pattern := range DefaultIgnore {
				if strings.HasPrefix(relPath+"/", pattern) || relPath+"/" == pattern {
					return filepath.SkipDir
				}
			}
		}

		// Skip if matched by .gitignore
		if w.gitignore != nil && w.gitignore.MatchesPath(relPath) {
			if d.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		// Skip directories and non-matching extensions
		if d.IsDir() {
			return nil
		}

		if !w.matchesExtension(path) {
			return nil
		}

		files = append(files, path)
		return nil
	})

	return files, err
}

func (w *Walker) matchesExtension(path string) bool {
	if len(w.extensions) == 0 {
		return true
	}
	ext := filepath.Ext(path)
	for _, e := range w.extensions {
		if ext == e {
			return true
		}
	}
	return false
}