diff --git a/fs.go b/fs.go index 593ca23..b1f1283 100644 --- a/fs.go +++ b/fs.go @@ -26,6 +26,7 @@ import ( "errors" "io" "os" + "path/filepath" "sort" "time" ) @@ -119,6 +120,87 @@ func ReadDir(dirname string, fs Fs) ([]os.FileInfo, error) { return list, nil } +// readDirNames reads the directory named by dirname and returns +// a sorted list of directory entries. +// adapted from https://golang.org/src/path/filepath/path.go +func readDirNames(dirname string, fs Fs) ([]string, error) { + f, err := fs.Open(dirname) + if err != nil { + return nil, err + } + names, err := f.Readdirnames(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +// walk recursively descends path, calling walkFn +// adapted from https://golang.org/src/path/filepath/path.go +func walk(path string, info os.FileInfo, walkFn filepath.WalkFunc, fs Fs) error { + err := walkFn(path, info, nil) + if err != nil { + if info.IsDir() && err == filepath.SkipDir { + return nil + } + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(path, fs) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := lstatIfOs(filename, fs) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = walk(filename, fileInfo, walkFn, fs) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// if the filesystem is OsFs use Lstat, else use fs.Stat +func lstatIfOs(path string, fs Fs) (info os.FileInfo, err error) { + _, ok := fs.(*OsFs) + if ok { + info, err = os.Lstat(path) + } else { + info, err = fs.Stat(path) + } + return +} + +// 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. The files are walked in lexical +// order, which makes the output deterministic but means that for very +// large directories Walk can be inefficient. +// Walk does not follow symbolic links. +func Walk(root string, walkFn filepath.WalkFunc, fs Fs) error { + info, err := lstatIfOs(root, fs) + if err != nil { + return walkFn(root, nil, err) + } + return walk(root, info, walkFn, fs) +} + // byName implements sort.Interface. type byName []os.FileInfo diff --git a/fs_test.go b/fs_test.go index 92cbf6f..110b3f9 100644 --- a/fs_test.go +++ b/fs_test.go @@ -28,14 +28,6 @@ import ( "testing" ) -var dot = []string{ - "fs.go", - "fs_test.go", - "httpFs.go", - "memfile.go", - "memmap.go", -} - var testDir = "/tmp/afero" var testSubDir = "/tmp/afero/we/have/to/go/deeper" var testName = "test.txt" @@ -406,6 +398,42 @@ func (m myFileInfo) String() string { return out } +func TestWalk(t *testing.T) { + outputs := make([]string, len(Fss)) + for i, fs := range Fss { + walkFn := func(path string, info os.FileInfo, err error) error { + var size int64 + if !info.IsDir() { + size = info.Size() + } + outputs[i] += fmt.Sprintln(path, info.Name(), size, info.IsDir(), err) + return nil + } + err := Walk(testDir, walkFn, fs) + if err != nil { + t.Error(err) + } + } + fail := false + for i, o := range outputs { + if i == 0 { + continue + } + if o != outputs[i-1] { + fail = true + break + } + } + if fail { + t.Log("Walk outputs not equal!") + for i, o := range outputs { + t.Log(Fss[i].Name()) + t.Log(o) + } + t.Fail() + } +} + func TestReaddirAll(t *testing.T) { defer removeTestDir(t) for _, fs := range Fss {