| // Copyright 2010 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package fs | |
| import ( | |
| "path" | |
| "runtime" | |
| ) | |
| // A GlobFS is a file system with a Glob method. | |
| type GlobFS interface { | |
| FS | |
| // Glob returns the names of all files matching pattern, | |
| // providing an implementation of the top-level | |
| // Glob function. | |
| Glob(pattern string) ([]string, error) | |
| } | |
| // Glob returns the names of all files matching pattern or nil | |
| // if there is no matching file. The syntax of patterns is the same | |
| // as in path.Match. The pattern may describe hierarchical names such as | |
| // /usr/*/bin/ed (assuming the Separator is '/'). | |
| // | |
| // Glob ignores file system errors such as I/O errors reading directories. | |
| // The only possible returned error is path.ErrBadPattern, reporting that | |
| // the pattern is malformed. | |
| // | |
| // If fs implements GlobFS, Glob calls fs.Glob. | |
| // Otherwise, Glob uses ReadDir to traverse the directory tree | |
| // and look for matches for the pattern. | |
| func Glob(fsys FS, pattern string) (matches []string, err error) { | |
| if fsys, ok := fsys.(GlobFS); ok { | |
| return fsys.Glob(pattern) | |
| } | |
| // Check pattern is well-formed. | |
| if _, err := path.Match(pattern, ""); err != nil { | |
| return nil, err | |
| } | |
| if !hasMeta(pattern) { | |
| if _, err = Stat(fsys, pattern); err != nil { | |
| return nil, nil | |
| } | |
| return []string{pattern}, nil | |
| } | |
| dir, file := path.Split(pattern) | |
| dir = cleanGlobPath(dir) | |
| if !hasMeta(dir) { | |
| return glob(fsys, dir, file, nil) | |
| } | |
| // Prevent infinite recursion. See issue 15879. | |
| if dir == pattern { | |
| return nil, path.ErrBadPattern | |
| } | |
| var m []string | |
| m, err = Glob(fsys, dir) | |
| if err != nil { | |
| return | |
| } | |
| for _, d := range m { | |
| matches, err = glob(fsys, d, file, matches) | |
| if err != nil { | |
| return | |
| } | |
| } | |
| return | |
| } | |
| // cleanGlobPath prepares path for glob matching. | |
| func cleanGlobPath(path string) string { | |
| switch path { | |
| case "": | |
| return "." | |
| default: | |
| return path[0 : len(path)-1] // chop off trailing separator | |
| } | |
| } | |
| // glob searches for files matching pattern in the directory dir | |
| // and appends them to matches, returning the updated slice. | |
| // If the directory cannot be opened, glob returns the existing matches. | |
| // New matches are added in lexicographical order. | |
| func glob(fs FS, dir, pattern string, matches []string) (m []string, e error) { | |
| m = matches | |
| infos, err := ReadDir(fs, dir) | |
| if err != nil { | |
| return // ignore I/O error | |
| } | |
| for _, info := range infos { | |
| n := info.Name() | |
| matched, err := path.Match(pattern, n) | |
| if err != nil { | |
| return m, err | |
| } | |
| if matched { | |
| m = append(m, path.Join(dir, n)) | |
| } | |
| } | |
| return | |
| } | |
| // hasMeta reports whether path contains any of the magic characters | |
| // recognized by path.Match. | |
| func hasMeta(path string) bool { | |
| for i := 0; i < len(path); i++ { | |
| c := path[i] | |
| if c == '*' || c == '?' || c == '[' || runtime.GOOS == "windows" && c == '\\' { | |
| return true | |
| } | |
| } | |
| return false | |
| } |