1//go:build !darwin && !(aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris)
2
3// TODO: add a "portable_dirent" build tag so that we can test this
4// on non-Windows platforms
5
6package fastwalk
7
8import (
9 "io/fs"
10 "os"
11 "sort"
12 "sync"
13
14 "github.com/charlievieth/fastwalk/internal/fmtdirent"
15)
16
17var _ DirEntry = (*portableDirent)(nil)
18
19type portableDirent struct {
20 fs.DirEntry
21 parent string
22 stat *fileInfo
23 depth uint32
24}
25
26func (d *portableDirent) String() string {
27 return fmtdirent.FormatDirEntry(d)
28}
29
30func (d *portableDirent) Depth() int {
31 return int(d.depth)
32}
33
34func (d *portableDirent) Stat() (fs.FileInfo, error) {
35 if d.DirEntry.Type()&os.ModeSymlink == 0 {
36 return d.DirEntry.Info()
37 }
38 stat := loadFileInfo(&d.stat)
39 stat.once.Do(func() {
40 stat.FileInfo, stat.err = os.Stat(d.parent + string(os.PathSeparator) + d.Name())
41 })
42 return stat.FileInfo, stat.err
43}
44
45func newDirEntry(dirName string, info fs.DirEntry, depth int) DirEntry {
46 return &portableDirent{
47 DirEntry: info,
48 parent: dirName,
49 depth: uint32(depth),
50 }
51}
52
53func fileInfoToDirEntry(dirname string, fi fs.FileInfo) DirEntry {
54 return newDirEntry(dirname, fs.FileInfoToDirEntry(fi), 0)
55}
56
57var direntSlicePool = sync.Pool{
58 New: func() any {
59 a := make([]DirEntry, 0, 32)
60 return &a
61 },
62}
63
64func putDirentSlice(p *[]DirEntry) {
65 // max is half as many as Unix because twice the size
66 if p != nil && cap(*p) <= 16*1024 {
67 a := *p
68 for i := range a {
69 a[i] = nil
70 }
71 *p = a[:0]
72 direntSlicePool.Put(p)
73 }
74}
75
76func sortDirents(mode SortMode, dents []DirEntry) {
77 if len(dents) <= 1 {
78 return
79 }
80 switch mode {
81 case SortLexical:
82 sort.Slice(dents, func(i, j int) bool {
83 return dents[i].Name() < dents[j].Name()
84 })
85 case SortFilesFirst:
86 sort.Slice(dents, func(i, j int) bool {
87 d1 := dents[i]
88 d2 := dents[j]
89 r1 := d1.Type().IsRegular()
90 r2 := d2.Type().IsRegular()
91 switch {
92 case r1 && !r2:
93 return true
94 case !r1 && r2:
95 return false
96 case !r1 && !r2:
97 // Both are not regular files: sort directories last
98 dd1 := d1.Type().IsDir()
99 dd2 := d2.Type().IsDir()
100 switch {
101 case !dd1 && dd2:
102 return true
103 case dd1 && !dd2:
104 return false
105 }
106 }
107 return d1.Name() < d2.Name()
108 })
109 case SortDirsFirst:
110 sort.Slice(dents, func(i, j int) bool {
111 d1 := dents[i]
112 d2 := dents[j]
113 dd1 := d1.Type().IsDir()
114 dd2 := d2.Type().IsDir()
115 switch {
116 case dd1 && !dd2:
117 return true
118 case !dd1 && dd2:
119 return false
120 case !dd1 && !dd2:
121 // Both are not directories: sort regular files first
122 r1 := d1.Type().IsRegular()
123 r2 := d2.Type().IsRegular()
124 switch {
125 case r1 && !r2:
126 return true
127 case !r1 && r2:
128 return false
129 }
130 }
131 return d1.Name() < d2.Name()
132 })
133 }
134}