walk.go

  1package vfsutil
  2
  3import (
  4	"io"
  5	"net/http"
  6	"os"
  7	pathpkg "path"
  8	"path/filepath"
  9	"sort"
 10)
 11
 12// Walk walks the filesystem rooted at root, calling walkFn for each file or
 13// directory in the filesystem, including root. All errors that arise visiting files
 14// and directories are filtered by walkFn. The files are walked in lexical
 15// order.
 16func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
 17	info, err := Stat(fs, root)
 18	if err != nil {
 19		return walkFn(root, nil, err)
 20	}
 21	return walk(fs, root, info, walkFn)
 22}
 23
 24// readDirNames reads the directory named by dirname and returns
 25// a sorted list of directory entries.
 26func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
 27	fis, err := ReadDir(fs, dirname)
 28	if err != nil {
 29		return nil, err
 30	}
 31	names := make([]string, len(fis))
 32	for i := range fis {
 33		names[i] = fis[i].Name()
 34	}
 35	sort.Strings(names)
 36	return names, nil
 37}
 38
 39// walk recursively descends path, calling walkFn.
 40func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
 41	err := walkFn(path, info, nil)
 42	if err != nil {
 43		if info.IsDir() && err == filepath.SkipDir {
 44			return nil
 45		}
 46		return err
 47	}
 48
 49	if !info.IsDir() {
 50		return nil
 51	}
 52
 53	names, err := readDirNames(fs, path)
 54	if err != nil {
 55		return walkFn(path, info, err)
 56	}
 57
 58	for _, name := range names {
 59		filename := pathpkg.Join(path, name)
 60		fileInfo, err := Stat(fs, filename)
 61		if err != nil {
 62			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
 63				return err
 64			}
 65		} else {
 66			err = walk(fs, filename, fileInfo, walkFn)
 67			if err != nil {
 68				if !fileInfo.IsDir() || err != filepath.SkipDir {
 69					return err
 70				}
 71			}
 72		}
 73	}
 74	return nil
 75}
 76
 77// WalkFilesFunc is the type of the function called for each file or directory visited by WalkFiles.
 78// It's like filepath.WalkFunc, except it provides an additional ReadSeeker parameter for file being visited.
 79type WalkFilesFunc func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error
 80
 81// WalkFiles walks the filesystem rooted at root, calling walkFn for each file or
 82// directory in the filesystem, including root. In addition to FileInfo, it passes an
 83// ReadSeeker to walkFn for each file it visits.
 84func WalkFiles(fs http.FileSystem, root string, walkFn WalkFilesFunc) error {
 85	file, info, err := openStat(fs, root)
 86	if err != nil {
 87		return walkFn(root, nil, nil, err)
 88	}
 89	return walkFiles(fs, root, info, file, walkFn)
 90}
 91
 92// walkFiles recursively descends path, calling walkFn.
 93// It closes the input file after it's done with it, so the caller shouldn't.
 94func walkFiles(fs http.FileSystem, path string, info os.FileInfo, file http.File, walkFn WalkFilesFunc) error {
 95	err := walkFn(path, info, file, nil)
 96	file.Close()
 97	if err != nil {
 98		if info.IsDir() && err == filepath.SkipDir {
 99			return nil
100		}
101		return err
102	}
103
104	if !info.IsDir() {
105		return nil
106	}
107
108	names, err := readDirNames(fs, path)
109	if err != nil {
110		return walkFn(path, info, nil, err)
111	}
112
113	for _, name := range names {
114		filename := pathpkg.Join(path, name)
115		file, fileInfo, err := openStat(fs, filename)
116		if err != nil {
117			if err := walkFn(filename, nil, nil, err); err != nil && err != filepath.SkipDir {
118				return err
119			}
120		} else {
121			err = walkFiles(fs, filename, fileInfo, file, walkFn)
122			// file is closed by walkFiles, so we don't need to close it here.
123			if err != nil {
124				if !fileInfo.IsDir() || err != filepath.SkipDir {
125					return err
126				}
127			}
128		}
129	}
130	return nil
131}
132
133// openStat performs Open and Stat and returns results, or first error encountered.
134// The caller is responsible for closing the returned file when done.
135func openStat(fs http.FileSystem, name string) (http.File, os.FileInfo, error) {
136	f, err := fs.Open(name)
137	if err != nil {
138		return nil, nil, err
139	}
140	fi, err := f.Stat()
141	if err != nil {
142		f.Close()
143		return nil, nil, err
144	}
145	return f, fi, nil
146}