github.com/spetr/go-zglob@v0.0.2/zglob.go (about) 1 package zglob 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "regexp" 8 "runtime" 9 "strings" 10 "sync" 11 12 "github.com/spetr/go-zglob/fastwalk" 13 ) 14 15 var ( 16 envre = regexp.MustCompile(`^(\$[a-zA-Z][a-zA-Z0-9_]+|\$\([a-zA-Z][a-zA-Z0-9_]+\))$`) 17 mu sync.Mutex 18 ) 19 20 type zenv struct { 21 dre *regexp.Regexp 22 fre *regexp.Regexp 23 pattern string 24 root string 25 } 26 27 func New(pattern string) (*zenv, error) { 28 globmask := "" 29 root := "" 30 for n, i := range strings.Split(filepath.ToSlash(pattern), "/") { 31 if root == "" && strings.Index(i, "*") != -1 { 32 if globmask == "" { 33 root = "." 34 } else { 35 root = filepath.ToSlash(globmask) 36 } 37 } 38 if n == 0 && i == "~" { 39 if runtime.GOOS == "windows" { 40 i = os.Getenv("USERPROFILE") 41 } else { 42 i = os.Getenv("HOME") 43 } 44 } 45 if envre.MatchString(i) { 46 i = strings.Trim(strings.Trim(os.Getenv(i[1:]), "()"), `"`) 47 } 48 49 globmask = filepath.Join(globmask, i) 50 if n == 0 { 51 if runtime.GOOS == "windows" && filepath.VolumeName(i) != "" { 52 globmask = i + "/" 53 } else if len(globmask) == 0 { 54 globmask = "/" 55 } 56 } 57 } 58 if root == "" { 59 return &zenv{ 60 dre: nil, 61 fre: nil, 62 pattern: pattern, 63 root: "", 64 }, nil 65 } 66 if globmask == "" { 67 globmask = "." 68 } 69 globmask = filepath.ToSlash(filepath.Clean(globmask)) 70 71 cc := []rune(globmask) 72 dirmask := "" 73 filemask := "" 74 for i := 0; i < len(cc); i++ { 75 if cc[i] == '*' { 76 if i < len(cc)-2 && cc[i+1] == '*' && cc[i+2] == '/' { 77 filemask += "(.*/)?" 78 if dirmask == "" { 79 dirmask = filemask 80 } 81 i += 2 82 } else { 83 filemask += "[^/]*" 84 } 85 } else { 86 c := cc[i] 87 if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c { 88 filemask += string(c) 89 } else { 90 filemask += fmt.Sprintf("[\\x%02X]", c) 91 } 92 if c == '/' && dirmask == "" && strings.Index(filemask, "*") != -1 { 93 dirmask = filemask 94 } 95 } 96 } 97 if dirmask == "" { 98 dirmask = filemask 99 } 100 if len(filemask) > 0 && filemask[len(filemask)-1] == '/' { 101 if root == "" { 102 root = filemask 103 } 104 filemask += "[^/]*" 105 } 106 if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { 107 dirmask = "(?i:" + dirmask + ")" 108 filemask = "(?i:" + filemask + ")" 109 } 110 return &zenv{ 111 dre: regexp.MustCompile("^" + dirmask), 112 fre: regexp.MustCompile("^" + filemask + "$"), 113 pattern: pattern, 114 root: filepath.Clean(root), 115 }, nil 116 } 117 118 func Glob(pattern string) ([]string, error) { 119 return glob(pattern, false) 120 } 121 122 func GlobFollowSymlinks(pattern string) ([]string, error) { 123 return glob(pattern, true) 124 } 125 126 func glob(pattern string, followSymlinks bool) ([]string, error) { 127 zenv, err := New(pattern) 128 if err != nil { 129 return nil, err 130 } 131 if zenv.root == "" { 132 _, err := os.Stat(pattern) 133 if err != nil { 134 return nil, os.ErrNotExist 135 } 136 return []string{pattern}, nil 137 } 138 relative := !filepath.IsAbs(pattern) 139 matches := []string{} 140 141 fastwalk.FastWalk(zenv.root, func(path string, info os.FileMode) error { 142 if zenv.root == "." && len(zenv.root) < len(path) { 143 path = path[len(zenv.root)+1:] 144 } 145 path = filepath.ToSlash(path) 146 147 if followSymlinks && info == os.ModeSymlink { 148 followedPath, err := filepath.EvalSymlinks(path) 149 if err == nil { 150 fi, err := os.Lstat(followedPath) 151 if err == nil && fi.IsDir() { 152 return fastwalk.TraverseLink 153 } 154 } 155 } 156 157 if info.IsDir() { 158 if path == "." || len(path) <= len(zenv.root) { 159 return nil 160 } 161 if zenv.fre.MatchString(path) { 162 mu.Lock() 163 matches = append(matches, path) 164 mu.Unlock() 165 return nil 166 } 167 if !zenv.dre.MatchString(path + "/") { 168 return filepath.SkipDir 169 } 170 } 171 172 if zenv.fre.MatchString(path) { 173 if relative && filepath.IsAbs(path) { 174 path = path[len(zenv.root)+1:] 175 } 176 mu.Lock() 177 matches = append(matches, path) 178 mu.Unlock() 179 } 180 return nil 181 }) 182 return matches, nil 183 } 184 185 func Match(pattern, name string) (matched bool, err error) { 186 zenv, err := New(pattern) 187 if err != nil { 188 return false, err 189 } 190 return zenv.Match(name), nil 191 } 192 193 func (z *zenv) Match(name string) bool { 194 if z.root == "" { 195 return z.pattern == name 196 } 197 198 name = filepath.ToSlash(name) 199 200 if name == "." || len(name) <= len(z.root) { 201 return false 202 } 203 204 if z.fre.MatchString(name) { 205 return true 206 } 207 return false 208 }