| // 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 ( |
| "golang.org/x/website/internal/backport/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 |
| } |