-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Glob helper function, GlobFS interface, and test. Add Glob method to fstest.MapFS. Add testing of Glob method to fstest.TestFS. For golang#41190. Change-Id: If89dd7f63e310ba5ca2651340267a9ff39fcc0c7 Reviewed-on: https://go-review.googlesource.com/c/go/+/243915 Trust: Russ Cox <[email protected]> Reviewed-by: Rob Pike <[email protected]>
- Loading branch information
Showing
5 changed files
with
299 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// 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) | ||
} | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright 2009 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_test | ||
|
||
import ( | ||
. "io/fs" | ||
"os" | ||
"testing" | ||
) | ||
|
||
var globTests = []struct { | ||
fs FS | ||
pattern, result string | ||
}{ | ||
{os.DirFS("."), "glob.go", "glob.go"}, | ||
{os.DirFS("."), "gl?b.go", "glob.go"}, | ||
{os.DirFS("."), "*", "glob.go"}, | ||
{os.DirFS(".."), "*/glob.go", "fs/glob.go"}, | ||
} | ||
|
||
func TestGlob(t *testing.T) { | ||
for _, tt := range globTests { | ||
matches, err := Glob(tt.fs, tt.pattern) | ||
if err != nil { | ||
t.Errorf("Glob error for %q: %s", tt.pattern, err) | ||
continue | ||
} | ||
if !contains(matches, tt.result) { | ||
t.Errorf("Glob(%#q) = %#v want %v", tt.pattern, matches, tt.result) | ||
} | ||
} | ||
for _, pattern := range []string{"no_match", "../*/no_match"} { | ||
matches, err := Glob(os.DirFS("."), pattern) | ||
if err != nil { | ||
t.Errorf("Glob error for %q: %s", pattern, err) | ||
continue | ||
} | ||
if len(matches) != 0 { | ||
t.Errorf("Glob(%#q) = %#v want []", pattern, matches) | ||
} | ||
} | ||
} | ||
|
||
func TestGlobError(t *testing.T) { | ||
_, err := Glob(os.DirFS("."), "[]") | ||
if err == nil { | ||
t.Error("expected error for bad pattern; got none") | ||
} | ||
} | ||
|
||
// contains reports whether vector contains the string s. | ||
func contains(vector []string, s string) bool { | ||
for _, elem := range vector { | ||
if elem == s { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
type globOnly struct{ GlobFS } | ||
|
||
func (globOnly) Open(name string) (File, error) { return nil, ErrNotExist } | ||
|
||
func TestGlobMethod(t *testing.T) { | ||
check := func(desc string, names []string, err error) { | ||
t.Helper() | ||
if err != nil || len(names) != 1 || names[0] != "hello.txt" { | ||
t.Errorf("Glob(%s) = %v, %v, want %v, nil", desc, names, err, []string{"hello.txt"}) | ||
} | ||
} | ||
|
||
// Test that ReadDir uses the method when present. | ||
names, err := Glob(globOnly{testFsys}, "*.txt") | ||
check("readDirOnly", names, err) | ||
|
||
// Test that ReadDir uses Open when the method is not present. | ||
names, err = Glob(openOnly{testFsys}, "*.txt") | ||
check("openOnly", names, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters