250511.1 · 3d5486c8bf7e4791ef453e91f1fa993d21420b9d
fastwalk: add new DirEntry.Depth method
The new DirEntry Depth method returns the depth at which the entry was generated relative to the root being walked. This is useful for programs that wish to limit traversal depth.
reading declarations
@@ -53,3 +53,21 @@ func StatDirEntry(path string, de fs.DirEntry) (fs.FileInfo, error) { } return os.Stat(path) }++// DirEntryDepth returns the depth at which entry de was generated relative+// to the root being walked or -1 if de does not have type [fastwalk.DirEntry].+//+// This is a helper function that saves the user from having to cast the+// [fs.DirEntry] argument to their walk function to a [fastwalk.DirEntry]+// and is equivalent to the below code:+//+// if d, _ := de.(DirEntry); d != nil {+// return d.Depth()+// }+// return -1+func DirEntryDepth(de fs.DirEntry) int {+ if d, _ := de.(DirEntry); d != nil {+ return d.Depth()+ }+ return -1+}
@@ -20,12 +20,17 @@ type portableDirent struct { fs.DirEntry parent string stat *fileInfo+ depth uint32 } func (d *portableDirent) String() string { return fmtdirent.FormatDirEntry(d) } +func (d *portableDirent) Depth() int {+ return int(d.depth)+}+ func (d *portableDirent) Stat() (fs.FileInfo, error) { if d.DirEntry.Type()&os.ModeSymlink == 0 { return d.DirEntry.Info()@@ -37,15 +42,16 @@ func (d *portableDirent) Stat() (fs.FileInfo, error) { return stat.FileInfo, stat.err } -func newDirEntry(dirName string, info fs.DirEntry) DirEntry {+func newDirEntry(dirName string, info fs.DirEntry, depth int) DirEntry { return &portableDirent{ DirEntry: info, parent: dirName,+ depth: uint32(depth), } } func fileInfoToDirEntry(dirname string, fi fs.FileInfo) DirEntry {- return newDirEntry(dirname, fs.FileInfoToDirEntry(fi))+ return newDirEntry(dirname, fs.FileInfoToDirEntry(fi), 0) } var direntSlicePool = sync.Pool{
@@ -23,6 +23,7 @@ type dirEntry struct { func (de dirEntry) Name() string { return de.name } func (de dirEntry) IsDir() bool { return de.typ.IsDir() } func (de dirEntry) Type() fs.FileMode { return de.typ.Type() }+func (de dirEntry) Depth() int { panic("not implemented") } func (de dirEntry) Info() (fs.FileInfo, error) { panic("not implemented") } func (de dirEntry) Stat() (fs.FileInfo, error) { panic("not implemented") }
@@ -15,6 +15,7 @@ type unixDirent struct { parent string name string typ fs.FileMode+ depth uint32 // uint32 so that we can pack it next to typ info *fileInfo stat *fileInfo }@@ -22,6 +23,7 @@ type unixDirent struct { func (d *unixDirent) Name() string { return d.name } func (d *unixDirent) IsDir() bool { return d.typ.IsDir() } func (d *unixDirent) Type() fs.FileMode { return d.typ }+func (d *unixDirent) Depth() int { return int(d.depth) } func (d *unixDirent) String() string { return fmtdirent.FormatDirEntry(d) } func (d *unixDirent) Info() (fs.FileInfo, error) {@@ -43,11 +45,12 @@ func (d *unixDirent) Stat() (fs.FileInfo, error) { return stat.FileInfo, stat.err } -func newUnixDirent(parent, name string, typ fs.FileMode) *unixDirent {+func newUnixDirent(parent, name string, typ fs.FileMode, depth int) *unixDirent { return &unixDirent{ parent: parent, name: name, typ: typ,+ depth: uint32(depth), } }
@@ -87,11 +87,11 @@ func TestUnixDirent(t *testing.T) { t.Fatal(err) } t.Run("Stat", func(t *testing.T) {- ent := newUnixDirent(tempdir, filepath.Base(fileName), fileInfo.Mode().Type())+ ent := newUnixDirent(tempdir, filepath.Base(fileName), fileInfo.Mode().Type(), 0) testUnixDirentParallel(t, ent, fileInfo, (*unixDirent).Stat) }) t.Run("Info", func(t *testing.T) {- ent := newUnixDirent(tempdir, filepath.Base(fileName), fileInfo.Mode().Type())+ ent := newUnixDirent(tempdir, filepath.Base(fileName), fileInfo.Mode().Type(), 0) testUnixDirentParallel(t, ent, fileInfo, (*unixDirent).Info) }) })@@ -110,11 +110,11 @@ func TestUnixDirent(t *testing.T) { if err != nil { t.Fatal(err) }- ent := newUnixDirent(tempdir, filepath.Base(linkName), fileInfo.Mode().Type())+ ent := newUnixDirent(tempdir, filepath.Base(linkName), fileInfo.Mode().Type(), 0) testUnixDirentParallel(t, ent, want, (*unixDirent).Stat) }) t.Run("Info", func(t *testing.T) {- ent := newUnixDirent(tempdir, filepath.Base(linkName), fileInfo.Mode().Type())+ ent := newUnixDirent(tempdir, filepath.Base(linkName), fileInfo.Mode().Type(), 0) testUnixDirentParallel(t, ent, fileInfo, (*unixDirent).Info) }) })@@ -232,7 +232,7 @@ func BenchmarkUnixDirentLoadFileInfo(b *testing.B) { b.Fatal(err) } parent, name := filepath.Split(wd)- d := newUnixDirent(parent, name, fi.Mode().Type())+ d := newUnixDirent(parent, name, fi.Mode().Type(), 0) for i := 0; i < b.N; i++ { loadFileInfo(&d.info)@@ -250,7 +250,7 @@ func BenchmarkUnixDirentInfo(b *testing.B) { b.Fatal(err) } parent, name := filepath.Split(wd)- d := newUnixDirent(parent, name, fi.Mode().Type())+ d := newUnixDirent(parent, name, fi.Mode().Type(), 0) for i := 0; i < b.N; i++ { fi, err := d.Info()@@ -273,7 +273,7 @@ func BenchmarkUnixDirentStat(b *testing.B) { b.Fatal(err) } parent, name := filepath.Split(wd)- d := newUnixDirent(parent, name, fi.Mode().Type())+ d := newUnixDirent(parent, name, fi.Mode().Type(), 0) for i := 0; i < b.N; i++ { fi, err := d.Stat()
@@ -330,6 +330,9 @@ type DirEntry interface { // If the entry denotes a symbolic link, Stat reports the information // about the target itself, not the link. Stat() (fs.FileInfo, error)++ // Depth returns the depth at which this entry was generated relative to the root.+ Depth() int } // Walk is a faster implementation of [filepath.WalkDir] that walks the file@@ -380,6 +383,11 @@ type DirEntry interface { // file. The result of Stat() and Info() are cached. The [StatDirEntry] // helper can be used to call Stat() on the returned [fastwalk.DirEntry]. //+// - Additionally, the [fs.DirEntry] argument (which has type [fastwalk.DirEntry]),+// has a Depth() method that returns the depth at which the entry was generated+// relative to the root being walked. The [DirEntryDepth] helper function+// can be used to call Depth() on the [fs.DirEntry] argument.+// // - Walk can follow symlinks in two ways: the fist, and simplest, is to // set Follow [Config] option to true - this will cause Walk to follow // symlinks and detect/ignore any symlink loops; the second, is for walkFn@@ -631,7 +639,7 @@ func (w *walker) walk(root string, info DirEntry, runUserCallback bool) error { } } - err := w.readDir(root)+ err := w.readDir(root, info.Depth()+1) if err != nil { // Second call, to report ReadDir error. return w.fn(root, info, err)
@@ -8,7 +8,7 @@ import ( "unsafe" ) -func (w *walker) readDir(dirName string) (err error) {+func (w *walker) readDir(dirName string, depth int) (err error) { var fd uintptr for { fd, err = opendir(dirName)@@ -68,7 +68,7 @@ func (w *walker) readDir(dirName string) (err error) { continue } nm := string(name)- de := newUnixDirent(dirName, nm, typ)+ de := newUnixDirent(dirName, nm, typ, depth) if w.sortMode == SortNone { if err := w.onDirEnt(dirName, nm, de); err != nil { if err != ErrSkipFiles {
@@ -2,15 +2,13 @@ package fastwalk -import (- "os"-)+import "os" // readDir calls fn for each directory entry in dirName. // It does not descend into directories or follow symlinks. // If fn returns a non-nil error, readDir returns with that error // immediately.-func (w *walker) readDir(dirName string) error {+func (w *walker) readDir(dirName string, depth int) error { f, err := os.Open(dirName) if err != nil { return err@@ -33,7 +31,7 @@ func (w *walker) readDir(dirName string) error { continue } // Need to use FileMode.Type().Type() for fs.DirEntry- e := newDirEntry(dirName, d)+ e := newDirEntry(dirName, d, depth) if w.sortMode == SortNone { if err := w.onDirEnt(dirName, d.Name(), e); err != nil { if err != ErrSkipFiles {
@@ -15,6 +15,7 @@ import ( "regexp" "runtime" "sort"+ "strconv" "strings" "sync" "sync/atomic"@@ -1174,6 +1175,37 @@ func TestSortModeString(t *testing.T) { } } +func TestFastWalk_Depth(t *testing.T) {+ tmp := filepath.Join(t.TempDir(), "root0")+ for _, r := range "abcdef" {+ path := fmt.Sprintf("%[1]s/%[2]s1/%[2]s2/%[2]s3/%[2]s4.txt", tmp, string(r))+ if err := writeFile(path, "", 0666); err != nil {+ t.Fatal(err)+ }+ }++ re := regexp.MustCompile(`(\d+)`)++ err := fastwalk.Walk(nil, tmp, func(path string, typ fs.DirEntry, err error) error {+ if err != nil {+ return err+ }+ a := re.FindAllString(typ.Name(), -1)+ want, err := strconv.Atoi(a[len(a)-1])+ if err != nil {+ return err+ }+ depth := fastwalk.DirEntryDepth(typ)+ if depth != want {+ t.Errorf("%s: got depth: %d want: %d", path, depth, want)+ }+ return nil+ })+ if err != nil {+ t.Fatal(err)+ }+}+ func TestConfigCopy(t *testing.T) { t.Run("Nil", func(t *testing.T) { c := (*fastwalk.Config)(nil).Copy()
@@ -20,7 +20,7 @@ const blockSize = 8192 // value used to represent a syscall.DT_UNKNOWN Dirent.Type. const unknownFileMode os.FileMode = ^os.FileMode(0) -func (w *walker) readDir(dirName string) error {+func (w *walker) readDir(dirName string, depth int) error { fd, err := open(dirName, 0, 0) if err != nil { return &os.PathError{Op: "open", Path: dirName, Err: err}@@ -72,7 +72,7 @@ func (w *walker) readDir(dirName string) error { if skipFiles && typ.IsRegular() { continue }- de := newUnixDirent(dirName, name, typ)+ de := newUnixDirent(dirName, name, typ, depth) if w.sortMode == SortNone { if err := w.onDirEnt(dirName, name, de); err != nil { if err == ErrSkipFiles {