|  | // Copyright 2020 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" | 
|  | ) | 
|  |  | 
|  | // 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. | 
|  | // | 
|  | // 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++ { | 
|  | switch path[i] { | 
|  | case '*', '?', '[', '\\': | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } |