-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathglob.go
162 lines (141 loc) · 3.59 KB
/
glob.go
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package pcre
import (
"io/fs"
"os"
"path/filepath"
"strings"
"unsafe"
"go.elara.ws/pcre/lib"
"modernc.org/libc"
)
// ConvertGlob converts the given glob into a
// pcre regular expression, and then returns
// the result.
func ConvertGlob(glob string) (string, error) {
tls := libc.NewTLS()
defer tls.Close()
// Get C string from given glob
cGlob, err := libc.CString(glob)
if err != nil {
return "", err
}
defer libc.Xfree(tls, cGlob)
// Convert length to size_t
cGlobLen := lib.Tsize_t(len(glob))
// Create null pointer
outPtr := uintptr(0)
// Get pointer to pointer
cOutPtr := uintptr(unsafe.Pointer(&outPtr))
// Create 0 size_t
outLen := lib.Tsize_t(0)
// Get pointer to size_t
cOutLen := uintptr(unsafe.Pointer(&outLen))
// Convert glob to regular expression
ret := lib.Xpcre2_pattern_convert_8(
tls,
cGlob,
cGlobLen,
lib.DPCRE2_CONVERT_GLOB,
cOutPtr,
cOutLen,
0,
)
if ret != 0 {
return "", codeToError(tls, ret)
}
defer lib.Xpcre2_converted_pattern_free_8(tls, outPtr)
// Get output as byte slice
out := unsafe.Slice((*byte)(unsafe.Pointer(outPtr)), outLen)
// Convert output to string
// This copies the data, so it's safe for later use
return string(out), nil
}
// CompileGlob is a convenience function that converts
// a glob to a pcre regular expression and then compiles
// it.
func CompileGlob(glob string) (*Regexp, error) {
pattern, err := ConvertGlob(glob)
if err != nil {
return nil, err
}
// Compile converted glob and return results
return Compile(pattern)
}
// Glob returns a list of matches for the given glob pattern.
// It returns nil if there was no match. If the glob contains
// "**", it will recurse through the directory, which may be
// extremely slow depending on which directory is being searched.
func Glob(glob string) ([]string, error) {
// If glob is empty, return nil
if glob == "" {
return nil, nil
}
// If the glob is a file path, return the file
_, err := os.Lstat(glob)
if err == nil {
return []string{glob}, nil
}
// If the glob has no glob characters, return nil
if !hasGlobChars(glob) {
return nil, nil
}
// Split glob by filepath separator
paths := strings.Split(glob, string(filepath.Separator))
var splitDir []string
// For every path in split list
for _, path := range paths {
// If glob characters forund, stop
if hasGlobChars(path) {
break
}
// Add path to splitDir
splitDir = append(splitDir, path)
}
// Join splitDir and add filepath separator. This is the directory that will be searched.
dir := filepath.Join(splitDir...)
if filepath.IsAbs(glob) {
dir = string(filepath.Separator) + dir
}
// If the directory is not accessible, return error
_, err = os.Lstat(dir)
if err != nil {
return nil, err
}
// Compile glob pattern
r, err := CompileGlob(glob)
if err != nil {
return nil, err
}
defer r.Close()
var matches []string
// If glob contains "**" (starstar), walk recursively. Otherwise, only search dir.
if strings.Contains(glob, "**") {
err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if r.MatchString(path) {
matches = append(matches, path)
}
return nil
})
if err != nil {
return nil, err
}
} else {
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, file := range files {
// Get full path of file
path := filepath.Join(dir, file.Name())
if r.MatchString(path) {
matches = append(matches, path)
}
}
}
return matches, nil
}
// hasGlobChars checks if the string has any
// characters that are part of a glob.
func hasGlobChars(s string) bool {
return strings.ContainsAny(s, "*[]?")
}