diff --git a/backend/walker.go b/backend/walker.go new file mode 100644 index 00000000..0b2fbcd2 --- /dev/null +++ b/backend/walker.go @@ -0,0 +1,119 @@ +// 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 backend + +import ( + "io/fs" + "path" +) + +// WalkDirFunc is the type of the function called by [WalkDir] to visit +// each file or directory. +// +// The path argument contains the argument to [WalkDir] as a prefix. +// That is, if WalkDir is called with root argument "dir" and finds a file +// named "a" in that directory, the walk function will be called with +// argument "dir/a". +// +// The d argument is the [DirEntry] for the named path. +// +// The error result returned by the function controls how [WalkDir] +// continues. If the function returns the special value [SkipDir], WalkDir +// skips the current directory (path if d.IsDir() is true, otherwise +// path's parent directory). If the function returns the special value +// [SkipAll], WalkDir skips all remaining files and directories. Otherwise, +// if the function returns a non-nil error, WalkDir stops entirely and +// returns that error. +// +// The err argument reports an error related to path, signaling that +// [WalkDir] will not walk into that directory. The function can decide how +// to handle that error; as described earlier, returning the error will +// cause WalkDir to stop walking the entire tree. +// +// [WalkDir] calls the function with a non-nil err argument in two cases. +// +// First, if the initial [Stat] on the root directory fails, WalkDir +// calls the function with path set to root, d set to nil, and err set to +// the error from [fs.Stat]. +// +// Second, if a directory's ReadDir method (see [ReadDirFile]) fails, WalkDir calls the +// function with path set to the directory's path, d set to an +// [DirEntry] describing the directory, and err set to the error from +// ReadDir. In this second case, the function is called twice with the +// path of the directory: the first call is before the directory read is +// attempted and has err set to nil, giving the function a chance to +// return [SkipDir] or [SkipAll] and avoid the ReadDir entirely. The second call +// is after a failed ReadDir and reports the error from ReadDir. +// (If ReadDir succeeds, there is no second call.) +// +// The differences between WalkDirFunc compared to [path/filepath.WalkFunc] are: +// +// - The second argument has type [DirEntry] instead of [FileInfo]. +// - The function is called before reading a directory, to allow [SkipDir] +// or [SkipAll] to bypass the directory read entirely or skip all remaining +// files and directories respectively. +// - If a directory read fails, the function is called a second time +// for that directory to report the error. +type WalkDirFunc func(path string, d fs.DirEntry, err error) error + +// walkDir recursively descends path, calling walkDirFn. +func walkDir(fsys fs.FS, name string, d fs.DirEntry, walkDirFn WalkDirFunc) error { + if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() { + if err == fs.SkipDir && d.IsDir() { + // Successfully skipped directory. + err = nil + } + // return errors.New("hi " + name) + return err + } + + dirs, err := fs.ReadDir(fsys, name) + if err != nil { + // Second call, to report ReadDir error. + err = walkDirFn(name, d, err) + if err != nil { + if err == fs.SkipDir && d.IsDir() { + err = nil + } + return err + } + } + + for _, d1 := range dirs { + name1 := path.Join(name, d1.Name()) + if err := walkDir(fsys, name1, d1, walkDirFn); err != nil { + if err == fs.SkipDir { + break + } + return err + } + } + return nil +} + +// WalkDir walks the file tree rooted at root, calling fn for each file or +// directory in the tree, including root. +// +// All errors that arise visiting files and directories are filtered by fn: +// see the [fs.WalkDirFunc] documentation for details. +// +// The files are walked in lexical order, which makes the output deterministic +// but requires WalkDir to read an entire directory into memory before proceeding +// to walk that directory. +// +// WalkDir does not follow symbolic links found in directories, +// but if root itself is a symbolic link, its target will be walked. +func WalkDir(fsys fs.FS, root string, fn WalkDirFunc) error { + info, err := fs.Stat(fsys, root) + if err != nil { + err = fn(root, nil, err) + } else { + err = walkDir(fsys, root, fs.FileInfoToDirEntry(info), fn) + } + if err == fs.SkipDir || err == fs.SkipAll { + return nil + } + return err +}