go.arsenm.dev/pcre@v0.0.0-20220530205550-74594f6c8b0e/glob.go (about) 1 package pcre 2 3 import ( 4 "io/fs" 5 "os" 6 "path/filepath" 7 "strings" 8 "unsafe" 9 10 "go.arsenm.dev/pcre/lib" 11 "modernc.org/libc" 12 ) 13 14 // ConvertGlob converts the given glob into a 15 // pcre regular expression, and then returns 16 // the result. 17 func ConvertGlob(glob string) (string, error) { 18 tls := libc.NewTLS() 19 defer tls.Close() 20 21 // Get C string from given glob 22 cGlob, err := libc.CString(glob) 23 if err != nil { 24 return "", err 25 } 26 defer libc.Xfree(tls, cGlob) 27 // Convert length to size_t 28 cGlobLen := lib.Tsize_t(len(glob)) 29 30 // Create null pointer 31 outPtr := uintptr(0) 32 // Get pointer to pointer 33 cOutPtr := uintptr(unsafe.Pointer(&outPtr)) 34 35 // Create 0 size_t 36 outLen := lib.Tsize_t(0) 37 // Get pointer to size_t 38 cOutLen := uintptr(unsafe.Pointer(&outLen)) 39 40 // Convert glob to regular expression 41 ret := lib.Xpcre2_pattern_convert_8( 42 tls, 43 cGlob, 44 cGlobLen, 45 lib.DPCRE2_CONVERT_GLOB, 46 cOutPtr, 47 cOutLen, 48 0, 49 ) 50 if ret != 0 { 51 return "", codeToError(tls, ret) 52 } 53 defer lib.Xpcre2_converted_pattern_free_8(tls, outPtr) 54 55 // Get output as byte slice 56 out := unsafe.Slice((*byte)(unsafe.Pointer(outPtr)), outLen) 57 // Convert output to string 58 // This copies the data, so it's safe for later use 59 return string(out), nil 60 } 61 62 // CompileGlob is a convenience function that converts 63 // a glob to a pcre regular expression and then compiles 64 // it. 65 func CompileGlob(glob string) (*Regexp, error) { 66 pattern, err := ConvertGlob(glob) 67 if err != nil { 68 return nil, err 69 } 70 // Compile converted glob and return results 71 return Compile(pattern) 72 } 73 74 // Glob returns a list of matches for the given glob pattern. 75 // It returns nil if there was no match. If the glob contains 76 // "**", it will recurse through the directory, which may be 77 // extremely slow depending on which directory is being searched. 78 func Glob(glob string) ([]string, error) { 79 // If glob is empty, return nil 80 if glob == "" { 81 return nil, nil 82 } 83 84 // If the glob is a file path, return the file 85 _, err := os.Lstat(glob) 86 if err == nil { 87 return []string{glob}, nil 88 } 89 90 // If the glob has no glob characters, return nil 91 if !hasGlobChars(glob) { 92 return nil, nil 93 } 94 95 // Split glob by filepath separator 96 paths := strings.Split(glob, string(filepath.Separator)) 97 98 var splitDir []string 99 // For every path in split list 100 for _, path := range paths { 101 // If glob characters forund, stop 102 if hasGlobChars(path) { 103 break 104 } 105 // Add path to splitDir 106 splitDir = append(splitDir, path) 107 } 108 109 // Join splitDir and add filepath separator. This is the directory that will be searched. 110 dir := filepath.Join(splitDir...) 111 112 if filepath.IsAbs(glob) { 113 dir = string(filepath.Separator) + dir 114 } 115 116 // If the directory is not accessible, return error 117 _, err = os.Lstat(dir) 118 if err != nil { 119 return nil, err 120 } 121 122 // Compile glob pattern 123 r, err := CompileGlob(glob) 124 if err != nil { 125 return nil, err 126 } 127 defer r.Close() 128 129 var matches []string 130 // If glob contains "**" (starstar), walk recursively. Otherwise, only search dir. 131 if strings.Contains(glob, "**") { 132 err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 133 if r.MatchString(path) { 134 matches = append(matches, path) 135 } 136 return nil 137 }) 138 if err != nil { 139 return nil, err 140 } 141 } else { 142 files, err := os.ReadDir(dir) 143 if err != nil { 144 return nil, err 145 } 146 for _, file := range files { 147 // Get full path of file 148 path := filepath.Join(dir, file.Name()) 149 if r.MatchString(path) { 150 matches = append(matches, path) 151 } 152 } 153 } 154 155 return matches, nil 156 } 157 158 // hasGlobChars checks if the string has any 159 // characters that are part of a glob. 160 func hasGlobChars(s string) bool { 161 return strings.ContainsAny(s, "*[]?") 162 }