adapters.go

  1package fastwalk
  2
  3import (
  4	"io/fs"
  5	"os"
  6	"path/filepath"
  7)
  8
  9func isDir(path string, d fs.DirEntry) bool {
 10	if d.IsDir() {
 11		return true
 12	}
 13	if d.Type()&os.ModeSymlink != 0 {
 14		if fi, err := StatDirEntry(path, d); err == nil {
 15			return fi.IsDir()
 16		}
 17	}
 18	return false
 19}
 20
 21// IgnoreDuplicateDirs wraps [fs.WalkDirFunc] walkFn to make it follow symbolic
 22// links and ignore duplicate directories (if a symlink points to a directory
 23// that has already been traversed it is skipped). The walkFn is called for
 24// for skipped directories, but the directory is not traversed (this is
 25// required for error handling).
 26//
 27// The Follow [Config] setting has no effect on the behavior of Walk when
 28// this wrapper is used.
 29//
 30// In most use cases, the returned [fs.WalkDirFunc] should not be reused.
 31// If it is reused, any previously visited file will be skipped.
 32//
 33// NOTE: The order of traversal is undefined. Given an "example" directory
 34// like the one below where "dir" is a directory and "smydir1" and "smydir2"
 35// are links to it, only one of "dir", "smydir1", or "smydir2" will be
 36// traversed, but which one is undefined.
 37//
 38//	example
 39//	├── dir
 40//	├── smydir1 -> dir
 41//	└── smydir2 -> dir
 42func IgnoreDuplicateDirs(walkFn fs.WalkDirFunc) fs.WalkDirFunc {
 43	filter := NewEntryFilter()
 44	return func(path string, d fs.DirEntry, err error) error {
 45		// Call walkFn before checking the entry filter so that we
 46		// don't record directories that are skipped with SkipDir.
 47		err = walkFn(path, d, err)
 48		if err != nil {
 49			if err != filepath.SkipDir && isDir(path, d) {
 50				filter.Entry(path, d)
 51			}
 52			return err
 53		}
 54		if isDir(path, d) {
 55			if filter.Entry(path, d) {
 56				return filepath.SkipDir
 57			}
 58			if d.Type() == os.ModeSymlink {
 59				return ErrTraverseLink
 60			}
 61		}
 62		return nil
 63	}
 64}
 65
 66// IgnoreDuplicateFiles wraps walkFn so that symlinks are followed and duplicate
 67// files are ignored. If a symlink resolves to a file that has already been
 68// visited it will be skipped.
 69//
 70// In most use cases, the returned [fs.WalkDirFunc] should not be reused.
 71// If it is reused, any previously visited file will be skipped.
 72//
 73// This can significantly slow Walk as os.Stat() is called for each path
 74// (on Windows, os.Stat() is only needed for symlinks).
 75func IgnoreDuplicateFiles(walkFn fs.WalkDirFunc) fs.WalkDirFunc {
 76	filter := NewEntryFilter()
 77	return func(path string, d fs.DirEntry, err error) error {
 78		// Skip all duplicate files, directories, and links
 79		if filter.Entry(path, d) {
 80			if isDir(path, d) {
 81				return filepath.SkipDir
 82			}
 83			return nil
 84		}
 85		err = walkFn(path, d, err)
 86		if err == nil && d.Type() == os.ModeSymlink && isDir(path, d) {
 87			err = ErrTraverseLink
 88		}
 89		return err
 90	}
 91}
 92
 93// IgnorePermissionErrors wraps walkFn so that [fs.ErrPermission] permission
 94// errors are ignored. The returned [fs.WalkDirFunc] may be reused.
 95func IgnorePermissionErrors(walkFn fs.WalkDirFunc) fs.WalkDirFunc {
 96	return func(path string, d fs.DirEntry, err error) error {
 97		if err != nil && os.IsPermission(err) {
 98			return nil
 99		}
100		return walkFn(path, d, err)
101	}
102}