Commit 4e3b725c authored by Rob Pike's avatar Rob Pike

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
parent 4c6454ae
...@@ -198,31 +198,17 @@ func report(err os.Error) { ...@@ -198,31 +198,17 @@ func report(err os.Error) {
} }
func walkDir(path string) { func walkDir(path string) {
v := make(fileVisitor) filepath.Walk(path, visitFile)
go func() {
filepath.Walk(path, v, v)
close(v)
}()
for err := range v {
if err != nil {
report(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) { func visitFile(path string, f *os.FileInfo, err os.Error) os.Error {
if isGoFile(f) { if err == nil && isGoFile(f) {
v <- nil // synchronize error handler err = processFile(path, false)
if err := processFile(path, false); err != nil { }
v <- err if err != nil {
} report(err)
} }
return nil
} }
func isGoFile(f *os.FileInfo) bool { func isGoFile(f *os.FileInfo) bool {
......
...@@ -149,32 +149,18 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) os.Er ...@@ -149,32 +149,18 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) os.Er
return err return err
} }
type fileVisitor chan os.Error func visitFile(path string, f *os.FileInfo, err os.Error) os.Error {
if err == nil && isGoFile(f) {
func (v fileVisitor) VisitDir(path string, f *os.FileInfo) bool { err = processFile(path, nil, os.Stdout, false)
return true }
} if err != nil {
report(err)
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
}
} }
return nil
} }
func walkDir(path string) { func walkDir(path string) {
v := make(fileVisitor) filepath.Walk(path, visitFile)
go func() {
filepath.Walk(path, v, v)
close(v)
}()
for err := range v {
if err != nil {
report(err)
}
}
} }
func main() { func main() {
......
...@@ -101,34 +101,20 @@ func doFile(name string, reader io.Reader) { ...@@ -101,34 +101,20 @@ func doFile(name string, reader io.Reader) {
file.checkFile(name, parsedFile) file.checkFile(name, parsedFile)
} }
// Visitor for filepath.Walk - trivial. Just calls doFile on each file. func visit(path string, f *os.FileInfo, err os.Error) os.Error {
// TODO: if govet becomes richer, might want to process if err != nil {
// a directory (package) at a time. errorf("walk error: %s", err)
type V struct{} return nil
}
func (v V) VisitDir(path string, f *os.FileInfo) bool { if f.IsRegular() && strings.HasSuffix(path, ".go") {
return true
}
func (v V) VisitFile(path string, f *os.FileInfo) {
if strings.HasSuffix(path, ".go") {
doFile(path, nil) doFile(path, nil)
} }
return nil
} }
// walkDir recursively walks the tree looking for .go files. // walkDir recursively walks the tree looking for .go files.
func walkDir(root string) { func walkDir(root string) {
errors := make(chan os.Error) filepath.Walk(root, visit)
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
} }
// error formats the error to standard error, adding program // error formats the error to standard error, adding program
......
...@@ -258,37 +258,61 @@ func Abs(path string) (string, os.Error) { ...@@ -258,37 +258,61 @@ func Abs(path string) (string, os.Error) {
return Join(wd, path), nil return Join(wd, path), nil
} }
// Visitor methods are invoked for corresponding file tree entries // SkipDir is used as a return value from WalkFuncs to indicate that
// visited by Walk. The provided path parameter begins with root. // the directory named in the call is to be skipped. It is not returned
type Visitor interface { // as an error by any function.
VisitDir(path string, f *os.FileInfo) bool var SkipDir = os.NewError("skip this directory")
VisitFile(path string, f *os.FileInfo)
} // 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
func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) { // walk recursively descends path, calling w.
if !f.IsDirectory() { func walk(path string, info *os.FileInfo, walkFn WalkFunc) os.Error {
v.VisitFile(path, f) err := walkFn(path, info, nil)
return if err != nil {
if info.IsDirectory() && err == SkipDir {
return nil
}
return err
} }
if !v.VisitDir(path, f) { if !info.IsDirectory() {
return // skip directory entries return nil
} }
list, err := readDir(path) list, err := readDir(path)
if err != nil { if err != nil {
if errors != nil { return walkFn(path, info, err)
errors <- err }
for _, fileInfo := range list {
if err = walk(Join(path, fileInfo.Name), fileInfo, walkFn); err != nil {
return err
} }
} }
return nil
}
for _, e := range list { // Walk walks the file tree rooted at root, calling walkFn for each file or
walk(Join(path, e.Name), e, v, errors) // 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 // 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. // Copied from io/ioutil to avoid the circular import.
func readDir(dirname string) ([]*os.FileInfo, os.Error) { func readDir(dirname string) ([]*os.FileInfo, os.Error) {
f, err := os.Open(dirname) f, err := os.Open(dirname)
...@@ -315,24 +339,6 @@ func (f fileInfoList) Len() int { return len(f) } ...@@ -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) 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] } 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. // Base returns the last element of path.
// Trailing path separators are removed before extracting the last element. // Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".". // If the path is empty, Base returns ".".
......
...@@ -306,9 +306,9 @@ func makeTree(t *testing.T) { ...@@ -306,9 +306,9 @@ func makeTree(t *testing.T) {
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } 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) { 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) t.Errorf("node %s mark = %d; expected 1", path, n.mark)
} }
n.mark = 0 n.mark = 0
...@@ -316,44 +316,41 @@ func checkMarks(t *testing.T) { ...@@ -316,44 +316,41 @@ func checkMarks(t *testing.T) {
} }
// Assumes that each node name is unique. Good enough for a test. // Assumes that each node name is unique. Good enough for a test.
func mark(name string) { // If clear is true, any incoming error is cleared before return. The errors
name = filepath.ToSlash(name) // 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) { walkTree(tree, tree.name, func(path string, n *Node) {
if n.name == name { if n.name == info.Name {
n.mark++ n.mark++
} }
}) })
} return nil
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)
} }
func TestWalk(t *testing.T) { func TestWalk(t *testing.T) {
makeTree(t) makeTree(t)
errors := make([]os.Error, 0, 10)
// 1) ignore error handling, expect none clear := true
v := &TestVisitor{} markFn := func(path string, info *os.FileInfo, err os.Error) os.Error {
filepath.Walk(tree.name, v, nil) return mark(path, info, err, &errors, clear)
checkMarks(t) }
// Expect no errors.
// 2) handle errors, expect none err := filepath.Walk(tree.name, markFn)
errors := make(chan os.Error, 64) if err != nil {
filepath.Walk(tree.name, v, errors)
select {
case err := <-errors:
t.Errorf("no error expected, found: %s", err) t.Errorf("no error expected, found: %s", err)
default:
// ok
} }
checkMarks(t) 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 // Test permission errors. Only possible if we're not root
// and only on some file systems (AFS, FAT). To avoid errors during // and only on some file systems (AFS, FAT). To avoid errors during
...@@ -362,40 +359,50 @@ func TestWalk(t *testing.T) { ...@@ -362,40 +359,50 @@ func TestWalk(t *testing.T) {
// introduce 2 errors: chmod top-level directories to 0 // 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[1].name), 0)
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
// 3) capture errors, expect two.
// mark respective subtrees manually // mark respective subtrees manually
markTree(tree.entries[1]) markTree(tree.entries[1])
markTree(tree.entries[3]) markTree(tree.entries[3])
// correct double-marking of directory itself // correct double-marking of directory itself
tree.entries[1].mark-- tree.entries[1].mark--
tree.entries[3].mark-- tree.entries[3].mark--
err := filepath.Walk(tree.name, markFn)
if err != nil {
t.Errorf("expected no error return from Walk, %s", err)
}
if len(errors) != 2 {
t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
}
// the inaccessible subtrees were marked manually
checkMarks(t, true)
errors = errors[0:0]
// 3) handle errors, expect two // 4) capture errors, stop after first error.
errors = make(chan os.Error, 64) // mark respective subtrees manually
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) markTree(tree.entries[1])
filepath.Walk(tree.name, v, errors) markTree(tree.entries[3])
Loop: // correct double-marking of directory itself
for i := 1; i <= 2; i++ { tree.entries[1].mark--
select { tree.entries[3].mark--
case <-errors: clear = false // error will stop processing
// ok err = filepath.Walk(tree.name, markFn)
default: if err == nil {
t.Errorf("%d. error expected, none found", i) t.Errorf("expected error return from Walk")
break Loop
}
} }
select { if len(errors) != 1 {
case err := <-errors: t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
t.Errorf("only two errors expected, found 3rd: %v", err)
default:
// ok
} }
// the inaccessible subtrees were marked manually // the inaccessible subtrees were marked manually
checkMarks(t) 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 // 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 { if err := os.RemoveAll(tree.name); err != nil {
t.Errorf("removeTree: %v", err) t.Errorf("removeTree: %v", err)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment