mirror of
https://github.com/golang/go.git
synced 2024-09-29 22:37:06 +00:00
path/filepath: new signature for Walk
This one uses a closure than an interface, and is much simpler to use. It also enables a called function to return an error and (possibly) halt processing. Fixes #2237. R=golang-dev, gri, rsc, r, cw, n13m3y3r CC=golang-dev https://golang.org/cl/5014043
This commit is contained in:
parent
4c6454aecf
commit
4e3b725cf0
@ -198,31 +198,17 @@ func report(err os.Error) {
|
||||
}
|
||||
|
||||
func walkDir(path string) {
|
||||
v := make(fileVisitor)
|
||||
go func() {
|
||||
filepath.Walk(path, v, v)
|
||||
close(v)
|
||||
}()
|
||||
for err := range v {
|
||||
if err != nil {
|
||||
report(err)
|
||||
}
|
||||
}
|
||||
filepath.Walk(path, visitFile)
|
||||
}
|
||||
|
||||
type fileVisitor chan os.Error
|
||||
|
||||
func (v fileVisitor) VisitDir(path string, f *os.FileInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v fileVisitor) VisitFile(path string, f *os.FileInfo) {
|
||||
if isGoFile(f) {
|
||||
v <- nil // synchronize error handler
|
||||
if err := processFile(path, false); err != nil {
|
||||
v <- err
|
||||
}
|
||||
func visitFile(path string, f *os.FileInfo, err os.Error) os.Error {
|
||||
if err == nil && isGoFile(f) {
|
||||
err = processFile(path, false)
|
||||
}
|
||||
if err != nil {
|
||||
report(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isGoFile(f *os.FileInfo) bool {
|
||||
|
@ -149,32 +149,18 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) os.Er
|
||||
return err
|
||||
}
|
||||
|
||||
type fileVisitor chan os.Error
|
||||
|
||||
func (v fileVisitor) VisitDir(path string, f *os.FileInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v fileVisitor) VisitFile(path string, f *os.FileInfo) {
|
||||
if isGoFile(f) {
|
||||
v <- nil // synchronize error handler
|
||||
if err := processFile(path, nil, os.Stdout, false); err != nil {
|
||||
v <- err
|
||||
}
|
||||
func visitFile(path string, f *os.FileInfo, err os.Error) os.Error {
|
||||
if err == nil && isGoFile(f) {
|
||||
err = processFile(path, nil, os.Stdout, false)
|
||||
}
|
||||
if err != nil {
|
||||
report(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func walkDir(path string) {
|
||||
v := make(fileVisitor)
|
||||
go func() {
|
||||
filepath.Walk(path, v, v)
|
||||
close(v)
|
||||
}()
|
||||
for err := range v {
|
||||
if err != nil {
|
||||
report(err)
|
||||
}
|
||||
}
|
||||
filepath.Walk(path, visitFile)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -101,34 +101,20 @@ func doFile(name string, reader io.Reader) {
|
||||
file.checkFile(name, parsedFile)
|
||||
}
|
||||
|
||||
// Visitor for filepath.Walk - trivial. Just calls doFile on each file.
|
||||
// TODO: if govet becomes richer, might want to process
|
||||
// a directory (package) at a time.
|
||||
type V struct{}
|
||||
|
||||
func (v V) VisitDir(path string, f *os.FileInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v V) VisitFile(path string, f *os.FileInfo) {
|
||||
if strings.HasSuffix(path, ".go") {
|
||||
func visit(path string, f *os.FileInfo, err os.Error) os.Error {
|
||||
if err != nil {
|
||||
errorf("walk error: %s", err)
|
||||
return nil
|
||||
}
|
||||
if f.IsRegular() && strings.HasSuffix(path, ".go") {
|
||||
doFile(path, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// walkDir recursively walks the tree looking for .go files.
|
||||
func walkDir(root string) {
|
||||
errors := make(chan os.Error)
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for e := range errors {
|
||||
errorf("walk error: %s", e)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
filepath.Walk(root, V{}, errors)
|
||||
close(errors)
|
||||
<-done
|
||||
filepath.Walk(root, visit)
|
||||
}
|
||||
|
||||
// error formats the error to standard error, adding program
|
||||
|
@ -258,37 +258,61 @@ func Abs(path string) (string, os.Error) {
|
||||
return Join(wd, path), nil
|
||||
}
|
||||
|
||||
// Visitor methods are invoked for corresponding file tree entries
|
||||
// visited by Walk. The provided path parameter begins with root.
|
||||
type Visitor interface {
|
||||
VisitDir(path string, f *os.FileInfo) bool
|
||||
VisitFile(path string, f *os.FileInfo)
|
||||
}
|
||||
// SkipDir is used as a return value from WalkFuncs to indicate that
|
||||
// the directory named in the call is to be skipped. It is not returned
|
||||
// as an error by any function.
|
||||
var SkipDir = os.NewError("skip this directory")
|
||||
|
||||
func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) {
|
||||
if !f.IsDirectory() {
|
||||
v.VisitFile(path, f)
|
||||
return
|
||||
// WalkFunc is the type of the function called for each file or directory
|
||||
// visited by Walk. If there was a problem walking to the file or directory
|
||||
// named by path, the incoming error will describe the problem and the
|
||||
// function can decide how to handle that error (and Walk will not descend
|
||||
// into that directory). If an error is returned, processing stops. The
|
||||
// sole exception is that if path is a directory and the function returns the
|
||||
// special value SkipDir, the contents of the directory are skipped
|
||||
// and processing continues as usual on the next file.
|
||||
type WalkFunc func(path string, info *os.FileInfo, err os.Error) os.Error
|
||||
|
||||
// walk recursively descends path, calling w.
|
||||
func walk(path string, info *os.FileInfo, walkFn WalkFunc) os.Error {
|
||||
err := walkFn(path, info, nil)
|
||||
if err != nil {
|
||||
if info.IsDirectory() && err == SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !v.VisitDir(path, f) {
|
||||
return // skip directory entries
|
||||
if !info.IsDirectory() {
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := readDir(path)
|
||||
if err != nil {
|
||||
if errors != nil {
|
||||
errors <- err
|
||||
}
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
|
||||
for _, e := range list {
|
||||
walk(Join(path, e.Name), e, v, errors)
|
||||
for _, fileInfo := range list {
|
||||
if err = walk(Join(path, fileInfo.Name), fileInfo, walkFn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk walks the file tree rooted at root, calling walkFn for each file or
|
||||
// directory in the tree, including root. All errors that arise visiting files
|
||||
// and directories are filtered by walkFn.
|
||||
func Walk(root string, walkFn WalkFunc) os.Error {
|
||||
info, err := os.Lstat(root)
|
||||
if err != nil {
|
||||
return walkFn(root, nil, err)
|
||||
}
|
||||
return walk(root, info, walkFn)
|
||||
}
|
||||
|
||||
// readDir reads the directory named by dirname and returns
|
||||
// a list of sorted directory entries.
|
||||
// a sorted list of directory entries.
|
||||
// Copied from io/ioutil to avoid the circular import.
|
||||
func readDir(dirname string) ([]*os.FileInfo, os.Error) {
|
||||
f, err := os.Open(dirname)
|
||||
@ -315,24 +339,6 @@ func (f fileInfoList) Len() int { return len(f) }
|
||||
func (f fileInfoList) Less(i, j int) bool { return f[i].Name < f[j].Name }
|
||||
func (f fileInfoList) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
|
||||
// Walk walks the file tree rooted at root, calling v.VisitDir or
|
||||
// v.VisitFile for each directory or file in the tree, including root.
|
||||
// If v.VisitDir returns false, Walk skips the directory's entries;
|
||||
// otherwise it invokes itself for each directory entry in sorted order.
|
||||
// An error reading a directory does not abort the Walk.
|
||||
// If errors != nil, Walk sends each directory read error
|
||||
// to the channel. Otherwise Walk discards the error.
|
||||
func Walk(root string, v Visitor, errors chan<- os.Error) {
|
||||
f, err := os.Lstat(root)
|
||||
if err != nil {
|
||||
if errors != nil {
|
||||
errors <- err
|
||||
}
|
||||
return // can't progress
|
||||
}
|
||||
walk(root, f, v, errors)
|
||||
}
|
||||
|
||||
// Base returns the last element of path.
|
||||
// Trailing path separators are removed before extracting the last element.
|
||||
// If the path is empty, Base returns ".".
|
||||
|
@ -306,9 +306,9 @@ func makeTree(t *testing.T) {
|
||||
|
||||
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
|
||||
|
||||
func checkMarks(t *testing.T) {
|
||||
func checkMarks(t *testing.T, report bool) {
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.mark != 1 {
|
||||
if n.mark != 1 && report {
|
||||
t.Errorf("node %s mark = %d; expected 1", path, n.mark)
|
||||
}
|
||||
n.mark = 0
|
||||
@ -316,44 +316,41 @@ func checkMarks(t *testing.T) {
|
||||
}
|
||||
|
||||
// Assumes that each node name is unique. Good enough for a test.
|
||||
func mark(name string) {
|
||||
name = filepath.ToSlash(name)
|
||||
// If clear is true, any incoming error is cleared before return. The errors
|
||||
// are always accumulated, though.
|
||||
func mark(path string, info *os.FileInfo, err os.Error, errors *[]os.Error, clear bool) os.Error {
|
||||
if err != nil {
|
||||
*errors = append(*errors, err)
|
||||
if clear {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.name == name {
|
||||
if n.name == info.Name {
|
||||
n.mark++
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type TestVisitor struct{}
|
||||
|
||||
func (v *TestVisitor) VisitDir(path string, f *os.FileInfo) bool {
|
||||
mark(f.Name)
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *TestVisitor) VisitFile(path string, f *os.FileInfo) {
|
||||
mark(f.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
makeTree(t)
|
||||
|
||||
// 1) ignore error handling, expect none
|
||||
v := &TestVisitor{}
|
||||
filepath.Walk(tree.name, v, nil)
|
||||
checkMarks(t)
|
||||
|
||||
// 2) handle errors, expect none
|
||||
errors := make(chan os.Error, 64)
|
||||
filepath.Walk(tree.name, v, errors)
|
||||
select {
|
||||
case err := <-errors:
|
||||
t.Errorf("no error expected, found: %s", err)
|
||||
default:
|
||||
// ok
|
||||
errors := make([]os.Error, 0, 10)
|
||||
clear := true
|
||||
markFn := func(path string, info *os.FileInfo, err os.Error) os.Error {
|
||||
return mark(path, info, err, &errors, clear)
|
||||
}
|
||||
checkMarks(t)
|
||||
// Expect no errors.
|
||||
err := filepath.Walk(tree.name, markFn)
|
||||
if err != nil {
|
||||
t.Errorf("no error expected, found: %s", err)
|
||||
}
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("unexpected errors: %s", errors)
|
||||
}
|
||||
checkMarks(t, true)
|
||||
errors = errors[0:0]
|
||||
|
||||
// Test permission errors. Only possible if we're not root
|
||||
// and only on some file systems (AFS, FAT). To avoid errors during
|
||||
@ -362,40 +359,50 @@ func TestWalk(t *testing.T) {
|
||||
// introduce 2 errors: chmod top-level directories to 0
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
|
||||
|
||||
// 3) capture errors, expect two.
|
||||
// mark respective subtrees manually
|
||||
markTree(tree.entries[1])
|
||||
markTree(tree.entries[3])
|
||||
// correct double-marking of directory itself
|
||||
tree.entries[1].mark--
|
||||
tree.entries[3].mark--
|
||||
|
||||
// 3) handle errors, expect two
|
||||
errors = make(chan os.Error, 64)
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
|
||||
filepath.Walk(tree.name, v, errors)
|
||||
Loop:
|
||||
for i := 1; i <= 2; i++ {
|
||||
select {
|
||||
case <-errors:
|
||||
// ok
|
||||
default:
|
||||
t.Errorf("%d. error expected, none found", i)
|
||||
break Loop
|
||||
}
|
||||
err := filepath.Walk(tree.name, markFn)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error return from Walk, %s", err)
|
||||
}
|
||||
select {
|
||||
case err := <-errors:
|
||||
t.Errorf("only two errors expected, found 3rd: %v", err)
|
||||
default:
|
||||
// ok
|
||||
if len(errors) != 2 {
|
||||
t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
|
||||
}
|
||||
// the inaccessible subtrees were marked manually
|
||||
checkMarks(t)
|
||||
checkMarks(t, true)
|
||||
errors = errors[0:0]
|
||||
|
||||
// 4) capture errors, stop after first error.
|
||||
// mark respective subtrees manually
|
||||
markTree(tree.entries[1])
|
||||
markTree(tree.entries[3])
|
||||
// correct double-marking of directory itself
|
||||
tree.entries[1].mark--
|
||||
tree.entries[3].mark--
|
||||
clear = false // error will stop processing
|
||||
err = filepath.Walk(tree.name, markFn)
|
||||
if err == nil {
|
||||
t.Errorf("expected error return from Walk")
|
||||
}
|
||||
if len(errors) != 1 {
|
||||
t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
|
||||
}
|
||||
// the inaccessible subtrees were marked manually
|
||||
checkMarks(t, false)
|
||||
errors = errors[0:0]
|
||||
|
||||
// restore permissions
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
|
||||
}
|
||||
|
||||
// cleanup
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
|
||||
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
|
||||
if err := os.RemoveAll(tree.name); err != nil {
|
||||
t.Errorf("removeTree: %v", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user