github.com/3JoB/vfs@v1.0.0/walk.go (about)

     1  package vfs
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"sort"
     7  	"strings"
     8  )
     9  
    10  // Walk walks the file tree rooted at root, calling walkFunc for each file or
    11  // directory in the tree, including root. All errors that arise visiting files
    12  // and directories are filtered by walkFn. The files are walked in lexical
    13  // order, which makes the output deterministic but means that for very
    14  // large directories Walk can be inefficient.
    15  // Walk does not follow symbolic links.
    16  func Walk(fs Filesystem, root string, walkFunc filepath.WalkFunc) error {
    17  	info, err := fs.Lstat(root)
    18  	if err != nil {
    19  		err = walkFunc(root, nil, err)
    20  	} else {
    21  		err = walk(fs, root, info, walkFunc)
    22  	}
    23  	if err == filepath.SkipDir {
    24  		return nil
    25  	}
    26  	return err
    27  }
    28  
    29  // readDirNames reads the directory named by dirname and returns
    30  // a sorted list of directory entries.
    31  func readDirNames(fs Filesystem, dirname string) ([]string, error) {
    32  	infos, err := fs.ReadDir(dirname)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	names := make([]string, 0, len(infos))
    37  	for _, info := range infos {
    38  		names = append(names, info.Name())
    39  	}
    40  	sort.Strings(names)
    41  	return names, nil
    42  }
    43  
    44  // walk recursively descends path, calling walkFunc.
    45  func walk(fs Filesystem, path string, info os.FileInfo, walkFunc filepath.WalkFunc) error {
    46  	if !info.IsDir() {
    47  		return walkFunc(path, info, nil)
    48  	}
    49  
    50  	names, err := readDirNames(fs, path)
    51  	err1 := walkFunc(path, info, err)
    52  	// If err != nil, walk can't walk into this directory.
    53  	// err1 != nil means walkFn want walk to skip this directory or stop walking.
    54  	// Therefore, if one of err and err1 isn't nil, walk will return.
    55  	if err != nil || err1 != nil {
    56  		// The caller's behavior is controlled by the return value, which is decided
    57  		// by walkFn. walkFn may ignore err and return nil.
    58  		// If walkFn returns SkipDir, it will be handled by the caller.
    59  		// So walk should return whatever walkFn returns.
    60  		return err1
    61  	}
    62  
    63  	for _, name := range names {
    64  		filename := strings.Join([]string{path, name}, string(fs.PathSeparator()))
    65  		fileInfo, err := fs.Lstat(filename)
    66  		if err != nil {
    67  			if err := walkFunc(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
    68  				return err
    69  			}
    70  		} else {
    71  			err = walk(fs, filename, fileInfo, walkFunc)
    72  			if err != nil {
    73  				if !fileInfo.IsDir() || err != filepath.SkipDir {
    74  					return err
    75  				}
    76  			}
    77  		}
    78  	}
    79  	return nil
    80  }