github.com/mattn/go-zglob@v0.0.5-0.20230108120541-9c2404f90757/zglob.go (about) 1 package zglob 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path" 8 "path/filepath" 9 "regexp" 10 "runtime" 11 "strings" 12 "sync" 13 14 "github.com/mattn/go-zglob/fastwalk" 15 ) 16 17 var ( 18 envre = regexp.MustCompile(`^(\$[a-zA-Z][a-zA-Z0-9_]+|\$\([a-zA-Z][a-zA-Z0-9_]+\))$`) 19 mu sync.Mutex 20 ) 21 22 type zenv struct { 23 dirmask string 24 fre *regexp.Regexp 25 braceDir bool 26 pattern string 27 root string 28 } 29 30 func toSlash(path string) string { 31 if filepath.Separator == '/' { 32 return path 33 } 34 var buf bytes.Buffer 35 cc := []rune(path) 36 for i := 0; i < len(cc); i++ { 37 if i < len(cc)-2 && cc[i] == '\\' && (cc[i+1] == '{' || cc[i+1] == '}') { 38 buf.WriteRune(cc[i]) 39 buf.WriteRune(cc[i+1]) 40 i++ 41 } else if cc[i] == '\\' { 42 buf.WriteRune('/') 43 } else { 44 buf.WriteRune(cc[i]) 45 } 46 } 47 return buf.String() 48 } 49 50 func New(pattern string) (*zenv, error) { 51 globmask := "" 52 root := "" 53 for n, i := range strings.Split(toSlash(pattern), "/") { 54 if root == "" && (strings.Index(i, "*") != -1 || strings.Index(i, "{") != -1) { 55 if globmask == "" { 56 root = "." 57 } else { 58 root = toSlash(globmask) 59 } 60 } 61 if n == 0 && i == "~" { 62 if runtime.GOOS == "windows" { 63 i = os.Getenv("USERPROFILE") 64 } else { 65 i = os.Getenv("HOME") 66 } 67 } 68 if envre.MatchString(i) { 69 i = strings.Trim(strings.Trim(os.Getenv(i[1:]), "()"), `"`) 70 } 71 72 globmask = path.Join(globmask, i) 73 if n == 0 { 74 if runtime.GOOS == "windows" && filepath.VolumeName(i) != "" { 75 globmask = i + "/" 76 } else if len(globmask) == 0 { 77 globmask = "/" 78 } 79 } 80 } 81 if root == "" { 82 return &zenv{ 83 dirmask: "", 84 fre: nil, 85 pattern: pattern, 86 root: "", 87 }, nil 88 } 89 if globmask == "" { 90 globmask = "." 91 } 92 globmask = toSlash(path.Clean(globmask)) 93 94 cc := []rune(globmask) 95 dirmask := "" 96 filemask := "" 97 staticDir := true 98 for i := 0; i < len(cc); i++ { 99 if i < len(cc)-2 && cc[i] == '\\' { 100 i++ 101 filemask += fmt.Sprintf("[\\x%02X]", cc[i]) 102 if staticDir { 103 dirmask += string(cc[i]) 104 } 105 } else if cc[i] == '*' { 106 staticDir = false 107 if i < len(cc)-2 && cc[i+1] == '*' && cc[i+2] == '/' { 108 filemask += "(.*/)?" 109 i += 2 110 } else { 111 filemask += "[^/]*" 112 } 113 } else { 114 if cc[i] == '{' { 115 staticDir = false 116 pattern := "" 117 for j := i + 1; j < len(cc); j++ { 118 if cc[j] == ',' { 119 pattern += "|" 120 } else if cc[j] == '}' { 121 i = j 122 break 123 } else { 124 c := cc[j] 125 if c == '/' { 126 pattern += string(c) 127 } else if ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c { 128 pattern += string(c) 129 } else { 130 pattern += fmt.Sprintf("[\\x%02X]", c) 131 } 132 } 133 } 134 if pattern != "" { 135 filemask += "(" + pattern + ")" 136 continue 137 } 138 } else if i < len(cc)-1 && cc[i] == '!' && cc[i+1] == '(' { 139 i++ 140 pattern := "" 141 for j := i + 1; j < len(cc); j++ { 142 if cc[j] == ')' { 143 i = j 144 break 145 } else { 146 c := cc[j] 147 pattern += fmt.Sprintf("[^\\x%02X/]*", c) 148 } 149 } 150 if pattern != "" { 151 if dirmask == "" { 152 dirmask = filemask 153 root = filemask 154 } 155 filemask += pattern 156 continue 157 } 158 } 159 c := cc[i] 160 if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c { 161 filemask += string(c) 162 } else { 163 filemask += fmt.Sprintf("[\\x%02X]", c) 164 } 165 if staticDir { 166 dirmask += string(c) 167 } 168 } 169 } 170 if len(filemask) > 0 && filemask[len(filemask)-1] == '/' { 171 if root == "" { 172 root = filemask 173 } 174 filemask += "[^/]*" 175 } 176 if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { 177 filemask = "(?i:" + filemask + ")" 178 } 179 return &zenv{ 180 dirmask: path.Dir(dirmask) + "/", 181 fre: regexp.MustCompile("^" + filemask + "$"), 182 pattern: pattern, 183 root: filepath.Clean(root), 184 }, nil 185 } 186 187 func Glob(pattern string) ([]string, error) { 188 return glob(pattern, false) 189 } 190 191 func GlobFollowSymlinks(pattern string) ([]string, error) { 192 return glob(pattern, true) 193 } 194 195 func glob(pattern string, followSymlinks bool) ([]string, error) { 196 zenv, err := New(pattern) 197 if err != nil { 198 return nil, err 199 } 200 if zenv.root == "" { 201 _, err := os.Stat(pattern) 202 if err != nil { 203 return nil, os.ErrNotExist 204 } 205 return []string{pattern}, nil 206 } 207 relative := !filepath.IsAbs(pattern) 208 matches := []string{} 209 210 err = fastwalk.FastWalk(zenv.root, func(path string, info os.FileMode) error { 211 if zenv.root == "." && len(zenv.root) < len(path) { 212 path = path[len(zenv.root)+1:] 213 } 214 path = filepath.ToSlash(path) 215 216 if followSymlinks && info == os.ModeSymlink { 217 followedPath, err := filepath.EvalSymlinks(path) 218 if err == nil { 219 fi, err := os.Lstat(followedPath) 220 if err == nil && fi.IsDir() { 221 return fastwalk.TraverseLink 222 } 223 } 224 } 225 226 if info.IsDir() { 227 if path == "." || len(path) <= len(zenv.root) { 228 return nil 229 } 230 if zenv.fre.MatchString(path) { 231 mu.Lock() 232 matches = append(matches, path) 233 mu.Unlock() 234 return nil 235 } 236 if len(path) < len(zenv.dirmask) && !strings.HasPrefix(zenv.dirmask, path+"/") { 237 return filepath.SkipDir 238 } 239 } 240 241 if zenv.fre.MatchString(path) { 242 if relative && filepath.IsAbs(path) { 243 path = path[len(zenv.root)+1:] 244 } 245 mu.Lock() 246 matches = append(matches, path) 247 mu.Unlock() 248 } 249 return nil 250 }) 251 252 if err != nil { 253 return nil, err 254 } 255 256 return matches, nil 257 } 258 259 func Match(pattern, name string) (matched bool, err error) { 260 zenv, err := New(pattern) 261 if err != nil { 262 return false, err 263 } 264 return zenv.Match(name), nil 265 } 266 267 func (z *zenv) Match(name string) bool { 268 if z.root == "" { 269 return z.pattern == name 270 } 271 272 name = filepath.ToSlash(name) 273 274 if name == "." || len(name) <= len(z.root) { 275 return false 276 } 277 278 if z.fre.MatchString(name) { 279 return true 280 } 281 return false 282 }