Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/sagikazarmark/locafero/finder.go
2875 views
1
// Package locafero looks for files and directories in an {fs.Fs} filesystem.
2
package locafero
3
4
import (
5
"errors"
6
"io/fs"
7
"path/filepath"
8
"strings"
9
10
"github.com/spf13/afero"
11
12
"github.com/sagikazarmark/locafero/internal/queue"
13
)
14
15
// Finder looks for files and directories in an [afero.Fs] filesystem.
16
type Finder struct {
17
// Paths represents a list of locations that the [Finder] will search in.
18
//
19
// They are essentially the root directories or starting points for the search.
20
//
21
// Examples:
22
// - home/user
23
// - etc
24
Paths []string
25
26
// Names are specific entries that the [Finder] will look for within the given Paths.
27
//
28
// It provides the capability to search for entries with depth,
29
// meaning it can target deeper locations within the directory structure.
30
//
31
// It also supports glob syntax (as defined by [filepath.Match]), offering greater flexibility in search patterns.
32
//
33
// Examples:
34
// - config.yaml
35
// - home/*/config.yaml
36
// - home/*/config.*
37
Names []string
38
39
// Type restricts the kind of entries returned by the [Finder].
40
//
41
// This parameter helps in differentiating and filtering out files from directories or vice versa.
42
Type FileType
43
}
44
45
// Find looks for files and directories in an [afero.Fs] filesystem.
46
func (f Finder) Find(fsys afero.Fs) ([]string, error) {
47
q := queue.NewEager[[]searchResult]()
48
49
for _, searchPath := range f.Paths {
50
for _, searchName := range f.Names {
51
q.Add(func() ([]searchResult, error) {
52
// If the name contains any glob character, perform a glob match
53
if strings.ContainsAny(searchName, globMatch) {
54
return globWalkSearch(fsys, searchPath, searchName, f.Type)
55
}
56
57
return statSearch(fsys, searchPath, searchName, f.Type)
58
})
59
}
60
}
61
62
searchResults, err := flatten(q.Wait())
63
if err != nil {
64
return nil, err
65
}
66
67
// Return early if no results were found
68
if len(searchResults) == 0 {
69
return nil, nil
70
}
71
72
results := make([]string, 0, len(searchResults))
73
74
for _, searchResult := range searchResults {
75
results = append(results, searchResult.path)
76
}
77
78
return results, nil
79
}
80
81
type searchResult struct {
82
path string
83
info fs.FileInfo
84
}
85
86
func flatten[T any](results [][]T, err error) ([]T, error) {
87
if err != nil {
88
return nil, err
89
}
90
91
var flattened []T
92
93
for _, r := range results {
94
flattened = append(flattened, r...)
95
}
96
97
return flattened, nil
98
}
99
100
func globWalkSearch(
101
fsys afero.Fs,
102
searchPath string,
103
searchName string,
104
searchType FileType,
105
) ([]searchResult, error) {
106
var results []searchResult
107
108
err := afero.Walk(fsys, searchPath, func(p string, fileInfo fs.FileInfo, err error) error {
109
if err != nil {
110
return err
111
}
112
113
// Skip the root path
114
if p == searchPath {
115
return nil
116
}
117
118
var result error
119
120
// Stop reading subdirectories
121
// TODO: add depth detection here
122
if fileInfo.IsDir() && filepath.Dir(p) == searchPath {
123
result = fs.SkipDir
124
}
125
126
// Skip unmatching type
127
if !searchType.match(fileInfo) {
128
return result
129
}
130
131
match, err := filepath.Match(searchName, fileInfo.Name())
132
if err != nil {
133
return err
134
}
135
136
if match {
137
results = append(results, searchResult{p, fileInfo})
138
}
139
140
return result
141
})
142
if err != nil {
143
return results, err
144
}
145
146
return results, nil
147
}
148
149
func statSearch(
150
fsys afero.Fs,
151
searchPath string,
152
searchName string,
153
searchType FileType,
154
) ([]searchResult, error) {
155
filePath := filepath.Join(searchPath, searchName)
156
157
fileInfo, err := fsys.Stat(filePath)
158
if errors.Is(err, fs.ErrNotExist) {
159
return nil, nil
160
}
161
if err != nil {
162
return nil, err
163
}
164
165
// Skip unmatching type
166
if !searchType.match(fileInfo) {
167
return nil, nil
168
}
169
170
return []searchResult{{filePath, fileInfo}}, nil
171
}
172
173