github.com/charlievieth/fastwalk@v1.0.3/adapters.go (about) 1 package fastwalk 2 3 import ( 4 "io/fs" 5 "os" 6 "path/filepath" 7 ) 8 9 func 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 Config.Follow 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 between 31 // in another call to Walk. If it is reused, any previously visited file will 32 // be skipped. 33 // 34 // NOTE: The order of traversal is undefined. Given an "example" directory 35 // like the one below where "dir" is a directory and "smydir1" and "smydir2" 36 // are links to it, only one of "dir", "smydir1", or "smydir2" will be 37 // traversed, but which one is undefined. 38 // 39 // example 40 // ├── dir 41 // ├── smydir1 -> dir 42 // └── smydir2 -> dir 43 func IgnoreDuplicateDirs(walkFn fs.WalkDirFunc) fs.WalkDirFunc { 44 filter := NewEntryFilter() 45 return func(path string, d fs.DirEntry, err error) error { 46 // Call walkFn before checking the entry filter so that we 47 // don't record directories that are skipped with SkipDir. 48 err = walkFn(path, d, err) 49 if err != nil { 50 if err != filepath.SkipDir && isDir(path, d) { 51 filter.Entry(path, d) 52 } 53 return err 54 } 55 if isDir(path, d) { 56 if filter.Entry(path, d) { 57 return filepath.SkipDir 58 } 59 if d.Type() == os.ModeSymlink { 60 return ErrTraverseLink 61 } 62 } 63 return nil 64 } 65 } 66 67 // IgnoreDuplicateFiles wraps walkFn so that symlinks are followed and duplicate 68 // files are ignored. If a symlink resolves to a file that has already been 69 // visited it will be skipped. 70 // 71 // In most use cases, the returned fs.WalkDirFunc should not be reused between 72 // in another call to Walk. If it is reused, any previously visited file will 73 // be skipped. 74 // 75 // This can significantly slow Walk as os.Stat() is called for each path 76 // (on Windows, os.Stat() is only needed for symlinks). 77 func IgnoreDuplicateFiles(walkFn fs.WalkDirFunc) fs.WalkDirFunc { 78 filter := NewEntryFilter() 79 return func(path string, d fs.DirEntry, err error) error { 80 // Skip all duplicate files, directories, and links 81 if filter.Entry(path, d) { 82 if isDir(path, d) { 83 return filepath.SkipDir 84 } 85 return nil 86 } 87 err = walkFn(path, d, err) 88 if err == nil && d.Type() == os.ModeSymlink && isDir(path, d) { 89 err = ErrTraverseLink 90 } 91 return err 92 } 93 } 94 95 // IgnorePermissionErrors wraps walkFn so that permission errors are ignored. 96 // The returned fs.WalkDirFunc may be reused. 97 func IgnorePermissionErrors(walkFn fs.WalkDirFunc) fs.WalkDirFunc { 98 return func(path string, d fs.DirEntry, err error) error { 99 if err != nil && os.IsPermission(err) { 100 return nil 101 } 102 return walkFn(path, d, err) 103 } 104 }