| | |
| | |
| | |
| |
|
| | package filepath |
| |
|
| | import ( |
| | "errors" |
| | "internal/filepathlite" |
| | "os" |
| | "runtime" |
| | "slices" |
| | "strings" |
| | "unicode/utf8" |
| | ) |
| |
|
| | |
| | var ErrBadPattern = errors.New("syntax error in pattern") |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func Match(pattern, name string) (matched bool, err error) { |
| | Pattern: |
| | for len(pattern) > 0 { |
| | var star bool |
| | var chunk string |
| | star, chunk, pattern = scanChunk(pattern) |
| | if star && chunk == "" { |
| | |
| | return !strings.Contains(name, string(Separator)), nil |
| | } |
| | |
| | t, ok, err := matchChunk(chunk, name) |
| | |
| | |
| | |
| | if ok && (len(t) == 0 || len(pattern) > 0) { |
| | name = t |
| | continue |
| | } |
| | if err != nil { |
| | return false, err |
| | } |
| | if star { |
| | |
| | |
| | for i := 0; i < len(name) && name[i] != Separator; i++ { |
| | t, ok, err := matchChunk(chunk, name[i+1:]) |
| | if ok { |
| | |
| | if len(pattern) == 0 && len(t) > 0 { |
| | continue |
| | } |
| | name = t |
| | continue Pattern |
| | } |
| | if err != nil { |
| | return false, err |
| | } |
| | } |
| | } |
| | return false, nil |
| | } |
| | return len(name) == 0, nil |
| | } |
| |
|
| | |
| | |
| | func scanChunk(pattern string) (star bool, chunk, rest string) { |
| | for len(pattern) > 0 && pattern[0] == '*' { |
| | pattern = pattern[1:] |
| | star = true |
| | } |
| | inrange := false |
| | for i := 0; i < len(pattern); i++ { |
| | switch pattern[i] { |
| | case '\\': |
| | |
| | if runtime.GOOS != "windows" && i+1 < len(pattern) { |
| | i++ |
| | } |
| | case '[': |
| | inrange = true |
| | case ']': |
| | inrange = false |
| | case '*': |
| | if !inrange { |
| | return star, pattern[:i], pattern[i:] |
| | } |
| | } |
| | } |
| | return star, pattern, "" |
| | } |
| |
|
| | |
| | |
| | |
| | func matchChunk(chunk, s string) (rest string, ok bool, err error) { |
| | |
| | |
| | |
| | failed := false |
| | for len(chunk) > 0 { |
| | failed = failed || len(s) == 0 |
| | switch chunk[0] { |
| | case '[': |
| | |
| | var r rune |
| | if !failed { |
| | var n int |
| | r, n = utf8.DecodeRuneInString(s) |
| | s = s[n:] |
| | } |
| | chunk = chunk[1:] |
| | |
| | negated := false |
| | if len(chunk) > 0 && chunk[0] == '^' { |
| | negated = true |
| | chunk = chunk[1:] |
| | } |
| | |
| | match := false |
| | nrange := 0 |
| | for { |
| | if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { |
| | chunk = chunk[1:] |
| | break |
| | } |
| | var lo, hi rune |
| | if lo, chunk, err = getEsc(chunk); err != nil { |
| | return "", false, err |
| | } |
| | hi = lo |
| | if chunk[0] == '-' { |
| | if hi, chunk, err = getEsc(chunk[1:]); err != nil { |
| | return "", false, err |
| | } |
| | } |
| | match = match || lo <= r && r <= hi |
| | nrange++ |
| | } |
| | failed = failed || match == negated |
| |
|
| | case '?': |
| | if !failed { |
| | failed = s[0] == Separator |
| | _, n := utf8.DecodeRuneInString(s) |
| | s = s[n:] |
| | } |
| | chunk = chunk[1:] |
| |
|
| | case '\\': |
| | if runtime.GOOS != "windows" { |
| | chunk = chunk[1:] |
| | if len(chunk) == 0 { |
| | return "", false, ErrBadPattern |
| | } |
| | } |
| | fallthrough |
| |
|
| | default: |
| | if !failed { |
| | failed = chunk[0] != s[0] |
| | s = s[1:] |
| | } |
| | chunk = chunk[1:] |
| | } |
| | } |
| | if failed { |
| | return "", false, nil |
| | } |
| | return s, true, nil |
| | } |
| |
|
| | |
| | func getEsc(chunk string) (r rune, nchunk string, err error) { |
| | if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { |
| | err = ErrBadPattern |
| | return |
| | } |
| | if chunk[0] == '\\' && runtime.GOOS != "windows" { |
| | chunk = chunk[1:] |
| | if len(chunk) == 0 { |
| | err = ErrBadPattern |
| | return |
| | } |
| | } |
| | r, n := utf8.DecodeRuneInString(chunk) |
| | if r == utf8.RuneError && n == 1 { |
| | err = ErrBadPattern |
| | } |
| | nchunk = chunk[n:] |
| | if len(nchunk) == 0 { |
| | err = ErrBadPattern |
| | } |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func Glob(pattern string) (matches []string, err error) { |
| | return globWithLimit(pattern, 0) |
| | } |
| |
|
| | func globWithLimit(pattern string, depth int) (matches []string, err error) { |
| | |
| | const pathSeparatorsLimit = 10000 |
| | if depth == pathSeparatorsLimit { |
| | return nil, ErrBadPattern |
| | } |
| |
|
| | |
| | if _, err := Match(pattern, ""); err != nil { |
| | return nil, err |
| | } |
| | if !hasMeta(pattern) { |
| | if _, err = os.Lstat(pattern); err != nil { |
| | return nil, nil |
| | } |
| | return []string{pattern}, nil |
| | } |
| |
|
| | dir, file := Split(pattern) |
| | volumeLen := 0 |
| | if runtime.GOOS == "windows" { |
| | volumeLen, dir = cleanGlobPathWindows(dir) |
| | } else { |
| | dir = cleanGlobPath(dir) |
| | } |
| |
|
| | if !hasMeta(dir[volumeLen:]) { |
| | return glob(dir, file, nil) |
| | } |
| |
|
| | |
| | if dir == pattern { |
| | return nil, ErrBadPattern |
| | } |
| |
|
| | var m []string |
| | m, err = globWithLimit(dir, depth+1) |
| | if err != nil { |
| | return |
| | } |
| | for _, d := range m { |
| | matches, err = glob(d, file, matches) |
| | if err != nil { |
| | return |
| | } |
| | } |
| | return |
| | } |
| |
|
| | |
| | func cleanGlobPath(path string) string { |
| | switch path { |
| | case "": |
| | return "." |
| | case string(Separator): |
| | |
| | return path |
| | default: |
| | return path[0 : len(path)-1] |
| | } |
| | } |
| |
|
| | |
| | func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) { |
| | vollen := filepathlite.VolumeNameLen(path) |
| | switch { |
| | case path == "": |
| | return 0, "." |
| | case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): |
| | |
| | return vollen + 1, path |
| | case vollen == len(path) && len(path) == 2: |
| | return vollen, path + "." |
| | default: |
| | if vollen >= len(path) { |
| | vollen = len(path) - 1 |
| | } |
| | return vollen, path[0 : len(path)-1] |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func glob(dir, pattern string, matches []string) (m []string, e error) { |
| | m = matches |
| | fi, err := os.Stat(dir) |
| | if err != nil { |
| | return |
| | } |
| | if !fi.IsDir() { |
| | return |
| | } |
| | d, err := os.Open(dir) |
| | if err != nil { |
| | return |
| | } |
| | defer d.Close() |
| |
|
| | names, _ := d.Readdirnames(-1) |
| | slices.Sort(names) |
| |
|
| | for _, n := range names { |
| | matched, err := Match(pattern, n) |
| | if err != nil { |
| | return m, err |
| | } |
| | if matched { |
| | m = append(m, Join(dir, n)) |
| | } |
| | } |
| | return |
| | } |
| |
|
| | |
| | |
| | func hasMeta(path string) bool { |
| | magicChars := `*?[` |
| | if runtime.GOOS != "windows" { |
| | magicChars = `*?[\` |
| | } |
| | return strings.ContainsAny(path, magicChars) |
| | } |
| |
|