Skip to content

Commit

Permalink
windows: fix sorting on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
charlievieth committed Jul 18, 2024
1 parent 4515ba8 commit c1c80ff
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 15 deletions.
27 changes: 23 additions & 4 deletions dirent_portable.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"slices"
"strings"
"sync"
)

var _ DirEntry = (*portableDirent)(nil)
Expand Down Expand Up @@ -46,17 +47,35 @@ func fileInfoToDirEntry(dirname string, fi fs.FileInfo) DirEntry {
return newDirEntry(dirname, fs.FileInfoToDirEntry(fi))
}

func sortDirents(mode SortMode, dents []fs.DirEntry) {
var direntSlicePool = sync.Pool{
New: func() any {
a := make([]DirEntry, 0, 32)
return &a
},
}

func putDirentSlice(p *[]DirEntry) {
if p != nil && cap(*p) <= 32*1024 /* 256Kb */ {
a := *p
for i := range a {
a[i] = nil
}
*p = a[:0]
direntSlicePool.Put(p)
}
}

func sortDirents(mode SortMode, dents []DirEntry) {
if len(dents) <= 1 {
return
}
switch mode {
case SortLexical:
slices.SortFunc(dents, func(d1, d2 fs.DirEntry) int {
slices.SortFunc(dents, func(d1, d2 DirEntry) int {
return strings.Compare(d1.Name(), d2.Name())
})
case SortFilesFirst:
slices.SortFunc(dents, func(d1, d2 fs.DirEntry) int {
slices.SortFunc(dents, func(d1, d2 DirEntry) int {
r1 := d1.Type().IsRegular()
r2 := d2.Type().IsRegular()
switch {
Expand All @@ -78,7 +97,7 @@ func sortDirents(mode SortMode, dents []fs.DirEntry) {
return strings.Compare(d1.Name(), d2.Name())
})
case SortDirsFirst:
slices.SortFunc(dents, func(d1, d2 fs.DirEntry) int {
slices.SortFunc(dents, func(d1, d2 DirEntry) int {
dd1 := d1.Type().IsDir()
dd2 := d2.Type().IsDir()
switch {
Expand Down
19 changes: 10 additions & 9 deletions dirent_portable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"time"
)

var _ fs.DirEntry = dirEntry{}
var _ DirEntry = dirEntry{}

// Minimal DirEntry for testing
type dirEntry struct {
Expand All @@ -21,7 +21,8 @@ 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) Info() (fs.FileInfo, error) { return nil, nil }
func (de dirEntry) Info() (fs.FileInfo, error) { panic("not implemented") }
func (de dirEntry) Stat() (fs.FileInfo, error) { panic("not implemented") }

func (de dirEntry) String() string {
return fs.FormatDirEntry(de)
Expand All @@ -30,7 +31,7 @@ func (de dirEntry) String() string {
// NB: this must be kept in sync with the
// TestSortDirents in dirent_unix_test.go
func TestSortDirents(t *testing.T) {
direntNames := func(dents []fs.DirEntry) []string {
direntNames := func(dents []DirEntry) []string {
names := make([]string, len(dents))
for i, d := range dents {
names[i] = d.Name()
Expand All @@ -39,7 +40,7 @@ func TestSortDirents(t *testing.T) {
}

t.Run("None", func(t *testing.T) {
dents := []fs.DirEntry{
dents := []DirEntry{
dirEntry{name: "b"},
dirEntry{name: "a"},
dirEntry{name: "d"},
Expand All @@ -54,15 +55,15 @@ func TestSortDirents(t *testing.T) {
})

rr := rand.New(rand.NewSource(time.Now().UnixNano()))
shuffleDirents := func(dents []fs.DirEntry) []fs.DirEntry {
shuffleDirents := func(dents []DirEntry) []DirEntry {
rr.Shuffle(len(dents), func(i, j int) {
dents[i], dents[j] = dents[j], dents[i]
})
return dents
}

// dents needs to be in the expected order
test := func(t *testing.T, dents []fs.DirEntry, mode SortMode) {
test := func(t *testing.T, dents []DirEntry, mode SortMode) {
want := direntNames(dents)
// Run multiple times with different shuffles
for i := 0; i < 10; i++ {
Expand All @@ -77,7 +78,7 @@ func TestSortDirents(t *testing.T) {
}

t.Run("Lexical", func(t *testing.T) {
dents := []fs.DirEntry{
dents := []DirEntry{
dirEntry{name: "a"},
dirEntry{name: "b"},
dirEntry{name: "c"},
Expand All @@ -87,7 +88,7 @@ func TestSortDirents(t *testing.T) {
})

t.Run("FilesFirst", func(t *testing.T) {
dents := []fs.DirEntry{
dents := []DirEntry{
// Files lexically
dirEntry{name: "f1", typ: 0},
dirEntry{name: "f2", typ: 0},
Expand All @@ -108,7 +109,7 @@ func TestSortDirents(t *testing.T) {
})

t.Run("DirsFirst", func(t *testing.T) {
dents := []fs.DirEntry{
dents := []DirEntry{
// Dirs lexically
dirEntry{name: "d1", typ: fs.ModeDir},
dirEntry{name: "d2", typ: fs.ModeDir},
Expand Down
31 changes: 29 additions & 2 deletions fastwalk_portable.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,47 @@ func (w *walker) readDir(dirName string) error {
return readErr
}

var p *[]DirEntry
if w.sortMode != SortNone {
p = direntSlicePool.Get().(*[]DirEntry)
}
defer putDirentSlice(p)

var skipFiles bool
for _, d := range des {
if skipFiles && d.Type().IsRegular() {
continue
}
// Need to use FileMode.Type().Type() for fs.DirEntry
e := newDirEntry(dirName, d)
if err := w.onDirEnt(dirName, d.Name(), e); err != nil {
if w.sortMode != SortNone {
if err := w.onDirEnt(dirName, d.Name(), e); err != nil {
if err != ErrSkipFiles {
return err
}
skipFiles = true
}
} else {
*p = append(*p, e)
}
}
if w.sortMode == SortNone {
return readErr
}

dents := *p
sortDirents(w.sortMode, dents)
for _, d := range dents {
d := d
if skipFiles && d.Type().IsRegular() {
continue
}
if err := w.onDirEnt(dirName, d.Name(), d); err != nil {
if err != ErrSkipFiles {
return err
}
skipFiles = true
}
}

return readErr
}

0 comments on commit c1c80ff

Please sign in to comment.